@@ -278,7 +278,7 @@ describe('devup commands', () => {
278278 )
279279 } )
280280
281- test ( 'exportDevup treeshake true stops early once a typography key is found ' , async ( ) => {
281+ test ( 'exportDevup treeshake true stops within current page subtree and skips later pages ' , async ( ) => {
282282 getColorCollectionSpy = spyOn (
283283 getColorCollectionModule ,
284284 'getDevupColorCollection' ,
@@ -301,45 +301,187 @@ describe('devup commands', () => {
301301 textStyleId : 'style1' ,
302302 getStyledTextSegments : ( ) => [ { textStyleId : 'style1' } ] ,
303303 } as unknown as TextNode
304- const currentPageFindAllWithCriteria = mock ( ( ) => [ currentTextNode ] )
305- const otherPageFindAllWithCriteria = mock ( ( ) => [ ] )
306- const rootFindAllWithCriteria = mock ( ( ) => [ ] )
304+ const firstSectionFindAllWithCriteria = mock ( ( ) => [ currentTextNode ] )
305+ const secondSectionFindAllWithCriteria = mock ( ( ) => [ ] )
306+ const otherPageLoadAsync = mock ( async ( ) => { } )
307+ const firstSection = {
308+ type : 'SECTION' ,
309+ findAllWithCriteria : firstSectionFindAllWithCriteria ,
310+ } as unknown as SectionNode
311+ const secondSection = {
312+ type : 'SECTION' ,
313+ findAllWithCriteria : secondSectionFindAllWithCriteria ,
314+ } as unknown as SectionNode
307315 const currentPage = {
308316 id : 'page-current' ,
309- findAllWithCriteria : currentPageFindAllWithCriteria ,
317+ children : [ firstSection , secondSection ] ,
310318 } as unknown as PageNode
311319 const otherPage = {
312320 id : 'page-other' ,
313- findAllWithCriteria : otherPageFindAllWithCriteria ,
321+ children : [ ] ,
322+ loadAsync : otherPageLoadAsync ,
314323 } as unknown as PageNode
315324
316325 ; ( globalThis as { figma ?: unknown } ) . figma = {
317326 util : { rgba : ( v : unknown ) => v } ,
318327 currentPage,
319- loadAllPagesAsync : async ( ) => { } ,
320328 getLocalTextStylesAsync : async ( ) => [
321329 { id : 'style1' , name : 'heading/1' } as unknown as TextStyle ,
322330 { id : 'style2' , name : 'heading/2' } as unknown as TextStyle ,
323331 ] ,
324332 root : {
325333 children : [ otherPage , currentPage ] ,
326- findAllWithCriteria : rootFindAllWithCriteria ,
327334 } ,
328335 mixed : Symbol ( 'mixed' ) ,
329336 variables : { getVariableByIdAsync : async ( ) => null } ,
330337 } as unknown as typeof figma
331338
332339 await exportDevup ( 'json' , true )
333340
334- expect ( currentPageFindAllWithCriteria ) . toHaveBeenCalledTimes ( 1 )
335- expect ( otherPageFindAllWithCriteria ) . not . toHaveBeenCalled ( )
336- expect ( rootFindAllWithCriteria ) . not . toHaveBeenCalled ( )
341+ expect ( firstSectionFindAllWithCriteria ) . toHaveBeenCalledTimes ( 1 )
342+ expect ( secondSectionFindAllWithCriteria ) . not . toHaveBeenCalled ( )
343+ expect ( otherPageLoadAsync ) . not . toHaveBeenCalled ( )
344+ expect ( downloadFileMock ) . toHaveBeenCalledWith (
345+ 'devup.json' ,
346+ expect . stringContaining ( '"typography"' ) ,
347+ )
348+ } )
349+
350+ test ( 'exportDevup treeshake true lazily loads later pages when needed' , async ( ) => {
351+ getColorCollectionSpy = spyOn (
352+ getColorCollectionModule ,
353+ 'getDevupColorCollection' ,
354+ ) . mockResolvedValue ( null )
355+ styleNameToTypographySpy = spyOn (
356+ styleNameToTypographyModule ,
357+ 'styleNameToTypography' ,
358+ ) . mockImplementation ( ( name : string ) =>
359+ name . includes ( '2' )
360+ ? ( { level : 1 , name : 'body' } as const )
361+ : ( { level : 0 , name : 'heading' } as const ) ,
362+ )
363+ textStyleToTypographySpy = spyOn (
364+ textStyleToTypographyModule ,
365+ 'textStyleToTypography' ,
366+ ) . mockReturnValue ( { fontFamily : 'Inter' } as unknown as DevupTypography )
367+
368+ const currentSectionFindAllWithCriteria = mock ( ( ) => [ ] )
369+ const otherTextNode = {
370+ type : 'TEXT' ,
371+ textStyleId : 'style2' ,
372+ getStyledTextSegments : ( ) => [ { textStyleId : 'style2' } ] ,
373+ } as unknown as TextNode
374+ const otherSectionFindAllWithCriteria = mock ( ( ) => [ otherTextNode ] )
375+ const otherPageLoadAsync = mock ( async ( ) => { } )
376+ const currentPage = {
377+ id : 'page-current' ,
378+ children : [
379+ {
380+ type : 'SECTION' ,
381+ findAllWithCriteria : currentSectionFindAllWithCriteria ,
382+ } as unknown as SectionNode ,
383+ ] ,
384+ } as unknown as PageNode
385+ const otherPage = {
386+ id : 'page-other' ,
387+ children : [
388+ {
389+ type : 'SECTION' ,
390+ findAllWithCriteria : otherSectionFindAllWithCriteria ,
391+ } as unknown as SectionNode ,
392+ ] ,
393+ loadAsync : otherPageLoadAsync ,
394+ } as unknown as PageNode
395+
396+ ; ( globalThis as { figma ?: unknown } ) . figma = {
397+ util : { rgba : ( v : unknown ) => v } ,
398+ currentPage,
399+ getLocalTextStylesAsync : async ( ) => [
400+ { id : 'style1' , name : 'heading/1' } as unknown as TextStyle ,
401+ { id : 'style2' , name : 'body/2' } as unknown as TextStyle ,
402+ ] ,
403+ root : {
404+ children : [ currentPage , otherPage ] ,
405+ } ,
406+ mixed : Symbol ( 'mixed' ) ,
407+ variables : { getVariableByIdAsync : async ( ) => null } ,
408+ } as unknown as typeof figma
409+
410+ await exportDevup ( 'json' , true )
411+
412+ expect ( currentSectionFindAllWithCriteria ) . toHaveBeenCalledTimes ( 1 )
413+ expect ( otherPageLoadAsync ) . toHaveBeenCalledTimes ( 1 )
414+ expect ( otherSectionFindAllWithCriteria ) . toHaveBeenCalledTimes ( 1 )
337415 expect ( downloadFileMock ) . toHaveBeenCalledWith (
338416 'devup.json' ,
339417 expect . stringContaining ( '"typography"' ) ,
340418 )
341419 } )
342420
421+ test ( 'exportDevup treeshake true handles direct text children and recursive fallback nodes' , async ( ) => {
422+ getColorCollectionSpy = spyOn (
423+ getColorCollectionModule ,
424+ 'getDevupColorCollection' ,
425+ ) . mockResolvedValue ( null )
426+ styleNameToTypographySpy = spyOn (
427+ styleNameToTypographyModule ,
428+ 'styleNameToTypography' ,
429+ ) . mockImplementation ( ( name : string ) =>
430+ name . includes ( '2' )
431+ ? ( { level : 1 , name : 'body' } as const )
432+ : ( { level : 0 , name : 'heading' } as const ) ,
433+ )
434+ textStyleToTypographySpy = spyOn (
435+ textStyleToTypographyModule ,
436+ 'textStyleToTypography' ,
437+ ) . mockImplementation (
438+ ( style : TextStyle ) => ( { id : style . id } ) as unknown as DevupTypography ,
439+ )
440+
441+ const directTextNode = {
442+ type : 'TEXT' ,
443+ textStyleId : 'style1' ,
444+ getStyledTextSegments : ( ) => [ { textStyleId : 'style1' } ] ,
445+ } as unknown as TextNode
446+ const nestedTextNode = {
447+ type : 'TEXT' ,
448+ textStyleId : 'style2' ,
449+ getStyledTextSegments : ( ) => [ { textStyleId : 'style2' } ] ,
450+ } as unknown as TextNode
451+ const recursiveNode = {
452+ type : 'GROUP' ,
453+ children : [ nestedTextNode ] ,
454+ } as unknown as GroupNode
455+ const currentPage = {
456+ id : 'page-current' ,
457+ children : [ directTextNode , recursiveNode ] ,
458+ } as unknown as PageNode
459+
460+ ; ( globalThis as { figma ?: unknown } ) . figma = {
461+ util : { rgba : ( v : unknown ) => v } ,
462+ currentPage,
463+ getLocalTextStylesAsync : async ( ) => [
464+ { id : 'style1' , name : 'heading/1' } as unknown as TextStyle ,
465+ { id : 'style2' , name : 'body/2' } as unknown as TextStyle ,
466+ ] ,
467+ root : {
468+ children : [ currentPage ] ,
469+ } ,
470+ mixed : Symbol ( 'mixed' ) ,
471+ variables : { getVariableByIdAsync : async ( ) => null } ,
472+ } as unknown as typeof figma
473+
474+ await exportDevup ( 'json' , true )
475+
476+ const firstCall = downloadFileMock . mock . calls [ 0 ] as unknown [ ] | undefined
477+ const data = ( firstCall ?. [ 1 ] as string ) ?? '{}'
478+ const parsed = JSON . parse ( data ) as {
479+ theme ?: { typography ?: Record < string , unknown > }
480+ }
481+ expect ( parsed . theme ?. typography ?. heading ) . toBeDefined ( )
482+ expect ( parsed . theme ?. typography ?. body ) . toBeDefined ( )
483+ } )
484+
343485 test ( 'exportDevup fills missing typography levels from styles map' , async ( ) => {
344486 getColorCollectionSpy = spyOn (
345487 getColorCollectionModule ,
0 commit comments