@@ -573,4 +573,128 @@ describe('getAnchoredPosition', () => {
573573 expect ( result . left ) . toEqual ( 290 ) // anchorRect.left - parentRect.left
574574 } )
575575 } )
576+
577+ describe ( 'displayInViewport option' , ( ) => {
578+ beforeEach ( ( ) => {
579+ // Mock window dimensions for consistent testing
580+ Object . defineProperty ( window , 'innerWidth' , { value : 1024 , writable : true } )
581+ Object . defineProperty ( window , 'innerHeight' , { value : 768 , writable : true } )
582+ } )
583+
584+ it ( 'defaults to false when not specified' , ( ) => {
585+ const anchorRect = makeDOMRect ( 300 , 200 , 50 , 50 )
586+ const floatingRect = makeDOMRect ( NaN , NaN , 100 , 100 )
587+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 20 , 20 , 1920 , 1080 ) , anchorRect , floatingRect )
588+
589+ const { top, left} = getAnchoredPosition ( float , anchor , { side : 'outside-bottom' } )
590+
591+ // Should use normal clipping behavior (relative to clipping parent)
592+ expect ( top ) . toEqual ( 234 ) // anchorRect.top + anchorRect.height + 4 - parentRect.top
593+ expect ( left ) . toEqual ( 280 ) // anchorRect.left - parentRect.left
594+ } )
595+
596+ it ( 'constrains to visible viewport when set to true' , ( ) => {
597+ const anchorRect = makeDOMRect ( 950 , 700 , 50 , 50 ) // Near bottom-right of viewport
598+ const floatingRect = makeDOMRect ( NaN , NaN , 200 , 200 ) // Large floating element
599+ const { float, anchor} = createVirtualDOM (
600+ makeDOMRect ( 0 , 0 , 2000 , 2000 ) , // Large container
601+ anchorRect ,
602+ floatingRect ,
603+ )
604+
605+ const { top, left} = getAnchoredPosition ( float , anchor , {
606+ side : 'outside-bottom' ,
607+ displayInViewport : true ,
608+ } )
609+
610+ // Should be constrained to fit within viewport (1024x768)
611+ expect ( left ) . toBeLessThanOrEqual ( 1024 - 200 ) // left + width <= viewport width
612+ expect ( top + 200 ) . toBeLessThanOrEqual ( 768 ) // top + height <= viewport height
613+ } )
614+
615+ it ( 'flips to top when bottom would exceed viewport height' , ( ) => {
616+ const anchorRect = makeDOMRect ( 400 , 700 , 50 , 50 ) // Near bottom of viewport
617+ const floatingRect = makeDOMRect ( NaN , NaN , 100 , 100 )
618+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 0 , 0 , 1920 , 1080 ) , anchorRect , floatingRect )
619+
620+ const { top, anchorSide} = getAnchoredPosition ( float , anchor , {
621+ side : 'outside-bottom' ,
622+ displayInViewport : true ,
623+ } )
624+
625+ // Should flip to top side to fit in viewport
626+ expect ( anchorSide ) . toEqual ( 'outside-top' )
627+ expect ( top ) . toBeGreaterThanOrEqual ( 0 )
628+ expect ( top + 100 ) . toBeLessThanOrEqual ( 768 )
629+ } )
630+
631+ it ( 'adjusts left position when right edge would exceed viewport width' , ( ) => {
632+ const anchorRect = makeDOMRect ( 950 , 300 , 50 , 50 ) // Near right edge of viewport
633+ const floatingRect = makeDOMRect ( NaN , NaN , 150 , 100 )
634+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 0 , 0 , 1920 , 1080 ) , anchorRect , floatingRect )
635+
636+ const { left} = getAnchoredPosition ( float , anchor , {
637+ side : 'outside-bottom' ,
638+ align : 'start' ,
639+ displayInViewport : true ,
640+ } )
641+
642+ // Should be pushed left to fit in viewport
643+ expect ( left + 150 ) . toBeLessThanOrEqual ( 1024 ) // left + width <= viewport width
644+ } )
645+
646+ it ( 'works with inside positioning' , ( ) => {
647+ const anchorRect = makeDOMRect ( 100 , 100 , 800 , 600 ) // Large anchor covering most viewport
648+ const floatingRect = makeDOMRect ( NaN , NaN , 200 , 100 )
649+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 0 , 0 , 1920 , 1080 ) , anchorRect , floatingRect )
650+
651+ const { top, left} = getAnchoredPosition ( float , anchor , {
652+ side : 'inside-center' ,
653+ align : 'center' ,
654+ displayInViewport : true ,
655+ } )
656+
657+ // Should position inside the anchor and within viewport
658+ expect ( left ) . toBeGreaterThanOrEqual ( 0 )
659+ expect ( top ) . toBeGreaterThanOrEqual ( 0 )
660+ expect ( left + 200 ) . toBeLessThanOrEqual ( 1024 )
661+ expect ( top + 100 ) . toBeLessThanOrEqual ( 768 )
662+ } )
663+
664+ it ( 'still respects allowOutOfBounds when both options are set' , ( ) => {
665+ const anchorRect = makeDOMRect ( 950 , 700 , 50 , 50 )
666+ const floatingRect = makeDOMRect ( NaN , NaN , 200 , 200 )
667+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 0 , 0 , 1920 , 1080 ) , anchorRect , floatingRect )
668+
669+ const { top, left} = getAnchoredPosition ( float , anchor , {
670+ side : 'outside-bottom' ,
671+ displayInViewport : true ,
672+ allowOutOfBounds : true ,
673+ } )
674+
675+ // When allowOutOfBounds is true, should allow overflow even with displayInViewport
676+ // The exact behavior depends on implementation, but it should not throw an error
677+ expect ( typeof top ) . toBe ( 'number' )
678+ expect ( typeof left ) . toBe ( 'number' )
679+ } )
680+
681+ it ( 'handles edge case when anchor is outside viewport' , ( ) => {
682+ const anchorRect = makeDOMRect ( 1200 , 900 , 50 , 50 ) // Outside 1024x768 viewport
683+ const floatingRect = makeDOMRect ( NaN , NaN , 100 , 100 )
684+ const { float, anchor} = createVirtualDOM ( makeDOMRect ( 0 , 0 , 2000 , 2000 ) , anchorRect , floatingRect )
685+
686+ const { top, left} = getAnchoredPosition ( float , anchor , {
687+ side : 'outside-bottom' ,
688+ displayInViewport : true ,
689+ } )
690+
691+ // When anchor is outside viewport, the position calculation should still work
692+ // but the final nudging should ensure no part of the floating element exceeds viewport
693+ // Since the anchor is at (1200, 900) with 'outside-bottom', the floating element
694+ // would initially be positioned around (1200, 954), which exceeds viewport bounds
695+ // The displayInViewport option should constrain this
696+ expect ( left + 100 ) . toBeLessThanOrEqual ( 1024 ) // right edge within viewport
697+ expect ( top + 100 ) . toBeLessThanOrEqual ( 768 ) // bottom edge within viewport
698+ } )
699+ } )
576700} )
0 commit comments