@@ -497,4 +497,138 @@ describe('tooltip close and delay behavior', () => {
497497 const tooltip = document . getElementById ( 'deferred-anchor-test' )
498498 expect ( tooltip ) . toBeInTheDocument ( )
499499 } )
500+
501+ test ( 'hides tooltip when quickly moving between anchors and then away with delayShow' , ( ) => {
502+ render (
503+ < >
504+ < span data-tooltip-id = "stuck-tooltip-test" > Anchor A</ span >
505+ < span data-tooltip-id = "stuck-tooltip-test" > Anchor B</ span >
506+ < TooltipController id = "stuck-tooltip-test" content = "Stuck Tooltip Test" delayShow = { 200 } />
507+ </ > ,
508+ )
509+
510+ const anchorA = screen . getByText ( 'Anchor A' )
511+ const anchorB = screen . getByText ( 'Anchor B' )
512+
513+ // Hover anchor A, then quickly move to anchor B before delayShow fires
514+ hoverAnchor ( anchorA )
515+ advanceTimers ( 50 )
516+ unhoverAnchor ( anchorA )
517+ hoverAnchor ( anchorB )
518+ advanceTimers ( 50 )
519+
520+ // Move away from anchor B before the deferred delayShow fires
521+ unhoverAnchor ( anchorB )
522+
523+ // Advance past the delayShow — tooltip should NOT appear
524+ advanceTimers ( 300 )
525+
526+ expect ( document . getElementById ( 'stuck-tooltip-test' ) ) . not . toBeInTheDocument ( )
527+ } )
528+
529+ test ( 'switches data-tooltip-content when moving between anchors with delayShow' , async ( ) => {
530+ render (
531+ < >
532+ < h1 data-tooltip-id = "content-switch-test" data-tooltip-content = "first item" >
533+ First heading
534+ </ h1 >
535+ < h2 data-tooltip-id = "content-switch-test" data-tooltip-content = "second item" >
536+ Second heading
537+ </ h2 >
538+ < TooltipController id = "content-switch-test" place = "bottom" delayShow = { 200 } />
539+ </ > ,
540+ )
541+
542+ const h1 = screen . getByText ( 'First heading' )
543+ const h2 = screen . getByText ( 'Second heading' )
544+
545+ // Hover h1 and wait for tooltip to show
546+ hoverAnchor ( h1 , 250 )
547+ await waitForTooltip ( 'content-switch-test' )
548+
549+ const tooltip = document . getElementById ( 'content-switch-test' )
550+ expect ( tooltip . textContent ) . toBe ( 'first item' )
551+
552+ // Move from h1 to h2
553+ unhoverAnchor ( h1 )
554+ hoverAnchor ( h2 )
555+
556+ // During delay, content should still reflect h1
557+ advanceTimers ( 50 )
558+ expect ( tooltip . textContent ) . toBe ( 'first item' )
559+
560+ // After delay, content should switch to h2
561+ advanceTimers ( 200 )
562+ expect ( tooltip . textContent ) . toBe ( 'second item' )
563+ } )
564+
565+ test ( 'switches content when quickly moving between anchors before first delayShow fires' , ( ) => {
566+ render (
567+ < >
568+ < span data-tooltip-id = "quick-switch-test" data-tooltip-content = "first item" >
569+ Anchor A
570+ </ span >
571+ < span data-tooltip-id = "quick-switch-test" data-tooltip-content = "second item" >
572+ Anchor B
573+ </ span >
574+ < TooltipController id = "quick-switch-test" delayShow = { 200 } />
575+ </ > ,
576+ )
577+
578+ const anchorA = screen . getByText ( 'Anchor A' )
579+ const anchorB = screen . getByText ( 'Anchor B' )
580+
581+ // Hover A briefly, then move to B before delayShow fires
582+ hoverAnchor ( anchorA )
583+ advanceTimers ( 50 )
584+ unhoverAnchor ( anchorA )
585+ hoverAnchor ( anchorB )
586+
587+ // Advance past the deferred delayShow
588+ advanceTimers ( 250 )
589+
590+ const tooltip = document . getElementById ( 'quick-switch-test' )
591+ expect ( tooltip ) . toBeInTheDocument ( )
592+ expect ( tooltip . textContent ) . toBe ( 'second item' )
593+ } )
594+
595+ test ( 'deferred anchor switch survives when closing transition completes before delay fires' , async ( ) => {
596+ render (
597+ < >
598+ < span data-tooltip-id = "transition-race-test" data-tooltip-content = "first item" >
599+ Anchor A
600+ </ span >
601+ < span data-tooltip-id = "transition-race-test" data-tooltip-content = "second item" >
602+ Anchor B
603+ </ span >
604+ < TooltipController id = "transition-race-test" delayShow = { 200 } />
605+ </ > ,
606+ )
607+
608+ const anchorA = screen . getByText ( 'Anchor A' )
609+ const anchorB = screen . getByText ( 'Anchor B' )
610+
611+ // Show tooltip at A
612+ hoverAnchor ( anchorA , 250 )
613+ await waitForTooltip ( 'transition-race-test' )
614+ const tooltip = document . getElementById ( 'transition-race-test' )
615+ expect ( tooltip . textContent ) . toBe ( 'first item' )
616+
617+ // Move from A to B — triggers deferred anchor switch
618+ unhoverAnchor ( anchorA )
619+ hoverAnchor ( anchorB )
620+
621+ // Simulate the closing transition completing before the deferred timer fires.
622+ // In a real browser, the opacity transition ends after ~150ms (before the 200ms delay).
623+ advanceTimers ( 25 )
624+ const transitionEndEvent = new Event ( 'transitionend' , { bubbles : true } )
625+ Object . defineProperty ( transitionEndEvent , 'propertyName' , { value : 'opacity' } )
626+ fireEvent ( tooltip , transitionEndEvent )
627+
628+ // After the deferred delay completes, content should switch to anchor B
629+ advanceTimers ( 250 )
630+ const updatedTooltip = document . getElementById ( 'transition-race-test' )
631+ expect ( updatedTooltip ) . toBeInTheDocument ( )
632+ expect ( updatedTooltip . textContent ) . toBe ( 'second item' )
633+ } )
500634} )
0 commit comments