UNPKG

devextreme

Version:

JavaScript/TypeScript Component Suite for Responsive Web Development

812 lines • 54.7 kB
/** * 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