Skip to content

Commit 5d50b1e

Browse files
committed
Split
1 parent d25d152 commit 5d50b1e

File tree

4 files changed

+587
-467
lines changed

4 files changed

+587
-467
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { assembleNodeTree, setupVariableMocks } from '../assemble-node-tree'
3+
4+
describe('assembleNodeTree', () => {
5+
test('should add getMainComponentAsync mock to INSTANCE node', async () => {
6+
// Create node data for an INSTANCE node
7+
const nodes = [
8+
{
9+
id: 'instance-1',
10+
name: 'InstanceNode',
11+
type: 'INSTANCE',
12+
mainComponent: null,
13+
},
14+
]
15+
16+
// assembleNodeTree processes nodes and adds getMainComponentAsync to INSTANCE nodes
17+
const rootNode = assembleNodeTree(nodes)
18+
19+
expect(rootNode.type).toBe('INSTANCE')
20+
21+
// The prepared node should have getMainComponentAsync method
22+
expect(typeof rootNode.getMainComponentAsync).toBe('function')
23+
24+
// Call the method and verify it returns null
25+
const getMainComponentAsync =
26+
rootNode.getMainComponentAsync as () => Promise<unknown>
27+
const mainComponent = await getMainComponentAsync()
28+
expect(mainComponent).toBeNull()
29+
})
30+
31+
test('should setup variable mocks when variables provided', async () => {
32+
// Setup globalThis.figma first (required for setupVariableMocks)
33+
;(globalThis as unknown as { figma: Record<string, unknown> }).figma = {}
34+
35+
// Create nodes with variable reference in fills
36+
const nodes = [
37+
{
38+
id: 'node-1',
39+
name: 'NodeWithVariable',
40+
type: 'FRAME',
41+
fills: [
42+
{
43+
type: 'SOLID',
44+
boundVariables: {
45+
color: 'VariableID:456',
46+
},
47+
},
48+
],
49+
},
50+
]
51+
52+
const variables = [{ id: 'VariableID:456', name: 'secondary-color' }]
53+
54+
// Call assembleNodeTree with variables
55+
const rootNode = assembleNodeTree(nodes, variables)
56+
57+
expect(rootNode.id).toBe('node-1')
58+
59+
// Verify variable mock was set up
60+
const g = globalThis as {
61+
figma?: {
62+
variables?: { getVariableByIdAsync?: (id: string) => Promise<unknown> }
63+
}
64+
}
65+
expect(g.figma?.variables?.getVariableByIdAsync).toBeDefined()
66+
67+
// Call the mock to verify it returns the variable info
68+
const getVariableByIdAsync = g.figma?.variables?.getVariableByIdAsync
69+
if (getVariableByIdAsync) {
70+
const variable = await getVariableByIdAsync('VariableID:456')
71+
expect(variable).toEqual({
72+
id: 'VariableID:456',
73+
name: 'secondary-color',
74+
})
75+
}
76+
})
77+
78+
test('should add getMainComponentAsync to non-INSTANCE nodes', async () => {
79+
// Create a FRAME node (not INSTANCE)
80+
const nodes = [
81+
{
82+
id: 'frame-1',
83+
name: 'FrameNode',
84+
type: 'FRAME',
85+
},
86+
]
87+
88+
const rootNode = assembleNodeTree(nodes)
89+
90+
expect(rootNode.type).toBe('FRAME')
91+
92+
// The FRAME node should have getMainComponentAsync method (default fallback)
93+
expect(typeof rootNode.getMainComponentAsync).toBe('function')
94+
95+
// Call the method and verify it returns null
96+
const getMainComponentAsync =
97+
rootNode.getMainComponentAsync as () => Promise<unknown>
98+
const result = await getMainComponentAsync()
99+
expect(result).toBeNull()
100+
})
101+
102+
test('should link children by id references', () => {
103+
// Create nodes with children as string IDs (how test data comes from toTestCaseFormat)
104+
const nodes = [
105+
{
106+
id: 'parent-1',
107+
name: 'Parent',
108+
type: 'FRAME',
109+
children: ['child-1', 'child-2'],
110+
},
111+
{
112+
id: 'child-1',
113+
name: 'Child1',
114+
type: 'FRAME',
115+
},
116+
{
117+
id: 'child-2',
118+
name: 'Child2',
119+
type: 'RECTANGLE',
120+
},
121+
]
122+
123+
const rootNode = assembleNodeTree(nodes)
124+
125+
expect(rootNode.id).toBe('parent-1')
126+
expect(Array.isArray(rootNode.children)).toBe(true)
127+
expect(rootNode.children?.length).toBe(2)
128+
129+
// Children should be linked as objects, not strings
130+
const child1 = rootNode.children?.[0]
131+
expect(typeof child1).toBe('object')
132+
expect((child1 as { id: string })?.id).toBe('child-1')
133+
134+
const child2 = rootNode.children?.[1]
135+
expect(typeof child2).toBe('object')
136+
expect((child2 as { id: string })?.id).toBe('child-2')
137+
})
138+
139+
test('should filter out undefined children', () => {
140+
// Create nodes with children referencing non-existent nodes
141+
const nodes = [
142+
{
143+
id: 'parent-1',
144+
name: 'Parent',
145+
type: 'FRAME',
146+
children: ['child-1', 'non-existent-child'],
147+
},
148+
{
149+
id: 'child-1',
150+
name: 'Child1',
151+
type: 'FRAME',
152+
},
153+
]
154+
155+
const rootNode = assembleNodeTree(nodes)
156+
157+
expect(rootNode.id).toBe('parent-1')
158+
expect(Array.isArray(rootNode.children)).toBe(true)
159+
// Only child-1 should be in children, non-existent-child should be filtered out
160+
expect(rootNode.children?.length).toBe(1)
161+
expect((rootNode.children?.[0] as { id: string })?.id).toBe('child-1')
162+
})
163+
164+
test('TEXT node getStyledTextSegments should return stored segments', () => {
165+
// Create TEXT node with styledTextSegments data
166+
const nodes = [
167+
{
168+
id: 'text-1',
169+
name: 'TextNode',
170+
type: 'TEXT',
171+
characters: 'Hello World',
172+
styledTextSegments: [
173+
{
174+
start: 0,
175+
end: 5,
176+
characters: 'Hello',
177+
fontName: { family: 'Arial', style: 'Bold' },
178+
fontWeight: 700,
179+
fontSize: 20,
180+
},
181+
{
182+
start: 6,
183+
end: 11,
184+
characters: 'World',
185+
fontName: { family: 'Arial', style: 'Regular' },
186+
fontWeight: 400,
187+
fontSize: 16,
188+
},
189+
],
190+
},
191+
]
192+
193+
const rootNode = assembleNodeTree(nodes)
194+
195+
expect(rootNode.type).toBe('TEXT')
196+
197+
// Call getStyledTextSegments
198+
const getStyledTextSegments = (
199+
rootNode as unknown as Record<string, unknown>
200+
).getStyledTextSegments as () => unknown[]
201+
expect(typeof getStyledTextSegments).toBe('function')
202+
203+
const segments = getStyledTextSegments()
204+
expect(Array.isArray(segments)).toBe(true)
205+
expect(segments.length).toBe(2)
206+
expect((segments[0] as { characters: string }).characters).toBe('Hello')
207+
expect((segments[1] as { characters: string }).characters).toBe('World')
208+
})
209+
210+
test('TEXT node getStyledTextSegments should generate default segment when no styledTextSegments', () => {
211+
// Create TEXT node without styledTextSegments
212+
const nodes = [
213+
{
214+
id: 'text-1',
215+
name: 'TextNode',
216+
type: 'TEXT',
217+
characters: 'Test Text',
218+
fontName: { family: 'Inter', style: 'Regular' },
219+
fontWeight: 400,
220+
fontSize: 14,
221+
lineHeight: { unit: 'AUTO' },
222+
letterSpacing: { unit: 'PERCENT', value: 0 },
223+
fills: [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }],
224+
},
225+
]
226+
227+
const rootNode = assembleNodeTree(nodes)
228+
229+
expect(rootNode.type).toBe('TEXT')
230+
231+
// Call getStyledTextSegments - should generate default segment
232+
const getStyledTextSegments = (
233+
rootNode as unknown as Record<string, unknown>
234+
).getStyledTextSegments as () => unknown[]
235+
const segments = getStyledTextSegments()
236+
237+
expect(Array.isArray(segments)).toBe(true)
238+
expect(segments.length).toBe(1)
239+
240+
const segment = segments[0] as Record<string, unknown>
241+
expect(segment.characters).toBe('Test Text')
242+
expect(segment.start).toBe(0)
243+
expect(segment.end).toBe(9)
244+
expect(segment.textDecoration).toBe('NONE')
245+
expect(segment.textCase).toBe('ORIGINAL')
246+
})
247+
248+
test('should handle children that are already NodeData objects', () => {
249+
// Create nodes where children are already objects, not string IDs
250+
const childNode = {
251+
id: 'child-1',
252+
name: 'Child1',
253+
type: 'FRAME',
254+
}
255+
256+
const nodes = [
257+
{
258+
id: 'parent-1',
259+
name: 'Parent',
260+
type: 'FRAME',
261+
children: [childNode], // Already an object, not a string ID
262+
},
263+
childNode,
264+
]
265+
266+
const rootNode = assembleNodeTree(nodes)
267+
268+
expect(rootNode.id).toBe('parent-1')
269+
expect(Array.isArray(rootNode.children)).toBe(true)
270+
expect(rootNode.children?.length).toBe(1)
271+
// Child should still be an object
272+
expect((rootNode.children?.[0] as { id: string })?.id).toBe('child-1')
273+
})
274+
})
275+
276+
describe('setupVariableMocks', () => {
277+
test('should setup figma.variables.getVariableByIdAsync mock', async () => {
278+
// Setup globalThis.figma
279+
;(globalThis as unknown as { figma: Record<string, unknown> }).figma = {}
280+
281+
const variables = [
282+
{ id: 'VariableID:123', name: 'test-color' },
283+
{ id: 'VariableID:456', name: 'another-color' },
284+
]
285+
286+
setupVariableMocks(variables)
287+
288+
const g = globalThis as {
289+
figma?: {
290+
variables?: { getVariableByIdAsync?: (id: string) => Promise<unknown> }
291+
}
292+
}
293+
294+
expect(g.figma?.variables?.getVariableByIdAsync).toBeDefined()
295+
296+
// Test the mock returns correct values
297+
if (g.figma?.variables?.getVariableByIdAsync) {
298+
const result1 =
299+
await g.figma.variables.getVariableByIdAsync('VariableID:123')
300+
expect(result1).toEqual({ id: 'VariableID:123', name: 'test-color' })
301+
302+
const result2 =
303+
await g.figma.variables.getVariableByIdAsync('VariableID:456')
304+
expect(result2).toEqual({ id: 'VariableID:456', name: 'another-color' })
305+
306+
// Non-existent variable should return null
307+
const result3 =
308+
await g.figma.variables.getVariableByIdAsync('VariableID:789')
309+
expect(result3).toBeNull()
310+
}
311+
})
312+
313+
test('should fallback to original mock when variable not found', async () => {
314+
// Setup globalThis.figma with an existing mock
315+
const originalMock = async (id: string) => {
316+
if (id === 'VariableID:original') {
317+
return { id: 'VariableID:original', name: 'original-color' }
318+
}
319+
return null
320+
}
321+
322+
;(
323+
globalThis as unknown as {
324+
figma: { variables: { getVariableByIdAsync: typeof originalMock } }
325+
}
326+
).figma = {
327+
variables: {
328+
getVariableByIdAsync: originalMock,
329+
},
330+
}
331+
332+
// Now call setupVariableMocks with new variables
333+
const variables = [{ id: 'VariableID:new', name: 'new-color' }]
334+
setupVariableMocks(variables)
335+
336+
const g = globalThis as {
337+
figma?: {
338+
variables?: { getVariableByIdAsync?: (id: string) => Promise<unknown> }
339+
}
340+
}
341+
342+
// New variable should work
343+
const result1 =
344+
await g.figma?.variables?.getVariableByIdAsync?.('VariableID:new')
345+
expect(result1).toEqual({ id: 'VariableID:new', name: 'new-color' })
346+
347+
// Original mock should still work via fallback
348+
const result2 = await g.figma?.variables?.getVariableByIdAsync?.(
349+
'VariableID:original',
350+
)
351+
expect(result2).toEqual({
352+
id: 'VariableID:original',
353+
name: 'original-color',
354+
})
355+
})
356+
})

0 commit comments

Comments
 (0)