@@ -2,7 +2,7 @@ import { readdirSync, readFileSync } from 'node:fs';
22import { join } from 'node:path' ;
33import { mount } from '@vue/test-utils' ;
44import { nextTick } from 'vue' ;
5- import { afterEach , describe , expect , it } from 'vitest' ;
5+ import { afterEach , describe , expect , it , vi } from 'vitest' ;
66
77import Chip from '../src/components/ui/Chip.vue' ;
88import MenuRow from '../src/components/ui/MenuRow.vue' ;
@@ -27,6 +27,7 @@ async function settle(): Promise<void> {
2727
2828afterEach ( ( ) => {
2929 document . body . replaceChildren ( ) ;
30+ vi . unstubAllGlobals ( ) ;
3031} ) ;
3132
3233describe ( 'MenuRow' , ( ) => {
@@ -149,6 +150,47 @@ describe('Popover', () => {
149150 wrapper . unmount ( ) ;
150151 } ) ;
151152
153+ it ( 're-places the panel when its content grows after opening' , async ( ) => {
154+ Object . defineProperty ( window , 'innerWidth' , { configurable : true , value : 800 } ) ;
155+ Object . defineProperty ( window , 'innerHeight' , { configurable : true , value : 800 } ) ;
156+ const observed : ( ( ) => void ) [ ] = [ ] ;
157+ vi . stubGlobal ( 'ResizeObserver' , class {
158+ constructor ( callback : ( ) => void ) {
159+ observed . push ( callback ) ;
160+ }
161+ observe ( ) : void { }
162+ disconnect ( ) : void { }
163+ } ) ;
164+ const anchor = document . createElement ( 'button' ) ;
165+ anchor . getBoundingClientRect = ( ) => ( {
166+ bottom : 200 , height : 20 , left : 100 , right : 160 , top : 180 ,
167+ width : 60 , x : 100 , y : 180 , toJSON : ( ) => ( { } ) ,
168+ } ) ;
169+ document . body . append ( anchor ) ;
170+ const wrapper = mount ( Popover , {
171+ attachTo : document . body ,
172+ props : { anchor, open : true } ,
173+ slots : { default : 'Menu' } ,
174+ } ) ;
175+ const panel = document . body . querySelector ( '[role="dialog"]' ) as HTMLElement ;
176+ Object . defineProperty ( panel , 'offsetWidth' , { configurable : true , value : 100 } ) ;
177+ Object . defineProperty ( panel , 'offsetHeight' , { configurable : true , writable : true , value : 80 } ) ;
178+
179+ await settle ( ) ;
180+ expect ( panel . style . top ) . toBe ( '204px' ) ;
181+
182+ // The menu opens on a spinner and fills in afterwards. Without the resize
183+ // observer the panel keeps the `top` it was measured at and its new bottom
184+ // runs off the viewport.
185+ Object . defineProperty ( panel , 'offsetHeight' , { configurable : true , value : 700 } ) ;
186+ expect ( observed ) . toHaveLength ( 1 ) ;
187+ observed [ 0 ] ! ( ) ;
188+ await settle ( ) ;
189+
190+ expect ( panel . style . top ) . toBe ( '16px' ) ;
191+ wrapper . unmount ( ) ;
192+ } ) ;
193+
152194 it ( 'closes on Escape' , async ( ) => {
153195 const anchor = document . createElement ( 'button' ) ;
154196 document . body . append ( anchor ) ;
0 commit comments