From 6d3d3b4e706697647187c1f289da0e5fae836e6c Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:54:34 +0400 Subject: [PATCH 1/2] test(scheduler): migrate popup customization tests to isolated environment --- .../appointment_popup.integration.test.ts | 342 ------------------ .../appointment_popup.test.ts | 265 ++++++++++++++ 2 files changed, 265 insertions(+), 342 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.integration.test.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.integration.test.ts index dd73a58c5ace..10f73412d58b 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.integration.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.integration.test.ts @@ -1388,53 +1388,6 @@ describe('Appointment Form', () => { }); }); }); - - describe('Customization', () => { - it('should propagate editing.form options to the form instance', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - form: { - height: 500, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const { dxForm: form } = POM.popup; - const formHeight = form.option('height') as number; - - expect(formHeight).toBe(500); - }); - - it('should merge editing.form options with default form configuration', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - form: { - height: 500, - elementAttr: { id: 'custom-form' }, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const { dxForm: form } = POM.popup; - const formHeight = form.option('height') as number; - const elementAttr = form.option('elementAttr') as { class?: string; id?: string }; - const { class: className, id } = elementAttr; - - expect(formHeight).toBe(500); - expect(className).toBe('dx-scheduler-form'); - expect(id).toBe('custom-form'); - }); - }); }); describe('Appointment Popup', () => { @@ -1628,214 +1581,6 @@ describe('Appointment Popup', () => { }); describe('Customization', () => { - it('should pass custom popup options from editing.popup to appointment popup', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - showTitle: true, - title: 'Custom Appointment Form', - maxHeight: '80%', - dragEnabled: true, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.popup.component.option('showTitle')).toBe(true); - expect(POM.popup.component.option('title')).toBe('Custom Appointment Form'); - expect(POM.popup.component.option('maxHeight')).toBe('80%'); - expect(POM.popup.component.option('dragEnabled')).toBe(true); - }); - - it('should use default popup options when editing.popup is not specified', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.popup.component.option('showTitle')).toBe(false); - expect(POM.popup.component.option('height')).toBe('auto'); - expect(POM.popup.component.option('maxHeight')).toBe('90%'); - }); - - it('should merge custom popup options with default options', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - showTitle: true, - title: 'My Form', - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.popup.component.option('showTitle')).toBe(true); - expect(POM.popup.component.option('title')).toBe('My Form'); - - expect(POM.popup.component.option('showCloseButton')).toBe(false); - expect(POM.popup.component.option('enableBodyScroll')).toBe(false); - expect(POM.popup.component.option('preventScrollEvents')).toBe(false); - }); - - it('should allow overriding default popup options', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - showCloseButton: true, - enableBodyScroll: true, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.popup.component.option('showCloseButton')).toBe(true); - expect(POM.popup.component.option('enableBodyScroll')).toBe(true); - }); - - it('should apply wrapperAttr configuration to popup', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - wrapperAttr: { - id: 'test', - }, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const wrapperAttr = POM.popup.component.option('wrapperAttr'); - expect(wrapperAttr.id).toBe('test'); - expect(wrapperAttr.class).toBeDefined(); - }); - - it('should call onInitialized callback when popup is initialized', async () => { - const onInitialized = jest.fn(); - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - onInitialized, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.isPopupVisible()).toBe(true); - expect(onInitialized).toHaveBeenCalled(); - expect(onInitialized).toHaveBeenCalledTimes(1); - }); - - it('should call onShowing callback when popup is shown', async () => { - const onShowing = jest.fn(); - const onAppointmentFormOpening = jest.fn(); - const { scheduler } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - onShowing, - }, - }, - onAppointmentFormOpening, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(onShowing).toHaveBeenCalled(); - expect(onShowing).toHaveBeenCalledTimes(1); - expect(onAppointmentFormOpening).toHaveBeenCalled(); - expect(onAppointmentFormOpening).toHaveBeenCalledTimes(1); - }); - - it('should call onHiding callback when popup is hidden', async () => { - const onHiding = jest.fn(); - const { scheduler } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - onHiding, - }, - }, - }); - - const focusSpy = jest.spyOn(scheduler, 'focus'); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(onHiding).not.toHaveBeenCalled(); - expect(focusSpy).not.toHaveBeenCalled(); - - scheduler.hideAppointmentPopup(); - - expect(onHiding).toHaveBeenCalled(); - expect(onHiding).toHaveBeenCalledTimes(1); - expect(focusSpy).toHaveBeenCalled(); - expect(focusSpy).toHaveBeenCalledTimes(1); - - focusSpy.mockRestore(); - }); - - it('should preserve custom toolbarItems when popup opens', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - popup: { - toolbarItems: [{ - toolbar: 'top', location: 'before', text: 'Custom Title', cssClass: 'custom-title', - }, { - toolbar: 'top', location: 'after', widget: 'dxButton', options: { text: 'Custom Save' }, - }], - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const toolbarItems = POM.popup.component.option('toolbarItems'); - - expect(toolbarItems).toBeDefined(); - expect(toolbarItems).toHaveLength(2); - expect(toolbarItems).toContainEqual(expect.objectContaining({ - cssClass: 'custom-title', location: 'before', text: 'Custom Title', toolbar: 'top', - })); - expect(toolbarItems).toContainEqual(expect.objectContaining( - { - toolbar: 'top', - location: 'after', - widget: 'dxButton', - options: expect.objectContaining({ text: 'Custom Save' }), - }, - )); - }); - it('should preserve custom toolbarItems when popup is reopened', async () => { const { scheduler, POM } = await createScheduler({ ...getDefaultConfig(), @@ -1857,93 +1602,6 @@ describe('Appointment Popup', () => { expect(toolbarItems).toHaveLength(1); expect(toolbarItems?.[0]?.text).toBe('Custom Toolbar'); }); - - it('should open popup if popup.deferRendering is false', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - deferRendering: false, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - expect(POM.isPopupVisible()).toBe(true); - }); - - describe('Popup width and maxWidth options', () => { - // Mock window width to avoid fullscreen mode - beforeEach(() => { - Object.defineProperty(document.documentElement, 'clientWidth', { - value: 1280, - }); - }); - - it('should use custom maxWidth when specified', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - maxWidth: 500, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const maxWidth = POM.popup.component.option('maxWidth'); - expect(maxWidth).toBe(500); - }); - - it('should use custom width as maxWidth when maxWidth is not specified', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - width: 600, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const width = POM.popup.component.option('width'); - expect(width).toBe(600); - - const maxWidth = POM.popup.component.option('maxWidth'); - expect(maxWidth).toBe(600); - }); - - it('should use maxWidth option value (not width) for maxWidth when both maxWidth and width are specified', async () => { - const { scheduler, POM } = await createScheduler({ - ...getDefaultConfig(), - editing: { - allowAdding: true, - allowUpdating: true, - popup: { - width: 600, - maxWidth: 500, - }, - }, - }); - - scheduler.showAppointmentPopup(commonAppointment); - - const width = POM.popup.component.option('width'); - expect(width).toBe(600); - - const maxWidth = POM.popup.component.option('maxWidth'); - expect(maxWidth).toBe(500); - }); - }); }); }); diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts index c17f7cef136f..6d6b077b4443 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts @@ -936,4 +936,269 @@ describe('Isolated AppointmentPopup environment', () => { expect(POM.isRecurrenceGroupVisible()).toBe(true); }); }); + + describe('Popup Customization (editing.popup)', () => { + it('should apply custom options from editing.popup to the popup instance', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { + showTitle: true, + title: 'Custom Appointment Form', + maxHeight: '80%', + dragEnabled: true, + }, + }, + }); + + const showTitle = POM.component.option('showTitle'); + const title = POM.component.option('title'); + const maxHeight = POM.component.option('maxHeight'); + const dragEnabled = POM.component.option('dragEnabled'); + + expect(showTitle).toBe(true); + expect(title).toBe('Custom Appointment Form'); + expect(maxHeight).toBe('80%'); + expect(dragEnabled).toBe(true); + }); + + it('should use default popup options when editing.popup is not specified', async () => { + const { POM } = await createAppointmentPopup(); + + const showTitle = POM.component.option('showTitle'); + const height = POM.component.option('height'); + const maxHeight = POM.component.option('maxHeight'); + + expect(showTitle).toBe(false); + expect(height).toBe('auto'); + expect(maxHeight).toBe('90%'); + }); + + it('should merge custom options with defaults preserving unspecified defaults', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { + showTitle: true, + title: 'My Form', + }, + }, + }); + + const showTitle = POM.component.option('showTitle'); + const title = POM.component.option('title'); + const showCloseButton = POM.component.option('showCloseButton'); + const enableBodyScroll = POM.component.option('enableBodyScroll'); + const preventScrollEvents = POM.component.option('preventScrollEvents'); + + expect(showTitle).toBe(true); + expect(title).toBe('My Form'); + expect(showCloseButton).toBe(false); + expect(enableBodyScroll).toBe(false); + expect(preventScrollEvents).toBe(false); + }); + + it('should allow overriding default popup options', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { + showCloseButton: true, + enableBodyScroll: true, + }, + }, + }); + + const showCloseButton = POM.component.option('showCloseButton'); + const enableBodyScroll = POM.component.option('enableBodyScroll'); + + expect(showCloseButton).toBe(true); + expect(enableBodyScroll).toBe(true); + }); + + it('should apply wrapperAttr and preserve default class', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { + wrapperAttr: { id: 'test' }, + }, + }, + }); + + const wrapperAttr = POM.component.option('wrapperAttr'); + + expect(wrapperAttr.id).toBe('test'); + expect(wrapperAttr.class).toBeDefined(); + }); + + it('should call onInitialized callback and preserve popup initialization', async () => { + const onInitialized = jest.fn(); + + const { POM } = await createAppointmentPopup({ + editing: { + popup: { onInitialized }, + }, + }); + + const isVisible = POM.component.option('visible'); + + expect(onInitialized).toHaveBeenCalledTimes(1); + expect(isVisible).toBe(true); + }); + + it('should call onShowing callback and trigger onAppointmentFormOpening', async () => { + const onShowing = jest.fn(); + const onAppointmentFormOpening = jest.fn(); + + await createAppointmentPopup({ + editing: { + popup: { onShowing }, + }, + onAppointmentFormOpening, + }); + + expect(onShowing).toHaveBeenCalledTimes(1); + expect(onAppointmentFormOpening).toHaveBeenCalledTimes(1); + }); + + it('should call onHiding callback and preserve default focus behavior on close', async () => { + const onHiding = jest.fn(); + + const { POM, callbacks } = await createAppointmentPopup({ + editing: { + popup: { onHiding }, + }, + }); + + const hidingCallsBefore = onHiding.mock.calls.length; + const focusCallsBefore = callbacks.focus.mock.calls.length; + + POM.cancelButton.click(); + + const hidingCallsAfter = onHiding.mock.calls.length; + const focusCallsAfter = callbacks.focus.mock.calls.length; + + expect(hidingCallsBefore).toBe(0); + expect(focusCallsBefore).toBe(0); + expect(hidingCallsAfter).toBe(1); + expect(focusCallsAfter).toBe(1); + }); + + it('should use custom toolbarItems instead of default ones', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { + toolbarItems: [{ + toolbar: 'top', location: 'before', text: 'Custom Title', cssClass: 'custom-title', + }, { + toolbar: 'top', location: 'after', widget: 'dxButton', options: { text: 'Custom Save' }, + }], + }, + }, + }); + + const toolbarItems = POM.component.option('toolbarItems'); + + expect(toolbarItems).toHaveLength(2); + expect(toolbarItems).toContainEqual(expect.objectContaining({ + cssClass: 'custom-title', location: 'before', text: 'Custom Title', toolbar: 'top', + })); + expect(toolbarItems).toContainEqual(expect.objectContaining({ + toolbar: 'top', + location: 'after', + widget: 'dxButton', + options: expect.objectContaining({ text: 'Custom Save' }), + })); + }); + + it('should render popup when deferRendering is false', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { deferRendering: false }, + }, + }); + + const isVisible = POM.component.option('visible'); + + expect(isVisible).toBe(true); + }); + + describe('width and maxWidth', () => { + beforeEach(() => { + Object.defineProperty(document.documentElement, 'clientWidth', { + value: 1280, + configurable: true, + }); + }); + + it('should use custom maxWidth when specified', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { maxWidth: 500 }, + }, + }); + + const maxWidth = POM.component.option('maxWidth'); + + expect(maxWidth).toBe(500); + }); + + it('should use width as maxWidth when only width is specified', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { width: 600 }, + }, + }); + + const width = POM.component.option('width'); + const maxWidth = POM.component.option('maxWidth'); + + expect(width).toBe(600); + expect(maxWidth).toBe(600); + }); + + it('should use maxWidth value when both width and maxWidth are specified', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + popup: { width: 600, maxWidth: 500 }, + }, + }); + + const width = POM.component.option('width'); + const maxWidth = POM.component.option('maxWidth'); + + expect(width).toBe(600); + expect(maxWidth).toBe(500); + }); + }); + }); + + describe('Form Customization (editing.form)', () => { + it('should propagate editing.form options to the form instance', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + form: { height: 500 }, + }, + }); + + const formHeight = POM.dxForm.option('height'); + + expect(formHeight).toBe(500); + }); + + it('should merge editing.form options with defaults preserving elementAttr.class', async () => { + const { POM } = await createAppointmentPopup({ + editing: { + form: { + height: 500, + elementAttr: { id: 'custom-form' }, + }, + }, + }); + + const formHeight = POM.dxForm.option('height') as number; + const elementAttr = POM.dxForm.option('elementAttr') as { class?: string; id?: string }; + + expect(formHeight).toBe(500); + expect(elementAttr.class).toBe('dx-scheduler-form'); + expect(elementAttr.id).toBe('custom-form'); + }); + }); }); From e9bae03b7dfcc3600e77e842f34e2b11cee91e37 Mon Sep 17 00:00:00 2001 From: Maksim Zakharov <251575087+bit-byte0@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:03:32 +0400 Subject: [PATCH 2/2] fix: add missing closing braces after merge resolution --- .../scheduler/appointment_popup/appointment_popup.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts index c611d797aea5..780d6f4ccbbc 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/appointment_popup.test.ts @@ -1199,6 +1199,9 @@ describe('Isolated AppointmentPopup environment', () => { expect(formHeight).toBe(500); expect(elementAttr.class).toBe('dx-scheduler-form'); expect(elementAttr.id).toBe('custom-form'); + }); + }); + describe('Resources', () => { it('should create resourceEditorsGroup when resources have no custom icons', async () => { const { POM } = await createAppointmentPopup({