@@ -161,3 +161,167 @@ describe('FitAddon', () => {
161161 expect ( resizeCallCount ) . toBe ( 0 ) ; // Still 0 because no element
162162 } ) ;
163163} ) ;
164+
165+ // ==========================================================================
166+ // onReady Auto-Retry Tests
167+ // ==========================================================================
168+
169+ describe ( 'onReady Auto-Retry' , ( ) => {
170+ let addon : FitAddon ;
171+
172+ beforeEach ( ( ) => {
173+ addon = new FitAddon ( ) ;
174+ } ) ;
175+
176+ afterEach ( ( ) => {
177+ addon . dispose ( ) ;
178+ } ) ;
179+
180+ test ( 'subscribes to onReady during activation' , ( ) => {
181+ let subscribed = false ;
182+
183+ const mockTerminal = {
184+ cols : 80 ,
185+ rows : 24 ,
186+ onReady : ( listener : ( ) => void ) => {
187+ subscribed = true ;
188+ return { dispose : ( ) => { } } ;
189+ } ,
190+ } ;
191+
192+ addon . activate ( mockTerminal as any ) ;
193+ expect ( subscribed ) . toBe ( true ) ;
194+ } ) ;
195+
196+ test ( 'calls fit() when onReady fires' , ( ) => {
197+ let readyCallback : ( ( ) => void ) | null = null ;
198+ let fitCallCount = 0 ;
199+
200+ // Create a mock element with computed dimensions
201+ const mockElement = document . createElement ( 'div' ) ;
202+ Object . defineProperty ( mockElement , 'clientWidth' , { value : 800 , configurable : true } ) ;
203+ Object . defineProperty ( mockElement , 'clientHeight' , { value : 400 , configurable : true } ) ;
204+
205+ const mockTerminal = {
206+ cols : 80 ,
207+ rows : 24 ,
208+ element : mockElement ,
209+ renderer : {
210+ getMetrics : ( ) => ( { width : 9 , height : 16 , baseline : 12 } ) ,
211+ } ,
212+ resize : ( cols : number , rows : number ) => {
213+ fitCallCount ++ ;
214+ mockTerminal . cols = cols ;
215+ mockTerminal . rows = rows ;
216+ } ,
217+ onReady : ( listener : ( ) => void ) => {
218+ readyCallback = listener ;
219+ return { dispose : ( ) => { } } ;
220+ } ,
221+ } ;
222+
223+ addon . activate ( mockTerminal as any ) ;
224+
225+ // Before ready, fit() may not resize (depending on implementation)
226+ const initialFitCount = fitCallCount ;
227+
228+ // Simulate terminal becoming ready
229+ if ( readyCallback ) {
230+ readyCallback ( ) ;
231+ }
232+
233+ // fit() should have been called via onReady handler
234+ expect ( fitCallCount ) . toBeGreaterThan ( initialFitCount ) ;
235+ } ) ;
236+
237+ test ( 'disposes onReady subscription on dispose()' , ( ) => {
238+ let disposed = false ;
239+
240+ const mockTerminal = {
241+ cols : 80 ,
242+ rows : 24 ,
243+ onReady : ( listener : ( ) => void ) => {
244+ return {
245+ dispose : ( ) => {
246+ disposed = true ;
247+ } ,
248+ } ;
249+ } ,
250+ } ;
251+
252+ addon . activate ( mockTerminal as any ) ;
253+ expect ( disposed ) . toBe ( false ) ;
254+
255+ addon . dispose ( ) ;
256+ expect ( disposed ) . toBe ( true ) ;
257+ } ) ;
258+
259+ test ( 'handles terminal without onReady gracefully' , ( ) => {
260+ const terminalWithoutReady = {
261+ cols : 80 ,
262+ rows : 24 ,
263+ resize : ( ) => { } ,
264+ } ;
265+
266+ expect ( ( ) => addon . activate ( terminalWithoutReady as any ) ) . not . toThrow ( ) ;
267+ expect ( ( ) => addon . fit ( ) ) . not . toThrow ( ) ;
268+ expect ( ( ) => addon . dispose ( ) ) . not . toThrow ( ) ;
269+ } ) ;
270+
271+ test ( 'fit() calculates correct dimensions from container' , ( ) => {
272+ // Create a mock element with known dimensions
273+ // FitAddon subtracts 15px for scrollbar, so we need to account for that
274+ const mockElement = document . createElement ( 'div' ) ;
275+ Object . defineProperty ( mockElement , 'clientWidth' , { value : 900 , configurable : true } ) ;
276+ Object . defineProperty ( mockElement , 'clientHeight' , { value : 480 , configurable : true } ) ;
277+
278+ let resizedCols = 0 ;
279+ let resizedRows = 0 ;
280+
281+ const mockTerminal = {
282+ cols : 80 ,
283+ rows : 24 ,
284+ element : mockElement ,
285+ renderer : {
286+ // 9px wide chars, 16px tall
287+ getMetrics : ( ) => ( { width : 9 , height : 16 , baseline : 12 } ) ,
288+ } ,
289+ resize : ( cols : number , rows : number ) => {
290+ resizedCols = cols ;
291+ resizedRows = rows ;
292+ mockTerminal . cols = cols ;
293+ mockTerminal . rows = rows ;
294+ } ,
295+ } ;
296+
297+ addon . activate ( mockTerminal as any ) ;
298+ addon . fit ( ) ;
299+
300+ // Expected: (900 - 15 scrollbar) / 9 = 98 cols, 480 / 16 = 30 rows
301+ expect ( resizedCols ) . toBe ( 98 ) ;
302+ expect ( resizedRows ) . toBe ( 30 ) ;
303+ } ) ;
304+
305+ test ( 'proposeDimensions returns correct values' , ( ) => {
306+ // FitAddon subtracts 15px for scrollbar width
307+ const mockElement = document . createElement ( 'div' ) ;
308+ Object . defineProperty ( mockElement , 'clientWidth' , { value : 720 , configurable : true } ) ;
309+ Object . defineProperty ( mockElement , 'clientHeight' , { value : 384 , configurable : true } ) ;
310+
311+ const mockTerminal = {
312+ cols : 80 ,
313+ rows : 24 ,
314+ element : mockElement ,
315+ renderer : {
316+ getMetrics : ( ) => ( { width : 8 , height : 16 , baseline : 12 } ) ,
317+ } ,
318+ resize : ( ) => { } ,
319+ } ;
320+
321+ addon . activate ( mockTerminal as any ) ;
322+ const dims = addon . proposeDimensions ( ) ;
323+
324+ // Expected: (720 - 15 scrollbar) / 8 = 88 cols, 384 / 16 = 24 rows
325+ expect ( dims ) . toEqual ( { cols : 88 , rows : 24 } ) ;
326+ } ) ;
327+ } ) ;
0 commit comments