Skip to content

Commit fb0c97f

Browse files
committed
Optimize export devup
1 parent 912a2bf commit fb0c97f

File tree

2 files changed

+238
-78
lines changed

2 files changed

+238
-78
lines changed

src/commands/devup/__tests__/index.test.ts

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)