Skip to content

Commit e647329

Browse files
authored
Merge pull request #31 from dev-five-git/optimize-export-devup
Optimize export devup
2 parents 99cdbb8 + 4e0de8a commit e647329

File tree

4 files changed

+435
-83
lines changed

4 files changed

+435
-83
lines changed

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

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,253 @@ describe('devup commands', () => {
235235
)
236236
})
237237

238+
test('exportDevup treeshake true handles mixed-style text nodes', async () => {
239+
getColorCollectionSpy = spyOn(
240+
getColorCollectionModule,
241+
'getDevupColorCollection',
242+
).mockResolvedValue(null)
243+
styleNameToTypographySpy = spyOn(
244+
styleNameToTypographyModule,
245+
'styleNameToTypography',
246+
).mockReturnValue({ level: 0, name: 'heading' })
247+
textStyleToTypographySpy = spyOn(
248+
textStyleToTypographyModule,
249+
'textStyleToTypography',
250+
).mockReturnValue({ fontFamily: 'Inter' } as unknown as DevupTypography)
251+
252+
const mixedSymbol = Symbol('mixed')
253+
const mixedTextNode = {
254+
type: 'TEXT',
255+
textStyleId: mixedSymbol,
256+
getStyledTextSegments: () => [
257+
{ textStyleId: 'style1' },
258+
{ textStyleId: 'style2' },
259+
],
260+
} as unknown as TextNode
261+
262+
;(globalThis as { figma?: unknown }).figma = {
263+
util: { rgba: (v: unknown) => v },
264+
loadAllPagesAsync: async () => {},
265+
getLocalTextStylesAsync: async () => [
266+
{ id: 'style1', name: 'heading/1' } as unknown as TextStyle,
267+
],
268+
root: { findAllWithCriteria: () => [mixedTextNode] },
269+
mixed: mixedSymbol,
270+
variables: { getVariableByIdAsync: async () => null },
271+
} as unknown as typeof figma
272+
273+
await exportDevup('json', true)
274+
275+
expect(downloadFileMock).toHaveBeenCalledWith(
276+
'devup.json',
277+
expect.stringContaining('"typography"'),
278+
)
279+
})
280+
281+
test('exportDevup treeshake true stops within current page subtree and skips later pages', async () => {
282+
getColorCollectionSpy = spyOn(
283+
getColorCollectionModule,
284+
'getDevupColorCollection',
285+
).mockResolvedValue(null)
286+
styleNameToTypographySpy = spyOn(
287+
styleNameToTypographyModule,
288+
'styleNameToTypography',
289+
).mockImplementation((name: string) =>
290+
name.includes('2')
291+
? ({ level: 1, name: 'heading' } as const)
292+
: ({ level: 0, name: 'heading' } as const),
293+
)
294+
textStyleToTypographySpy = spyOn(
295+
textStyleToTypographyModule,
296+
'textStyleToTypography',
297+
).mockReturnValue({ fontFamily: 'Inter' } as unknown as DevupTypography)
298+
299+
const currentTextNode = {
300+
type: 'TEXT',
301+
textStyleId: 'style1',
302+
getStyledTextSegments: () => [{ textStyleId: 'style1' }],
303+
} as unknown as TextNode
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
315+
const currentPage = {
316+
id: 'page-current',
317+
children: [firstSection, secondSection],
318+
} as unknown as PageNode
319+
const otherPage = {
320+
id: 'page-other',
321+
children: [],
322+
loadAsync: otherPageLoadAsync,
323+
} as unknown as PageNode
324+
325+
;(globalThis as { figma?: unknown }).figma = {
326+
util: { rgba: (v: unknown) => v },
327+
currentPage,
328+
getLocalTextStylesAsync: async () => [
329+
{ id: 'style1', name: 'heading/1' } as unknown as TextStyle,
330+
{ id: 'style2', name: 'heading/2' } as unknown as TextStyle,
331+
],
332+
root: {
333+
children: [otherPage, currentPage],
334+
},
335+
mixed: Symbol('mixed'),
336+
variables: { getVariableByIdAsync: async () => null },
337+
} as unknown as typeof figma
338+
339+
await exportDevup('json', true)
340+
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)
415+
expect(downloadFileMock).toHaveBeenCalledWith(
416+
'devup.json',
417+
expect.stringContaining('"typography"'),
418+
)
419+
})
420+
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+
238485
test('exportDevup fills missing typography levels from styles map', async () => {
239486
getColorCollectionSpy = spyOn(
240487
getColorCollectionModule,

0 commit comments

Comments
 (0)