Scheduler: refactor appointment_popup module (TS)#33575
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors the internal Scheduler appointment popup module by splitting/renaming legacy m_* modules into clearer TS modules (form, popup), tightening types around scheduler/form/popup interactions, and extracting form-item customization logic into a dedicated utility with Jest coverage.
Changes:
- Renamed/refactored appointment popup modules (
m_form→form,m_popup→popup) and updated Scheduler + test mocks imports accordingly. - Strengthened TypeScript typings across appointment popup/form/recurrence utilities (e.g.,
DayOfWeek, scheduler “proxy” interfaces). - Added
customize_form_items.tshelper + updated unit tests to use the new module.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/devextreme/js/__internal/scheduler/m_scheduler.ts | Updates appointment popup imports and includes minor formatting cleanups. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/utils.ts | Tightens firstDayOfWeek typing to DayOfWeek. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/recurrence_form.ts | Repoints scheduler typing to the new form scheduler interface and improves typing in component creation. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/popup.ts | Major refactor: introduces explicit state/types and replaces Deferred/when flow with Promises for saving + popup show/hide. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/form.ts | Major refactor: introduces AppointmentFormScheduler interface, updates customization pipeline, and improves typing. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/customize_form_items.ts | New extracted helper for merging default form items with user config. |
| packages/devextreme/js/__internal/scheduler/appointment_popup/customize_form_items.test.ts | Updates tests to import the new helper module. |
| packages/devextreme/js/__internal/scheduler/tests/mock/model/scheduler.ts | Updates appointment popup constant import to the renamed module. |
| packages/devextreme/js/__internal/scheduler/tests/mock/create_appointment_popup.ts | Updates appointment popup/form imports to renamed modules and keeps mocks aligned. |
Comments suppressed due to low confidence (2)
packages/devextreme/js/__internal/scheduler/appointment_popup/popup.ts:139
show()/hide()throwerrors.Error('E1033')whenpopupInstanceis not set.E1033is an existing UI widgets error code for an unrelated case (date navigator step) and expects a parameter, so this will produce a misleading error message here. Prefer not throwing at all (e.g., no-op/return) or throw a dedicated error with a correct code/message; alternatively, capture the instance fromcreateComponentsynchronously sopopupInstanceis always defined before callingshow()/hide().
packages/devextreme/js/__internal/scheduler/appointment_popup/popup.ts:343saveChangesAsync()callshideLoading()only in the success.then()branch. Ifthis.config.onSave()(or the validation promise) rejects/throws, the chain goes to.catch(noop)and the load panel is never hidden, leaving the UI stuck. Use afinallyto always callhideLoading()(or call it in both success and error paths) and avoid swallowing the error before the load panel is cleaned up.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
packages/devextreme/js/__internal/scheduler/appointment_popup/popup.ts:341
- This makes
saveChangesAsyncdefer the save work to a native Promise microtask even when form validation is synchronous.hideAppointmentPopup(true)still callssaveChangesAsync()without awaiting it and then returns immediately, so callers that currently rely on the appointment being saved whenhideAppointmentPopup(true)returns will now observe stale data until the microtask runs (the existing integration tests aroundhideAppointmentPopup(true)assert synchronously). Consider preserving the synchronous path for already-complete validation or updating the caller/API contract to await the returned promise.
packages/devextreme/js/__internal/scheduler/appointment_popup/popup.ts:138 E1033is the date-navigator error ("Unknown step in the date navigator: '{0}'"), so throwing it here would produce a misleading message unrelated to appointment popup creation/hiding. Use an error that matches this failure mode, or keep the previous no-op behavior forhide()when no popup instance exists.
| if (this.popupInstance) { | ||
| this.popupInstance.show().catch(noop); | ||
| } else { | ||
| throw errors.Error('E1033'); |
There was a problem hiding this comment.
E1033: 'Unknown step in the date navigator: '{0}'',
I am not sure that we need to display this error here. And not sure if we need to return any errors here or in hide step
There was a problem hiding this comment.
https://js.devexpress.com/jQuery/Documentation/ApiReference/UI_Components/Errors_and_Warnings/#E1033
A [Scheduler](https://js.devexpress.com/jQuery/Documentation/ApiReference/UI_Components/dxScheduler/) internal error.
To solve the issue, please submit a ticket to our [Support Center](https://www.devexpress.com/ask). Include your UI component configuration, fake data, and the steps needed to reproduce the issue in the ticket.
There was a problem hiding this comment.
OK, looks like a mistake of mine
| getResourceManager: () => this.resourceManager, | ||
|
|
||
| getFirstDayOfWeek: () => this.option('firstDayOfWeek'), | ||
| getFirstDayOfWeek: () => this.getFirstDayOfWeek(), |
There was a problem hiding this comment.
@aleksei-semikozov I think you had a bug related to this string when you were also passing a function instead of option.
There was a problem hiding this comment.
In this function, we use the firstDayOfWeek option in this way, but if this otpion is not specified, the localization settings are applied. This is the same way we use it in the header
|
|
||
| scheduler.instance.showAppointmentPopup(newItem, true); | ||
| $('.dx-scheduler-appointment-popup .dx-popup-done').trigger('dxclick'); | ||
| await Promise.resolve(); |
There was a problem hiding this comment.
Why do we need this in tests?
There was a problem hiding this comment.
because Deferred that was used before refactoring were acting synchronously even that it is Promise analogue and all tests also were set up to check synchronous behavior. After refactoring we used Promise that acts asynchronous and tests were not ready for it - they didn't have any option to wait a bit (save button clicks before Promise resolves) and that's why I needed to add async behavior to tests
Scheduler: refactor
appointment_popupmodule (TS) #4124What does the PR change?
m_prefix from files inappointment_popup/folder.Deferredwith nativePromise.How did you achieve this?
Removing from files _m prefix turned strict mode on, and strict mode complained about missing types and the
Deferredusage (it needed@ts-expect-errorto even compile). Fixed all that: added interfaces, switched toasync/await+ nativePromise.One more thing - some tests broke after switching to
Promisebecause they expected the save to finish synchronously (which Deferred kinda allowed). NativePromisealways resolves async, so addedawait Promise.resolve()after save calls in those tests to let the chain settle before asserting.How can we verify these changes?
pnpm run test-jest