@@ -327,6 +327,43 @@ describe('useDragDrop spring-open revert', () => {
327327 expect ( expandedFolders . has ( 'folder-1' ) ) . toBe ( true )
328328 } )
329329
330+ /**
331+ * The spring-open timer is armed for 400ms, so a drag ending just before it fires leaves it
332+ * pending. Relying on the effect cleanup to cancel it would let it land after the drag-end
333+ * collapse had already emptied the set — re-adding the folder for the *next* drag to close, by
334+ * which point the user had opened it themselves.
335+ */
336+ it ( 'does not spring-open a folder when the drag ends before the timer fires' , ( ) => {
337+ act ( ( ) => {
338+ latest . handleDragStart ( null )
339+ } )
340+ act ( ( ) => {
341+ latest
342+ . createFolderDragHandlers ( 'folder-1' , null )
343+ . onDragOver ( fakeFolderDragOverEvent ( ) as never )
344+ } )
345+
346+ /**
347+ * Deliberately outside `act`: the race only exists while React has scheduled the drag-end state
348+ * changes but not yet committed them, so the effect cleanup has not run and the timer is still
349+ * armed. Wrapping this in `act` would flush the commit first and cancel the timer via the
350+ * cleanup, hiding the very gap under test.
351+ */
352+ latest . handleDragEnd ( )
353+ act ( ( ) => {
354+ vi . advanceTimersByTime ( 500 )
355+ } )
356+
357+ expect ( mockSetExpanded ) . not . toHaveBeenCalledWith ( 'folder-1' , true )
358+ expect ( expandedFolders . has ( 'folder-1' ) ) . toBe ( false )
359+
360+ // Nothing was left behind for a later drag to collapse.
361+ act ( ( ) => {
362+ latest . handleDragEnd ( )
363+ } )
364+ expect ( mockSetExpanded ) . not . toHaveBeenCalledWith ( 'folder-1' , false )
365+ } )
366+
330367 it ( 'never closes a folder the user had already opened themselves' , ( ) => {
331368 expandedFolders . add ( 'folder-1' )
332369
0 commit comments