Skip to content

Commit 40cde54

Browse files
authored
Scheduler: T1308596 — "Uncaught TypeError: Cannot read properties of undefined (reading 'getBoundingClientRect')" is thrown when dragging an appointment while an onAppointmentUpdating promise is pending (#31642)
1 parent 60c9172 commit 40cde54

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

e2e/testcafe-devextreme/tests/scheduler/common/dragAndDrop/dragEvents.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,48 @@ TEST_CASES.forEach(({ view, expectedToItemData }) => {
127127
await clearCallbackTesting();
128128
});
129129
});
130+
131+
test('Should block appointment dragging while onAppointmentUpdating Promise is pending (T1308596)', async (t) => {
132+
const scheduler = new Scheduler(SCHEDULER_SELECTOR);
133+
const appointment = scheduler.getAppointment('Test Appointment');
134+
135+
const targetCell1 = scheduler.getDateTableCell(18, 2);
136+
const targetCell2 = scheduler.getDateTableCell(18, 5);
137+
138+
const initialPosition = await appointment.element.boundingClientRect;
139+
140+
await t.dragToElement(appointment.element, targetCell1, { speed: 1 });
141+
await t.dragToElement(appointment.element, targetCell2, { speed: 1 });
142+
await t.dragToElement(appointment.element, targetCell2, { speed: 1 });
143+
await t.dragToElement(appointment.element, targetCell2, { speed: 1 });
144+
145+
await t.wait(6000);
146+
147+
const positionAfterPromiseResolved = await appointment.element.boundingClientRect;
148+
const cell1Position = await targetCell1.boundingClientRect;
149+
150+
await t
151+
.expect(positionAfterPromiseResolved.left)
152+
.notEql(initialPosition.left)
153+
.expect(positionAfterPromiseResolved.left)
154+
.eql(cell1Position.left);
155+
}).before(async () => {
156+
await createWidget('dxScheduler', {
157+
dataSource: [{
158+
text: 'Test Appointment',
159+
startDate: new Date(2023, 0, 2, 10, 0),
160+
endDate: new Date(2023, 0, 2, 11, 0),
161+
}],
162+
views: ['week'],
163+
currentView: 'week',
164+
currentDate: new Date(2023, 0, 2),
165+
height: 600,
166+
onAppointmentUpdating: (e) => {
167+
e.cancel = new Promise((resolve) => {
168+
setTimeout(() => {
169+
resolve(false);
170+
}, 5000);
171+
});
172+
},
173+
});
174+
});

packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ export default class AppointmentDragBehavior {
9595
e.itemData = this.getItemData(e.itemElement);
9696
e.itemSettings = this.getItemSettings(e.itemElement);
9797

98+
if (this.scheduler._isAppointmentBeingUpdated(e.itemData)) {
99+
e.cancel = true;
100+
return;
101+
}
102+
98103
appointmentDragging.onDragStart?.(e);
99104

100105
if (!e.cancel) {
@@ -105,6 +110,16 @@ export default class AppointmentDragBehavior {
105110

106111
createDragMoveHandler(options, appointmentDragging) {
107112
return (e) => {
113+
if (!this.appointmentInfo) {
114+
e.cancel = true;
115+
return;
116+
}
117+
118+
if (this.scheduler._isAppointmentBeingUpdated(this.appointmentInfo.appointment)) {
119+
e.cancel = true;
120+
return;
121+
}
122+
108123
appointmentDragging.onDragMove?.(e);
109124

110125
if (!e.cancel) {
@@ -115,6 +130,11 @@ export default class AppointmentDragBehavior {
115130

116131
createDragEndHandler(options, appointmentDragging) {
117132
return (e) => {
133+
if (!this.appointmentInfo) {
134+
e.cancel = true;
135+
return;
136+
}
137+
118138
const updatedData = this.appointments.invoke('getUpdatedData', e.itemData);
119139

120140
this.appointmentInfo = null;

packages/devextreme/js/__internal/scheduler/m_scheduler.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ class Scheduler extends Widget<any> {
205205

206206
_asyncTemplatesTimers!: any[];
207207

208+
_updatingAppointments: Set<Appointment> = new Set();
209+
208210
_dataSourceLoadedCallback: any;
209211

210212
_subscribes: any;
@@ -2147,6 +2149,10 @@ class Scheduler extends Widget<any> {
21472149
dragEvent.cancel = new Deferred();
21482150
}
21492151

2152+
if (isPromise(updatingOptions.cancel) && dragEvent) {
2153+
this._updatingAppointments.add(target);
2154+
}
2155+
21502156
return this._processActionResult(updatingOptions, function (canceled) {
21512157
// @ts-expect-error
21522158
let deferred = new Deferred();
@@ -2160,14 +2166,19 @@ class Scheduler extends Widget<any> {
21602166
.done(() => {
21612167
dragEvent && dragEvent.cancel.resolve(false);
21622168
})
2163-
.always((storeAppointment) => this._onDataPromiseCompleted(StoreEventNames.UPDATED, storeAppointment))
2169+
.always((storeAppointment) => {
2170+
this._updatingAppointments.delete(target);
2171+
this._onDataPromiseCompleted(StoreEventNames.UPDATED, storeAppointment);
2172+
})
21642173
.fail(() => performFailAction());
21652174
} catch (err) {
21662175
performFailAction(err);
2176+
this._updatingAppointments.delete(target);
21672177
deferred.resolve();
21682178
}
21692179
} else {
21702180
performFailAction();
2181+
this._updatingAppointments.delete(target);
21712182
deferred.resolve();
21722183
}
21732184

@@ -2616,6 +2627,10 @@ class Scheduler extends Widget<any> {
26162627
return this._workSpace.dragBehavior;
26172628
}
26182629

2630+
_isAppointmentBeingUpdated(appointmentData: Appointment): boolean {
2631+
return this._updatingAppointments.has(appointmentData);
2632+
}
2633+
26192634
getViewOffsetMs(): number {
26202635
const offsetFromOptions = this._getCurrentViewOption('offset');
26212636
return this.normalizeViewOffsetValue(offsetFromOptions);

0 commit comments

Comments
 (0)