Skip to content

Commit e318ba2

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 (#31641)
1 parent 3e581c6 commit e318ba2

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
@@ -96,6 +96,11 @@ export default class AppointmentDragBehavior {
9696
e.itemData = this.getItemData(e.itemElement);
9797
e.itemSettings = this.getItemSettings(e.itemElement);
9898

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

101106
if (!e.cancel) {
@@ -106,6 +111,16 @@ export default class AppointmentDragBehavior {
106111

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

111126
if (!e.cancel) {
@@ -116,6 +131,11 @@ export default class AppointmentDragBehavior {
116131

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

121141
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
@@ -197,6 +197,8 @@ class Scheduler extends SchedulerOptionsBaseWidget {
197197

198198
_asyncTemplatesTimers!: any[];
199199

200+
_updatingAppointments: Set<Appointment> = new Set();
201+
200202
_dataSourceLoadedCallback: any;
201203

202204
_subscribes: any;
@@ -1734,6 +1736,10 @@ class Scheduler extends SchedulerOptionsBaseWidget {
17341736
dragEvent.cancel = new Deferred();
17351737
}
17361738

1739+
if (isPromise(updatingOptions.cancel) && dragEvent) {
1740+
this._updatingAppointments.add(target);
1741+
}
1742+
17371743
return this._processActionResult(updatingOptions, function (canceled) {
17381744
// @ts-expect-error
17391745
let deferred = new Deferred();
@@ -1747,14 +1753,19 @@ class Scheduler extends SchedulerOptionsBaseWidget {
17471753
.done(() => {
17481754
dragEvent?.cancel.resolve(false);
17491755
})
1750-
.always((storeAppointment) => this._onDataPromiseCompleted(StoreEventNames.UPDATED, storeAppointment))
1756+
.always((storeAppointment) => {
1757+
this._updatingAppointments.delete(target);
1758+
this._onDataPromiseCompleted(StoreEventNames.UPDATED, storeAppointment);
1759+
})
17511760
.fail(() => performFailAction());
17521761
} catch (err) {
17531762
performFailAction(err);
1763+
this._updatingAppointments.delete(target);
17541764
deferred.resolve();
17551765
}
17561766
} else {
17571767
performFailAction();
1768+
this._updatingAppointments.delete(target);
17581769
deferred.resolve();
17591770
}
17601771

@@ -2130,6 +2141,10 @@ class Scheduler extends SchedulerOptionsBaseWidget {
21302141
return this._workSpace.dragBehavior;
21312142
}
21322143

2144+
_isAppointmentBeingUpdated(appointmentData: Appointment): boolean {
2145+
return this._updatingAppointments.has(appointmentData);
2146+
}
2147+
21332148
getViewOffsetMs(): number {
21342149
const offsetFromOptions = this.getViewOption('offset');
21352150
return this.normalizeViewOffsetValue(offsetFromOptions);

0 commit comments

Comments
 (0)