@@ -4174,6 +4174,175 @@ describe("XtermHost", () => {
41744174 global . cancelAnimationFrame = originalCancelAnimationFrame ;
41754175 } ) ;
41764176
4177+ it ( "attaches the resize observer after desktop hydration is granted" , async ( ) => {
4178+ const store = createStore ( ) ;
4179+ const sendCommand = vi . fn ( ) . mockImplementation ( ( op : string ) => {
4180+ if ( op === "terminal.snapshot" ) {
4181+ return Promise . resolve ( { status : "unsupported" } ) ;
4182+ }
4183+
4184+ if ( op === "terminal.replay" ) {
4185+ return Promise . resolve ( { status : "ok" , seq : 0 } ) ;
4186+ }
4187+
4188+ return Promise . resolve ( { status : "ok" } ) ;
4189+ } ) ;
4190+ const subscribe = vi . fn ( ( ) => vi . fn ( ) ) ;
4191+ const observe = vi . fn ( ) ;
4192+ const disconnect = vi . fn ( ) ;
4193+ const originalResizeObserver = global . ResizeObserver ;
4194+ const originalRequestAnimationFrame = global . requestAnimationFrame ;
4195+ const originalCancelAnimationFrame = global . cancelAnimationFrame ;
4196+
4197+ hydrationCoordinatorMocks . autoGrant = false ;
4198+ mockTerminal . cols = 120 ;
4199+ mockTerminal . rows = 32 ;
4200+
4201+ class ResizeObserverMock {
4202+ observe = observe ;
4203+ unobserve = vi . fn ( ) ;
4204+ disconnect = disconnect ;
4205+
4206+ constructor ( _callback : ResizeObserverCallback ) { }
4207+ }
4208+
4209+ global . ResizeObserver = ResizeObserverMock as unknown as typeof ResizeObserver ;
4210+
4211+ global . requestAnimationFrame = vi . fn ( ( callback : FrameRequestCallback ) => {
4212+ callback ( 16 ) ;
4213+ return 1 ;
4214+ } ) as typeof requestAnimationFrame ;
4215+ global . cancelAnimationFrame = vi . fn ( ) as typeof cancelAnimationFrame ;
4216+
4217+ store . set ( wsClientAtom , {
4218+ sendCommand,
4219+ subscribe,
4220+ } as never ) ;
4221+
4222+ render (
4223+ < Provider store = { store } >
4224+ < XtermHost terminalId = "hydration-resize-terminal" workspaceId = "test-workspace" />
4225+ </ Provider >
4226+ ) ;
4227+
4228+ expect ( mockTerminal . open ) . not . toHaveBeenCalled ( ) ;
4229+ expect ( observe ) . not . toHaveBeenCalled ( ) ;
4230+
4231+ await act ( async ( ) => {
4232+ hydrationCoordinatorMocks . resolveGranted ( ) ;
4233+ await Promise . resolve ( ) ;
4234+ await Promise . resolve ( ) ;
4235+ await Promise . resolve ( ) ;
4236+ } ) ;
4237+
4238+ await waitFor ( ( ) => {
4239+ expect ( mockTerminal . open ) . toHaveBeenCalledTimes ( 1 ) ;
4240+ } ) ;
4241+
4242+ expect ( observe ) . toHaveBeenCalledTimes ( 1 ) ;
4243+ expect ( disconnect ) . not . toHaveBeenCalled ( ) ;
4244+
4245+ global . ResizeObserver = originalResizeObserver ;
4246+ global . requestAnimationFrame = originalRequestAnimationFrame ;
4247+ global . cancelAnimationFrame = originalCancelAnimationFrame ;
4248+ } ) ;
4249+
4250+ it ( "debounces resize-observer fits until resize settles" , async ( ) => {
4251+ const store = createStore ( ) ;
4252+ const fontsReady = new Promise < void > ( ( ) => { } ) ;
4253+ Object . defineProperty ( document , "fonts" , {
4254+ configurable : true ,
4255+ value : {
4256+ ready : fontsReady ,
4257+ } ,
4258+ } ) ;
4259+ const sendCommand = vi . fn ( ) . mockImplementation ( ( op : string ) => {
4260+ if ( op === "terminal.snapshot" ) {
4261+ return Promise . resolve ( { status : "unsupported" } ) ;
4262+ }
4263+
4264+ if ( op === "terminal.replay" ) {
4265+ return Promise . resolve ( { status : "ok" , seq : 0 } ) ;
4266+ }
4267+
4268+ return Promise . resolve ( { status : "ok" } ) ;
4269+ } ) ;
4270+ const subscribe = vi . fn ( ( ) => vi . fn ( ) ) ;
4271+ const originalResizeObserver = global . ResizeObserver ;
4272+ const originalRequestAnimationFrame = global . requestAnimationFrame ;
4273+ const originalCancelAnimationFrame = global . cancelAnimationFrame ;
4274+ let resizeObserverCallback : ResizeObserverCallback | null = null ;
4275+
4276+ hydrationCoordinatorMocks . autoGrant = false ;
4277+ mockTerminal . cols = 120 ;
4278+ mockTerminal . rows = 32 ;
4279+
4280+ class ResizeObserverMock {
4281+ observe = vi . fn ( ) ;
4282+ unobserve = vi . fn ( ) ;
4283+ disconnect = vi . fn ( ) ;
4284+
4285+ constructor ( callback : ResizeObserverCallback ) {
4286+ resizeObserverCallback = callback ;
4287+ }
4288+ }
4289+
4290+ global . ResizeObserver = ResizeObserverMock as unknown as typeof ResizeObserver ;
4291+
4292+ global . requestAnimationFrame = vi . fn ( ( callback : FrameRequestCallback ) => {
4293+ callback ( 16 ) ;
4294+ return 1 ;
4295+ } ) as typeof requestAnimationFrame ;
4296+ global . cancelAnimationFrame = vi . fn ( ) as typeof cancelAnimationFrame ;
4297+
4298+ store . set ( wsClientAtom , {
4299+ sendCommand,
4300+ subscribe,
4301+ } as never ) ;
4302+
4303+ render (
4304+ < Provider store = { store } >
4305+ < XtermHost terminalId = "debounced-resize-terminal" workspaceId = "test-workspace" />
4306+ </ Provider >
4307+ ) ;
4308+
4309+ await act ( async ( ) => {
4310+ hydrationCoordinatorMocks . resolveGranted ( ) ;
4311+ await Promise . resolve ( ) ;
4312+ await Promise . resolve ( ) ;
4313+ await Promise . resolve ( ) ;
4314+ } ) ;
4315+
4316+ expect ( resizeObserverCallback ) . toBeTypeOf ( "function" ) ;
4317+
4318+ vi . useFakeTimers ( { toFake : [ "setTimeout" , "clearTimeout" ] } ) ;
4319+
4320+ mockFitAddon . fit . mockClear ( ) ;
4321+
4322+ act ( ( ) => {
4323+ resizeObserverCallback ?.( [ ] , { } as ResizeObserver ) ;
4324+ resizeObserverCallback ?.( [ ] , { } as ResizeObserver ) ;
4325+ resizeObserverCallback ?.( [ ] , { } as ResizeObserver ) ;
4326+ } ) ;
4327+
4328+ act ( ( ) => {
4329+ vi . advanceTimersByTime ( 149 ) ;
4330+ } ) ;
4331+
4332+ expect ( mockFitAddon . fit ) . not . toHaveBeenCalled ( ) ;
4333+
4334+ act ( ( ) => {
4335+ vi . advanceTimersByTime ( 1 ) ;
4336+ } ) ;
4337+
4338+ expect ( mockFitAddon . fit ) . toHaveBeenCalledTimes ( 1 ) ;
4339+
4340+ global . ResizeObserver = originalResizeObserver ;
4341+ global . requestAnimationFrame = originalRequestAnimationFrame ;
4342+ global . cancelAnimationFrame = originalCancelAnimationFrame ;
4343+ vi . useRealTimers ( ) ;
4344+ } ) ;
4345+
41774346 it ( "waits for websocket connection before initial resize sync and snapshot recovery" , async ( ) => {
41784347 const store = createStore ( ) ;
41794348 const snapshotChunk = new TextEncoder ( ) . encode ( "snapshot after connect\n" ) ;
0 commit comments