@@ -211,6 +211,12 @@ describe("XtermHost", () => {
211211 Math . min ( mockTerminal . buffer . active . baseY , nextViewportY )
212212 ) ;
213213 } ) ;
214+ Object . defineProperty ( document , "fonts" , {
215+ configurable : true ,
216+ value : {
217+ ready : Promise . resolve ( ) ,
218+ } ,
219+ } ) ;
214220 } ) ;
215221
216222 afterEach ( ( ) => {
@@ -1411,7 +1417,8 @@ describe("XtermHost", () => {
14111417 </ Provider >
14121418 ) ;
14131419
1414- const firstOnDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ; await act ( async ( ) => {
1420+ const firstOnDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ;
1421+ await act ( async ( ) => {
14151422 await firstOnDataCallback ?.( "f" ) ;
14161423 await firstOnDataCallback ?.( "o" ) ;
14171424 await firstOnDataCallback ?.( "o" ) ;
@@ -1532,7 +1539,8 @@ describe("XtermHost", () => {
15321539 ) ;
15331540
15341541 const onDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ;
1535- expect ( onDataCallback ) . toBeTypeOf ( "function" ) ; await onDataCallback ?.( "f" ) ;
1542+ expect ( onDataCallback ) . toBeTypeOf ( "function" ) ;
1543+ await onDataCallback ?.( "f" ) ;
15361544 await onDataCallback ?.( "i" ) ;
15371545 await onDataCallback ?.( "x" ) ;
15381546 await user . click ( screen . getByRole ( "button" , { name : "Ctrl" } ) ) ;
@@ -1569,7 +1577,8 @@ describe("XtermHost", () => {
15691577 ) ;
15701578
15711579 const onDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ;
1572- expect ( onDataCallback ) . toBeTypeOf ( "function" ) ; await onDataCallback ?.( "f" ) ;
1580+ expect ( onDataCallback ) . toBeTypeOf ( "function" ) ;
1581+ await onDataCallback ?.( "f" ) ;
15731582 await onDataCallback ?.( "i" ) ;
15741583 await onDataCallback ?.( "x" ) ;
15751584 await user . click ( screen . getByRole ( "button" , { name : "Ctrl" } ) ) ;
@@ -1606,7 +1615,8 @@ describe("XtermHost", () => {
16061615 ) ;
16071616
16081617 const onDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ;
1609- expect ( onDataCallback ) . toBeTypeOf ( "function" ) ; await onDataCallback ?.( "f" ) ;
1618+ expect ( onDataCallback ) . toBeTypeOf ( "function" ) ;
1619+ await onDataCallback ?.( "f" ) ;
16101620 await onDataCallback ?.( "o" ) ;
16111621 await onDataCallback ?.( "o" ) ;
16121622 await onDataCallback ?.( " " ) ;
@@ -1646,7 +1656,8 @@ describe("XtermHost", () => {
16461656 ) ;
16471657
16481658 const onDataCallback = mockTerminal . onData . mock . calls [ 0 ] ?. [ 0 ] ;
1649- expect ( onDataCallback ) . toBeTypeOf ( "function" ) ; await onDataCallback ?.( "n" ) ;
1659+ expect ( onDataCallback ) . toBeTypeOf ( "function" ) ;
1660+ await onDataCallback ?.( "n" ) ;
16501661 await onDataCallback ?.( "p" ) ;
16511662 await onDataCallback ?.( "m" ) ;
16521663 await user . click ( screen . getByRole ( "button" , { name : "Ctrl" } ) ) ;
@@ -3378,9 +3389,9 @@ describe("XtermHost", () => {
33783389 } ) ;
33793390
33803391 await waitFor ( ( ) => {
3381- expect (
3382- mockTerminal . write . mock . calls . some ( ( [ written ] ) => written === reconnectSnapshot )
3383- ) . toBe ( true ) ;
3392+ expect ( mockTerminal . write . mock . calls . some ( ( [ written ] ) => written === reconnectSnapshot ) ) . toBe (
3393+ true
3394+ ) ;
33843395 } ) ;
33853396 expect ( snapshotCount ) . toBe ( 2 ) ;
33863397 expect ( sendCommand . mock . calls . some ( ( [ op ] ) => op === "terminal.replay" ) ) . toBe ( false ) ;
@@ -4079,6 +4090,90 @@ describe("XtermHost", () => {
40794090 global . cancelAnimationFrame = originalCancelAnimationFrame ;
40804091 } ) ;
40814092
4093+ it ( "waits for fonts to settle before the initial replay sync" , async ( ) => {
4094+ const store = createStore ( ) ;
4095+ let resolveFontsReady : ( ( ) => void ) | undefined ;
4096+ const fontsReady = new Promise < void > ( ( resolve ) => {
4097+ resolveFontsReady = resolve ;
4098+ } ) ;
4099+ Object . defineProperty ( document , "fonts" , {
4100+ configurable : true ,
4101+ value : {
4102+ ready : fontsReady ,
4103+ } ,
4104+ } ) ;
4105+
4106+ const dispatchCommand = vi . fn ( ) . mockImplementation ( ( op : string ) => {
4107+ if ( op === "terminal.replay" ) {
4108+ return Promise . resolve ( { ok : true , data : { status : "ok" , seq : 200 } } ) ;
4109+ }
4110+
4111+ return Promise . resolve ( { ok : true , data : { status : "ok" } } ) ;
4112+ } ) ;
4113+ const subscribe = vi . fn ( ( ) => vi . fn ( ) ) ;
4114+ const rafCallbacks : FrameRequestCallback [ ] = [ ] ;
4115+ const originalRequestAnimationFrame = global . requestAnimationFrame ;
4116+ const originalCancelAnimationFrame = global . cancelAnimationFrame ;
4117+
4118+ mockTerminal . cols = 132 ;
4119+ mockTerminal . rows = 36 ;
4120+
4121+ global . requestAnimationFrame = vi . fn ( ( callback : FrameRequestCallback ) => {
4122+ rafCallbacks . push ( callback ) ;
4123+ return rafCallbacks . length ;
4124+ } ) as typeof requestAnimationFrame ;
4125+ global . cancelAnimationFrame = vi . fn ( ) as typeof cancelAnimationFrame ;
4126+
4127+ store . set ( wsClientAtom , {
4128+ sendCommand : dispatchCommand ,
4129+ subscribe,
4130+ getStatus : vi . fn ( ( ) => "connected" ) ,
4131+ onStatus : vi . fn ( ( ) => ( ) => { } ) ,
4132+ } as never ) ;
4133+
4134+ render (
4135+ < Provider store = { store } >
4136+ < XtermHost terminalId = "font-ready-terminal" workspaceId = "test-workspace" />
4137+ </ Provider >
4138+ ) ;
4139+
4140+ await act ( async ( ) => {
4141+ const callback = rafCallbacks . shift ( ) ;
4142+ callback ?.( 16 ) ;
4143+ await Promise . resolve ( ) ;
4144+ await Promise . resolve ( ) ;
4145+ await Promise . resolve ( ) ;
4146+ } ) ;
4147+
4148+ expect ( dispatchCommand ) . not . toHaveBeenCalledWith ( "terminal.replay" , {
4149+ terminalId : "font-ready-terminal" ,
4150+ lastSeq : 0 ,
4151+ } ) ;
4152+ expect ( mockFitAddon . fit ) . toHaveBeenCalledTimes ( 1 ) ;
4153+
4154+ await act ( async ( ) => {
4155+ resolveFontsReady ?.( ) ;
4156+ await Promise . resolve ( ) ;
4157+ } ) ;
4158+
4159+ await act ( async ( ) => {
4160+ const callback = rafCallbacks . shift ( ) ;
4161+ callback ?.( 32 ) ;
4162+ await Promise . resolve ( ) ;
4163+ await Promise . resolve ( ) ;
4164+ await Promise . resolve ( ) ;
4165+ } ) ;
4166+
4167+ await waitFor ( ( ) => {
4168+ expect ( mockFitAddon . fit ) . toHaveBeenCalledTimes ( 2 ) ;
4169+ expectResizeCall ( dispatchCommand , "font-ready-terminal" , 132 , 36 ) ;
4170+ expectReplayCall ( dispatchCommand , "font-ready-terminal" , 0 ) ;
4171+ } ) ;
4172+
4173+ global . requestAnimationFrame = originalRequestAnimationFrame ;
4174+ global . cancelAnimationFrame = originalCancelAnimationFrame ;
4175+ } ) ;
4176+
40824177 it ( "waits for websocket connection before initial resize sync and snapshot recovery" , async ( ) => {
40834178 const store = createStore ( ) ;
40844179 const snapshotChunk = new TextEncoder ( ) . encode ( "snapshot after connect\n" ) ;
0 commit comments