@@ -89,28 +89,47 @@ test('Should set correct start and end dates in drag&dropped appointment', async
8989 const firstScheduler = new Scheduler ( `#${ FIRST_SCHEDULER_SELECTOR } ` ) ;
9090 const secondScheduler = new Scheduler ( `#${ SECOND_SCHEDULER_SELECTOR } ` ) ;
9191
92- const appointmentToMoveElement = firstScheduler
93- . getAppointment ( TEST_APPOINTMENT . text )
94- . element ( ) ;
95- const cellToMoveElement = secondScheduler
96- . getDateTableCell ( 0 , 0 ) ;
92+ const cellToMoveElement = secondScheduler . getDateTableCell ( 0 , 0 ) ;
9793
9894 // The first scheduler uses an async data source, so make sure its appointment is
99- // actually rendered (and the layout has settled) before the drag starts. Otherwise
100- // dragToElement may capture a stale position and the drop never reaches the target.
95+ // rendered before dragging — otherwise dragToElement may grab a stale position.
10196 await t . expect ( firstScheduler . getAppointmentCount ( ) ) . eql ( 1 ) ;
10297
103- await t . dragToElement ( appointmentToMoveElement , cellToMoveElement , { speed : 0.5 } ) ;
98+ // Dropping an appointment onto another scheduler occasionally fails to register on a
99+ // loaded CI machine: the drop over the target cell is missed, the async onAdd handler
100+ // never runs, and the appointment snaps back to the source. Retry the drag until the
101+ // appointment actually appears in the target scheduler. We decide whether to retry via
102+ // `.exists` (which waits out the scheduler's async appointment rendering) rather than
103+ // an immediate count read, so a slow-but-successful drop is not mistaken for a miss and
104+ // re-dragged into a duplicate. The source selector is resolved fresh each attempt
105+ // because a missed drop re-renders the source node.
106+ const MAX_DRAG_ATTEMPTS = 3 ;
107+ const DROP_RENDER_TIMEOUT = 3000 ;
108+
109+ for ( let attempt = 0 ; attempt < MAX_DRAG_ATTEMPTS ; attempt += 1 ) {
110+ await t . dragToElement (
111+ firstScheduler . getAppointment ( TEST_APPOINTMENT . text ) . element ,
112+ cellToMoveElement ,
113+ { speed : 0.5 } ,
114+ ) ;
115+
116+ const isTransferred = await secondScheduler
117+ . getAppointment ( TEST_APPOINTMENT . text )
118+ . element
119+ . with ( { timeout : DROP_RENDER_TIMEOUT } )
120+ . exists ;
121+
122+ if ( isTransferred ) {
123+ break ;
124+ }
125+ }
104126
105- // The drop runs async onRemove/onAdd handlers. Wait for the appointment to actually
106- // appear in the target scheduler via an auto-retrying assertion instead of a fixed
107- // delay, then read its time the same way so the whole chain re-evaluates while the
108- // DOM updates.
109- await t . expect ( secondScheduler . getAppointmentCount ( ) ) . eql ( 1 , { timeout : 3000 } ) ;
127+ // Final verification: the appointment is in the target scheduler with the expected time.
128+ await t . expect ( secondScheduler . getAppointmentCount ( ) ) . eql ( 1 , { timeout : DROP_RENDER_TIMEOUT } ) ;
110129
111130 await t
112131 . expect ( secondScheduler . getAppointment ( TEST_APPOINTMENT . text ) . date . time )
113- . eql ( EXPECTED_APPOINTMENT_TIME , { timeout : 3000 } ) ;
132+ . eql ( EXPECTED_APPOINTMENT_TIME , { timeout : DROP_RENDER_TIMEOUT } ) ;
114133} ) . before ( async ( ) => {
115134 await setStyleAttribute ( Selector ( '#container' ) , 'display: flex;' ) ;
116135 await appendElementTo ( '#container' , 'div' , FIRST_SCHEDULER_SELECTOR ) ;
0 commit comments