devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
812 lines • 54.7 kB
JavaScript
/**
* DevExtreme (esm/__internal/scheduler/appointments_new/appointments.test.js)
* Version: 25.2.7
* Build date: Tue May 05 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
afterEach,
beforeEach,
describe,
expect,
it,
jest
} from "@jest/globals";
import $ from "../../../core/renderer";
import fx from "../../../common/core/animation/fx";
import {
mockAppointmentDataAccessor
} from "../__mock__/appointment_data_accessor.mock";
import {
getResourceManagerMock
} from "../__mock__/resource_manager.mock";
import {
mockAgendaViewModel,
mockAppointmentCollectorViewModel,
mockGridViewModel
} from "./__mock__/appointment_view_model";
import {
Appointments
} from "./appointments";
import {
APPOINTMENT_CLASSES,
APPOINTMENT_COLLECTOR_CLASSES,
APPOINTMENTS_CONTAINER_CLASS
} from "./const";
const mockAppointmentDataSource = () => ({
getUpdatedAppointment: () => null,
getUpdatedAppointmentKeys: () => []
});
const getProperties = function() {
let options = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
return {
currentView: "week",
tabIndex: 0,
viewModel: [],
items: [],
$allDayContainer: $("<div>"),
appointmentTemplate: "appointment",
appointmentCollectorTemplate: "appointmentCollector",
onAppointmentRendered: () => {},
getStartViewDate: () => new Date(2024, 0, 1),
getSortedAppointments: () => [],
isVirtualScrolling: () => false,
scrollTo: () => {},
getAppointmentDataSource: mockAppointmentDataSource,
getResourceManager: () => getResourceManagerMock(options.resources ?? []),
getDataAccessor: () => mockAppointmentDataAccessor
}
};
const createAppointments = properties => {
const $element = $(".root");
return new Appointments($element, properties)
};
const defaultAppointmentData = {
text: "Test appointment",
startDate: new Date(2024, 0, 1, 9, 0),
endDate: new Date(2024, 0, 1, 10, 0)
};
describe("Appointments", () => {
beforeEach(() => {
fx.off = true;
const $container = $("<div>").addClass("container").appendTo(document.body);
$("<div>").addClass("root").appendTo($container);
$("<div>").addClass("allday-container").appendTo($container)
});
afterEach(() => {
$(".container").remove();
fx.off = false;
jest.useRealTimers()
});
describe("Classes", () => {
it("should have correct container class", () => {
const instance = createAppointments(getProperties());
expect(instance.$element().hasClass(APPOINTMENTS_CONTAINER_CLASS)).toBe(true)
})
});
describe("Rendering", () => {
it("should render view model with grid appointments", () => {
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(defaultAppointmentData, {
sortedIndex: 0
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1)
});
it("should render view model with agenda appointments", () => {
const instance = createAppointments(Object.assign({}, getProperties(), {
currentView: "agenda"
}));
instance.option("viewModel", [mockAgendaViewModel(defaultAppointmentData, {
sortedIndex: 0
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1)
});
it("should render view model with appointment collectors", () => {
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockAppointmentCollectorViewModel(defaultAppointmentData, {
sortedIndex: 0
})]);
expect(instance.$element().find(`.${APPOINTMENT_COLLECTOR_CLASSES.CONTAINER}`).length).toBe(1)
});
it("should rerender all appointments when view model is completely changed", () => {
const data1 = Object.assign({}, defaultAppointmentData);
const data2 = Object.assign({}, defaultAppointmentData, {
text: "Appointment 2"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(data1, {
sortedIndex: 0
}), mockGridViewModel(data2, {
sortedIndex: 1
})]);
const elementsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray();
expect(elementsBefore.length).toBe(2);
const data3 = Object.assign({}, defaultAppointmentData, {
text: "Appointment 3"
});
const data4 = Object.assign({}, defaultAppointmentData, {
text: "Appointment 4"
});
instance.option("viewModel", [mockGridViewModel(data3, {
sortedIndex: 0
}), mockGridViewModel(data4, {
sortedIndex: 1
})]);
const elementsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray();
expect(elementsAfter.length).toBe(2);
expect(elementsAfter[0]).not.toBe(elementsBefore[0]);
expect(elementsAfter[1]).not.toBe(elementsBefore[1])
});
it("should render allDay appointment to the allDay container", () => {
var _instance$option$$all;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData, {
allDay: true
}), {
sortedIndex: 0
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(0);
expect(null === (_instance$option$$all = instance.option().$allDayContainer) || void 0 === _instance$option$$all ? void 0 : _instance$option$$all.find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1)
});
it("should not render allDay agenda appointment to the allDay container", () => {
var _instance$option$$all2;
const instance = createAppointments(Object.assign({}, getProperties(), {
currentView: "agenda"
}));
instance.option("viewModel", [mockAgendaViewModel(Object.assign({}, defaultAppointmentData, {
allDay: true
}), {
sortedIndex: 0
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1);
expect(null === (_instance$option$$all2 = instance.option().$allDayContainer) || void 0 === _instance$option$$all2 ? void 0 : _instance$option$$all2.find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(0)
});
it("should clean all day container when switching from grid view to agenda view", () => {
var _instance$option$$all3, _instance$option$$all4;
const instance = createAppointments(Object.assign({}, getProperties(), {
currentView: "week"
}));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData, {
allDay: true
}), {
sortedIndex: 0
})]);
expect(null === (_instance$option$$all3 = instance.option().$allDayContainer) || void 0 === _instance$option$$all3 ? void 0 : _instance$option$$all3.find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1);
instance.option("currentView", "agenda");
instance.option("viewModel", [mockAgendaViewModel(Object.assign({}, defaultAppointmentData, {
allDay: true
}), {
sortedIndex: 0
})]);
expect(null === (_instance$option$$all4 = instance.option().$allDayContainer) || void 0 === _instance$option$$all4 ? void 0 : _instance$option$$all4.find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(0);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(1)
});
it.each(["appointmentTemplate", "appointmentCollectorTemplate"])("should rerender appointments if %s is changed", optionName => {
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(defaultAppointmentData, {
sortedIndex: 0
})]);
const elementsBefore = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray();
expect(elementsBefore.length).toBe(1);
instance.option(optionName, () => {});
const elementsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray();
expect(elementsAfter.length).toBe(1);
expect(elementsAfter[0]).not.toBe(elementsBefore[0])
})
});
describe("Partial rendering", () => {
it("should render only changed view items on add", () => {
var _instance$getViewItem, _instance$getViewItem2;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
})]);
const viewItemA = instance.getViewItemBySortedIndex(0);
const viewItemB = instance.getViewItemBySortedIndex(1);
const elementA = null === viewItemA || void 0 === viewItemA ? void 0 : viewItemA.$element().get(0);
const elementB = null === viewItemB || void 0 === viewItemB ? void 0 : viewItemB.$element().get(0);
const dataNEW = Object.assign({}, defaultAppointmentData, {
text: "Appointment NEW"
});
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataNEW, {
sortedIndex: 1
}), mockGridViewModel(dataB, {
sortedIndex: 2
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(3);
expect(instance.getViewItemBySortedIndex(0)).toBe(viewItemA);
expect(instance.getViewItemBySortedIndex(2)).toBe(viewItemB);
expect(null === (_instance$getViewItem = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem ? void 0 : _instance$getViewItem.$element().get(0)).toBe(elementA);
expect(null === (_instance$getViewItem2 = instance.getViewItemBySortedIndex(2)) || void 0 === _instance$getViewItem2 ? void 0 : _instance$getViewItem2.$element().get(0)).toBe(elementB)
});
it("should render only changed view items on remove", () => {
var _instance$getViewItem3, _instance$getViewItem4;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
const viewItemA = instance.getViewItemBySortedIndex(0);
const viewItemC = instance.getViewItemBySortedIndex(2);
const elementA = null === viewItemA || void 0 === viewItemA ? void 0 : viewItemA.$element().get(0);
const elementC = null === viewItemC || void 0 === viewItemC ? void 0 : viewItemC.$element().get(0);
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataC, {
sortedIndex: 1
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(2);
expect(instance.getViewItemBySortedIndex(0)).toBe(viewItemA);
expect(instance.getViewItemBySortedIndex(1)).toBe(viewItemC);
expect(null === (_instance$getViewItem3 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem3 ? void 0 : _instance$getViewItem3.$element().get(0)).toBe(elementA);
expect(null === (_instance$getViewItem4 = instance.getViewItemBySortedIndex(1)) || void 0 === _instance$getViewItem4 ? void 0 : _instance$getViewItem4.$element().get(0)).toBe(elementC)
});
it("should render only changed view items on update", () => {
var _instance$getViewItem5, _instance$getViewItem6, _instance$getViewItem7;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
const viewItemA = instance.getViewItemBySortedIndex(0);
const viewItemB = instance.getViewItemBySortedIndex(1);
const viewItemC = instance.getViewItemBySortedIndex(2);
const elementA = null === viewItemA || void 0 === viewItemA ? void 0 : viewItemA.$element().get(0);
const elementB = null === viewItemB || void 0 === viewItemB ? void 0 : viewItemB.$element().get(0);
const elementC = null === viewItemC || void 0 === viewItemC ? void 0 : viewItemC.$element().get(0);
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1,
groupIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(3);
expect(instance.getViewItemBySortedIndex(0)).toBe(viewItemA);
expect(instance.getViewItemBySortedIndex(1)).not.toBe(viewItemB);
expect(instance.getViewItemBySortedIndex(2)).toBe(viewItemC);
expect(null === (_instance$getViewItem5 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem5 ? void 0 : _instance$getViewItem5.$element().get(0)).toBe(elementA);
expect(null === (_instance$getViewItem6 = instance.getViewItemBySortedIndex(1)) || void 0 === _instance$getViewItem6 ? void 0 : _instance$getViewItem6.$element().get(0)).not.toBe(elementB);
expect(null === (_instance$getViewItem7 = instance.getViewItemBySortedIndex(2)) || void 0 === _instance$getViewItem7 ? void 0 : _instance$getViewItem7.$element().get(0)).toBe(elementC)
});
it("should rerender several view items on several items update", () => {
var _instance$getViewItem8, _instance$getViewItem9, _instance$getViewItem0;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment 1"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment 2"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
const viewItem0 = instance.getViewItemBySortedIndex(0);
const viewItem1 = instance.getViewItemBySortedIndex(1);
const viewItem2 = instance.getViewItemBySortedIndex(2);
const element0 = null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().get(0);
const element1 = null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0);
const element2 = null === viewItem2 || void 0 === viewItem2 ? void 0 : viewItem2.$element().get(0);
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1,
groupIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2,
groupIndex: 1
})]);
const appointmentsAfter = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).toArray();
expect(appointmentsAfter.length).toBe(3);
expect(instance.getViewItemBySortedIndex(0)).toBe(viewItem0);
expect(instance.getViewItemBySortedIndex(1)).not.toBe(viewItem1);
expect(instance.getViewItemBySortedIndex(2)).not.toBe(viewItem2);
expect(null === (_instance$getViewItem8 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem8 ? void 0 : _instance$getViewItem8.$element().get(0)).toBe(element0);
expect(null === (_instance$getViewItem9 = instance.getViewItemBySortedIndex(1)) || void 0 === _instance$getViewItem9 ? void 0 : _instance$getViewItem9.$element().get(0)).not.toBe(element1);
expect(null === (_instance$getViewItem0 = instance.getViewItemBySortedIndex(2)) || void 0 === _instance$getViewItem0 ? void 0 : _instance$getViewItem0.$element().get(0)).not.toBe(element2)
});
it("should only resize view item if its size changed", () => {
var _instance$getViewItem1, _instance$getViewItem10, _instance$getViewItem11;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1,
top: 10,
left: 10,
height: 50,
width: 100
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
const viewItemA = instance.getViewItemBySortedIndex(0);
const viewItemB = instance.getViewItemBySortedIndex(1);
const viewItemC = instance.getViewItemBySortedIndex(2);
const elementA = null === viewItemA || void 0 === viewItemA ? void 0 : viewItemA.$element().get(0);
const elementB = null === viewItemB || void 0 === viewItemB ? void 0 : viewItemB.$element().get(0);
const elementC = null === viewItemC || void 0 === viewItemC ? void 0 : viewItemC.$element().get(0);
instance.option("viewModel", [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1,
top: 20,
left: 20,
height: 60,
width: 110
}), mockGridViewModel(dataC, {
sortedIndex: 2
})]);
expect(instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).length).toBe(3);
expect(instance.getViewItemBySortedIndex(0)).toBe(viewItemA);
expect(instance.getViewItemBySortedIndex(1)).toBe(viewItemB);
expect(instance.getViewItemBySortedIndex(2)).toBe(viewItemC);
expect(null === (_instance$getViewItem1 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem1 ? void 0 : _instance$getViewItem1.$element().get(0)).toBe(elementA);
expect(null === (_instance$getViewItem10 = instance.getViewItemBySortedIndex(1)) || void 0 === _instance$getViewItem10 ? void 0 : _instance$getViewItem10.$element().get(0)).toBe(elementB);
expect(null === (_instance$getViewItem11 = instance.getViewItemBySortedIndex(2)) || void 0 === _instance$getViewItem11 ? void 0 : _instance$getViewItem11.$element().get(0)).toBe(elementC);
expect($(elementB).css("top")).toBe("20px");
expect($(elementB).css("left")).toBe("20px");
expect($(elementB).css("height")).toBe("60px");
expect($(elementB).css("width")).toBe("110px")
})
});
describe("Resources", () => {
it("should apply resource color", async () => {
const instance = createAppointments(Object.assign({}, getProperties({
resources: [{
fieldExpr: "roomId",
dataSource: [{
text: "Room 1",
id: 1,
color: "red"
}]
}]
})));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData, {
roomId: 1
}), {
sortedIndex: 0
})]);
await new Promise(process.nextTick);
const $appointment = instance.$element().find(`.${APPOINTMENT_CLASSES.CONTAINER}`).first();
expect($appointment.css("backgroundColor")).toBe("red")
})
});
describe("Options", () => {
describe("tabIndex", () => {
it("should pass tabIndex change to view items", () => {
var _instance$getViewItem12, _instance$getViewItem13, _instance$getViewItem14;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), mockAppointmentCollectorViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})]);
instance.option("tabIndex", 2);
expect(null === (_instance$getViewItem12 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem12 ? void 0 : _instance$getViewItem12.$element().attr("tabindex")).toBe("2");
expect(null === (_instance$getViewItem13 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem13 ? void 0 : _instance$getViewItem13.$element().attr("tabindex")).toBe("-1");
expect(null === (_instance$getViewItem14 = instance.getViewItemByIndex(2)) || void 0 === _instance$getViewItem14 ? void 0 : _instance$getViewItem14.$element().attr("tabindex")).toBe("-1")
});
it("should keep only focused appointment tabbable on tabIndex change", () => {
var _instance$getViewItem15, _instance$getViewItem16, _instance$getViewItem17, _instance$getViewItem18;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), mockAppointmentCollectorViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})]);
(null === (_instance$getViewItem15 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem15 ? void 0 : _instance$getViewItem15.$element().get(0)).click();
instance.option("tabIndex", 2);
expect(null === (_instance$getViewItem16 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem16 ? void 0 : _instance$getViewItem16.$element().attr("tabindex")).toBe("-1");
expect(null === (_instance$getViewItem17 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem17 ? void 0 : _instance$getViewItem17.$element().attr("tabindex")).toBe("2");
expect(null === (_instance$getViewItem18 = instance.getViewItemByIndex(2)) || void 0 === _instance$getViewItem18 ? void 0 : _instance$getViewItem18.$element().attr("tabindex")).toBe("-1")
});
it("should not rerender view items on tabIndex change", () => {
var _instance$getViewItem19, _instance$getViewItem20, _instance$getViewItem21, _instance$getViewItem22;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockAppointmentCollectorViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
})]);
const element0 = null === (_instance$getViewItem19 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem19 ? void 0 : _instance$getViewItem19.$element().get(0);
const element1 = null === (_instance$getViewItem20 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem20 ? void 0 : _instance$getViewItem20.$element().get(0);
instance.option("tabIndex", 2);
expect(null === (_instance$getViewItem21 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem21 ? void 0 : _instance$getViewItem21.$element().get(0)).toBe(element0);
expect(null === (_instance$getViewItem22 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem22 ? void 0 : _instance$getViewItem22.$element().get(0)).toBe(element1)
});
it("should have first appointment have correct index after tabIndex changed from -1 to 0", () => {
var _instance$getViewItem23, _instance$getViewItem24;
const instance = createAppointments(Object.assign({}, getProperties(), {
tabIndex: -1
}));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
})]);
instance.option("tabIndex", 0);
expect(null === (_instance$getViewItem23 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem23 ? void 0 : _instance$getViewItem23.$element().attr("tabindex")).toBe("0");
expect(null === (_instance$getViewItem24 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem24 ? void 0 : _instance$getViewItem24.$element().attr("tabindex")).toBe("-1")
})
})
});
describe("Focus and keyboard navigation", () => {
describe("Basic navigation", () => {
it("should set tabindex=0 on first appointment and tabindex=-1 on others after render", () => {
var _instance$getViewItem25, _instance$getViewItem26, _instance$getViewItem27;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), mockAppointmentCollectorViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})]);
expect(null === (_instance$getViewItem25 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem25 ? void 0 : _instance$getViewItem25.$element().attr("tabindex")).toBe("0");
expect(null === (_instance$getViewItem26 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem26 ? void 0 : _instance$getViewItem26.$element().attr("tabindex")).toBe("-1");
expect(null === (_instance$getViewItem27 = instance.getViewItemByIndex(2)) || void 0 === _instance$getViewItem27 ? void 0 : _instance$getViewItem27.$element().attr("tabindex")).toBe("-1")
});
it("should restore tabindex=0 on first appointment and tabindex=-1 on others after rerender", () => {
var _instance$getViewItem28, _instance$getViewItem29, _instance$getViewItem30;
const instance = createAppointments(getProperties());
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), mockAppointmentCollectorViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})]);
instance.option("appointmentTemplate", () => {});
expect(null === (_instance$getViewItem28 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem28 ? void 0 : _instance$getViewItem28.$element().attr("tabindex")).toBe("0");
expect(null === (_instance$getViewItem29 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem29 ? void 0 : _instance$getViewItem29.$element().attr("tabindex")).toBe("-1");
expect(null === (_instance$getViewItem30 = instance.getViewItemByIndex(2)) || void 0 === _instance$getViewItem30 ? void 0 : _instance$getViewItem30.$element().attr("tabindex")).toBe("-1")
})
});
describe.each(["appointment", "appointmentCollector"])("Basic navigation for %s", type => {
const createItem = (data, overrides) => "appointmentCollector" === type ? mockAppointmentCollectorViewModel(data, overrides) : mockGridViewModel(data, overrides);
it("should move focus to next view item on Tab", () => {
const viewModel = [createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})];
const instance = createAppointments(Object.assign({}, getProperties(), {
getSortedAppointments: () => viewModel
}));
instance.option("viewModel", viewModel);
const viewItem0 = instance.getViewItemBySortedIndex(0);
const viewItem1 = instance.getViewItemBySortedIndex(1);
(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().get(0)).click();
null === viewItem0 || void 0 === viewItem0 || viewItem0.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().attr("tabindex")).toBe("-1");
expect(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().attr("tabindex")).toBe("0");
expect(document.activeElement).toBe(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0))
});
it("should move focus to previous view item on Shift+Tab", () => {
const viewModel = [createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})];
const instance = createAppointments(Object.assign({}, getProperties(), {
getSortedAppointments: () => viewModel
}));
instance.option("viewModel", viewModel);
const viewItem0 = instance.getViewItemBySortedIndex(0);
const viewItem1 = instance.getViewItemBySortedIndex(1);
(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0)).click();
null === viewItem1 || void 0 === viewItem1 || viewItem1.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
shiftKey: true,
bubbles: true
}));
expect(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().attr("tabindex")).toBe("0");
expect(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().attr("tabindex")).toBe("-1");
expect(document.activeElement).toBe(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().get(0))
});
it("should focus view item on click", () => {
const instance = createAppointments(getProperties());
instance.option("viewModel", [createItem(defaultAppointmentData, {
sortedIndex: 0
})]);
const viewItem = instance.getViewItemBySortedIndex(0);
const element = null === viewItem || void 0 === viewItem ? void 0 : viewItem.$element().get(0);
element.click();
expect(element.getAttribute("tabindex")).toBe("0");
expect(element.classList.contains("dx-state-focused")).toBe(true);
expect(document.activeElement).toBe(element)
});
it("should reset focused state when focus moves outside the container", () => {
var _instance$getViewItem31, _instance$getViewItem32, _instance$getViewItem33;
const externalButton = $("<button>").prependTo($(".container")).get(0);
const instance = createAppointments(getProperties());
instance.option("viewModel", [createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), createItem(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
})]);
(null === (_instance$getViewItem31 = instance.getViewItemBySortedIndex(1)) || void 0 === _instance$getViewItem31 ? void 0 : _instance$getViewItem31.$element().get(0)).click();
externalButton.focus();
expect(null === (_instance$getViewItem32 = instance.getViewItemByIndex(0)) || void 0 === _instance$getViewItem32 ? void 0 : _instance$getViewItem32.$element().attr("tabindex")).toBe("0");
expect(null === (_instance$getViewItem33 = instance.getViewItemByIndex(1)) || void 0 === _instance$getViewItem33 ? void 0 : _instance$getViewItem33.$element().attr("tabindex")).toBe("-1")
})
});
describe("Virtual scrolling navigation", () => {
const makeSortedEntity = function(sortedIndex) {
let startDate = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : new Date(2024, 0, 1, 9, 0);
return {
sortedIndex: sortedIndex,
allDay: false,
itemData: {},
source: {
startDate: startDate.getTime(),
endDate: startDate.getTime() + 36e5
}
}
};
it("should call scrollTo on Tab when virtual scrolling is enabled", () => {
const scrollTo = jest.fn();
const instance = createAppointments(Object.assign({}, getProperties(), {
isVirtualScrolling: () => true,
scrollTo: scrollTo,
getSortedAppointments: () => [makeSortedEntity(0), makeSortedEntity(1), makeSortedEntity(1)]
}));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
})]);
const viewItem1 = instance.getViewItemBySortedIndex(0);
(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0)).click();
null === viewItem1 || void 0 === viewItem1 || viewItem1.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(scrollTo).toHaveBeenCalled()
});
it("should focus next appointment directly if it is already rendered", () => {
const instance = createAppointments(Object.assign({}, getProperties(), {
isVirtualScrolling: () => true,
getSortedAppointments: () => [makeSortedEntity(0), makeSortedEntity(1), makeSortedEntity(2)]
}));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
}), mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
})]);
const viewItem1 = instance.getViewItemBySortedIndex(1);
const viewItem2 = instance.getViewItemBySortedIndex(2);
(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0)).click();
null === viewItem1 || void 0 === viewItem1 || viewItem1.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(document.activeElement).toBe(null === viewItem2 || void 0 === viewItem2 ? void 0 : viewItem2.$element().get(0))
});
it("should restore focus to target appointment after it scrolls into view", () => {
var _instance$getViewItem34;
const item0 = mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
});
const item1 = mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 1
});
const item2 = mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 2
});
const instance = createAppointments(Object.assign({}, getProperties(), {
isVirtualScrolling: () => true,
getSortedAppointments: () => [makeSortedEntity(0), makeSortedEntity(1), makeSortedEntity(2)]
}));
instance.option("viewModel", [item0, item1]);
const viewItem1 = instance.getViewItemBySortedIndex(1);
(null === viewItem1 || void 0 === viewItem1 ? void 0 : viewItem1.$element().get(0)).click();
null === viewItem1 || void 0 === viewItem1 || viewItem1.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(instance.getViewItemBySortedIndex(2)).toBeUndefined();
instance.option("viewModel", [item0, item1, item2]);
const $viewItem2 = null === (_instance$getViewItem34 = instance.getViewItemBySortedIndex(2)) || void 0 === _instance$getViewItem34 ? void 0 : _instance$getViewItem34.$element();
expect(document.activeElement).toBe(null === $viewItem2 || void 0 === $viewItem2 ? void 0 : $viewItem2.get(0));
expect(null === $viewItem2 || void 0 === $viewItem2 ? void 0 : $viewItem2.attr("tabindex")).toBe("0")
});
it("should pass appointment start date to scrollTo when it is after the start view date", () => {
const scrollTo = jest.fn();
const startViewDate = new Date(2024, 0, 1);
const appointmentStartDate = new Date(2024, 0, 5, 9, 0);
const instance = createAppointments(Object.assign({}, getProperties(), {
isVirtualScrolling: () => true,
scrollTo: scrollTo,
getStartViewDate: () => startViewDate,
getSortedAppointments: () => [makeSortedEntity(0), makeSortedEntity(1, appointmentStartDate)]
}));
instance.option("viewModel", [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
})]);
const viewItem0 = instance.getViewItemBySortedIndex(0);
(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().get(0)).click();
null === viewItem0 || void 0 === viewItem0 || viewItem0.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(scrollTo).toHaveBeenCalledWith(appointmentStartDate, expect.anything())
});
it("should clamp scrollTo date to start view date when appointment starts before it", () => {
const scrollTo = jest.fn();
const startViewDate = new Date(2024, 0, 1);
const appointmentStartDate = new Date(2023, 11, 31, 9, 0);
const viewModel = [mockGridViewModel(Object.assign({}, defaultAppointmentData), {
sortedIndex: 0
})];
const sortedEntities = [makeSortedEntity(0), makeSortedEntity(1, appointmentStartDate)];
const instance = createAppointments(Object.assign({}, getProperties(), {
isVirtualScrolling: () => true,
scrollTo: scrollTo,
getStartViewDate: () => startViewDate,
getSortedAppointments: () => sortedEntities
}));
instance.option("viewModel", viewModel);
const viewItem0 = instance.getViewItemBySortedIndex(0);
(null === viewItem0 || void 0 === viewItem0 ? void 0 : viewItem0.$element().get(0)).click();
null === viewItem0 || void 0 === viewItem0 || viewItem0.$element().get(0).dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}));
expect(scrollTo).toHaveBeenCalledWith(startViewDate, expect.anything())
})
});
describe("Navigation after partial render", () => {
const pressTab = () => {
const activeElement = document.activeElement;
activeElement.dispatchEvent(new KeyboardEvent("keydown", {
key: "Tab",
bubbles: true
}))
};
it("should navigate to the last appointment correctly after an appointment is added", () => {
var _instance$getViewItem35;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
let viewModel = [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})];
const instance = createAppointments(Object.assign({}, getProperties(), {
getSortedAppointments: () => viewModel
}));
instance.option("viewModel", viewModel);
const dataNEW = Object.assign({}, defaultAppointmentData, {
text: "Appointment NEW"
});
viewModel = [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataNEW, {
sortedIndex: 2
}), mockGridViewModel(dataC, {
sortedIndex: 3
})];
instance.option("viewModel", viewModel);
(null === (_instance$getViewItem35 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem35 ? void 0 : _instance$getViewItem35.$element().get(0)).click();
pressTab();
pressTab();
pressTab();
const lastViewItem = instance.getViewItemBySortedIndex(3);
expect(document.activeElement).toBe(null === lastViewItem || void 0 === lastViewItem ? void 0 : lastViewItem.$element().get(0));
expect(null === lastViewItem || void 0 === lastViewItem ? void 0 : lastViewItem.$element().attr("tabindex")).toBe("0")
});
it("should navigate to the last appointment correctly after an appointment is removed", () => {
var _instance$getViewItem36;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
let viewModel = [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})];
const instance = createAppointments(Object.assign({}, getProperties(), {
getSortedAppointments: () => viewModel
}));
instance.option("viewModel", viewModel);
viewModel = [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataC, {
sortedIndex: 1
})];
instance.option("viewModel", viewModel);
(null === (_instance$getViewItem36 = instance.getViewItemBySortedIndex(0)) || void 0 === _instance$getViewItem36 ? void 0 : _instance$getViewItem36.$element().get(0)).click();
pressTab();
const lastViewItem = instance.getViewItemBySortedIndex(1);
expect(document.activeElement).toBe(null === lastViewItem || void 0 === lastViewItem ? void 0 : lastViewItem.$element().get(0));
expect(null === lastViewItem || void 0 === lastViewItem ? void 0 : lastViewItem.$element().attr("tabindex")).toBe("0")
});
it("should navigate to the last appointment correctly after an appointment is updated", () => {
var _instance$getViewItem37;
const dataA = Object.assign({}, defaultAppointmentData);
const dataB = Object.assign({}, defaultAppointmentData, {
text: "Appointment B"
});
const dataC = Object.assign({}, defaultAppointmentData, {
text: "Appointment C"
});
let viewModel = [mockGridViewModel(dataA, {
sortedIndex: 0
}), mockGridViewModel(dataB, {
sortedIndex: 1
}), mockGridViewModel(dataC, {
sortedIndex: 2
})];
const instance = createAppointments(Object.assign({}, getPropert