@@ -419,47 +419,120 @@ describe('droppable', () => {
419419 } ) ;
420420 } ) ;
421421
422- describe ( 'Pointer events for touch/mouse fallback' , ( ) => {
423- it ( 'should call onDragEnter on pointerover when dragging' , ( ) => {
422+ describe ( 'Pointer events for touch/mouse fallback (document pointermove)' , ( ) => {
423+ beforeEach ( ( ) => {
424+ // Give the node a known bounding rect: x:10, y:10 → x:110, y:110
425+ vi . spyOn ( node , 'getBoundingClientRect' ) . mockReturnValue ( {
426+ left : 10 , top : 10 , right : 110 , bottom : 110 ,
427+ width : 100 , height : 100 , x : 10 , y : 10 ,
428+ toJSON : ( ) => ( { } )
429+ } ) ;
430+ } ) ;
431+
432+ function dispatchDocumentPointerMove ( clientX : number , clientY : number ) {
433+ const event = new PointerEvent ( 'pointermove' , { bubbles : false , cancelable : false } ) ;
434+ Object . defineProperty ( event , 'clientX' , { value : clientX } ) ;
435+ Object . defineProperty ( event , 'clientY' , { value : clientY } ) ;
436+ document . dispatchEvent ( event ) ;
437+ }
438+
439+ it ( 'should set targetContainer and add drag-over class when pointer enters bounds' , ( ) => {
440+ const action = droppable ( node , {
441+ container : 'test' ,
442+ attributes : { dragOverClass : 'drag-over' }
443+ } ) ;
444+
445+ dndState . isDragging = true ;
446+ dispatchDocumentPointerMove ( 60 , 60 ) ; // inside 10-110 bounds
447+
448+ expect ( dndState . targetContainer ) . toBe ( 'test' ) ;
449+ expect ( node . classList . contains ( 'drag-over' ) ) . toBe ( true ) ;
450+
451+ action . destroy ( ) ;
452+ } ) ;
453+
454+ it ( 'should call onDragEnter only once when pointer enters bounds' , ( ) => {
424455 const onDragEnter = vi . fn ( ) ;
425456 const action = droppable ( node , {
426457 container : 'test' ,
427458 callbacks : { onDragEnter }
428459 } ) ;
429460
430461 dndState . isDragging = true ;
431- node . dispatchEvent ( new PointerEvent ( 'pointerover' , { bubbles : true } ) ) ;
462+ dispatchDocumentPointerMove ( 60 , 60 ) ; // enter
463+ dispatchDocumentPointerMove ( 70 , 70 ) ; // still inside — should not fire again
464+
465+ expect ( onDragEnter ) . toHaveBeenCalledTimes ( 1 ) ;
432466
433- expect ( onDragEnter ) . toHaveBeenCalled ( ) ;
434467 action . destroy ( ) ;
435468 } ) ;
436469
437- it ( 'should not call onDragEnter on pointerover when not dragging ' , ( ) => {
470+ it ( 'should not call onDragEnter when pointer is outside bounds ' , ( ) => {
438471 const onDragEnter = vi . fn ( ) ;
439472 const action = droppable ( node , {
440473 container : 'test' ,
441474 callbacks : { onDragEnter }
442475 } ) ;
443476
477+ dndState . isDragging = true ;
478+ dispatchDocumentPointerMove ( 5 , 5 ) ; // outside bounds (left: 10)
479+
480+ expect ( onDragEnter ) . not . toHaveBeenCalled ( ) ;
481+
482+ action . destroy ( ) ;
483+ } ) ;
484+
485+ it ( 'should call onDragLeave and clear targetContainer when pointer leaves bounds' , ( ) => {
486+ const onDragLeave = vi . fn ( ) ;
487+ const action = droppable ( node , {
488+ container : 'test' ,
489+ callbacks : { onDragLeave }
490+ } ) ;
491+
492+ dndState . isDragging = true ;
493+ dispatchDocumentPointerMove ( 60 , 60 ) ; // enter
494+ dispatchDocumentPointerMove ( 5 , 5 ) ; // leave
495+
496+ expect ( onDragLeave ) . toHaveBeenCalledTimes ( 1 ) ;
497+ expect ( dndState . targetContainer ) . toBeNull ( ) ;
498+ expect ( node . classList . contains ( 'drag-over' ) ) . toBe ( false ) ;
499+
500+ action . destroy ( ) ;
501+ } ) ;
502+
503+ it ( 'should not fire any events when not dragging' , ( ) => {
504+ const onDragEnter = vi . fn ( ) ;
505+ const onDragLeave = vi . fn ( ) ;
506+ const action = droppable ( node , {
507+ container : 'test' ,
508+ callbacks : { onDragEnter, onDragLeave }
509+ } ) ;
510+
444511 dndState . isDragging = false ;
445- node . dispatchEvent ( new PointerEvent ( 'pointerover' , { bubbles : true } ) ) ;
512+ dispatchDocumentPointerMove ( 60 , 60 ) ;
446513
447514 expect ( onDragEnter ) . not . toHaveBeenCalled ( ) ;
515+ expect ( onDragLeave ) . not . toHaveBeenCalled ( ) ;
516+
448517 action . destroy ( ) ;
449518 } ) ;
450519
451- it ( 'should call onDragLeave on pointerout when dragging ' , ( ) => {
520+ it ( 'should not call onDragLeave when leaving a different container ' , ( ) => {
452521 const onDragLeave = vi . fn ( ) ;
453522 const action = droppable ( node , {
454523 container : 'test' ,
455524 callbacks : { onDragLeave }
456525 } ) ;
457526
458527 dndState . isDragging = true ;
459- dndState . targetContainer = 'test' ;
460- node . dispatchEvent ( new PointerEvent ( 'pointerout' , { bubbles : true } ) ) ;
528+ dndState . targetContainer = 'other-container' ; // something else is active
529+ dispatchDocumentPointerMove ( 60 , 60 ) ; // enter this node
530+ dndState . targetContainer = 'other-container' ; // simulate another container taking over
531+ dispatchDocumentPointerMove ( 5 , 5 ) ; // leave this node bounds
532+
533+ // onDragLeave should not fire because targetContainer !== 'test'
534+ expect ( onDragLeave ) . not . toHaveBeenCalled ( ) ;
461535
462- expect ( onDragLeave ) . toHaveBeenCalled ( ) ;
463536 action . destroy ( ) ;
464537 } ) ;
465538 } ) ;
0 commit comments