Skip to content

Commit d25d152

Browse files
committed
Add case
1 parent cfda41c commit d25d152

File tree

2 files changed

+184
-14
lines changed

2 files changed

+184
-14
lines changed

src/codegen/utils/__tests__/node-proxy.test.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,4 +716,150 @@ describe('nodeProxyTracker', () => {
716716
const result = await getMainComponentAsync()
717717
expect(result).toBeNull()
718718
})
719+
720+
test('assembleNodeTree should link children by id references', () => {
721+
// Create nodes with children as string IDs (how test data comes from toTestCaseFormat)
722+
const nodes = [
723+
{
724+
id: 'parent-1',
725+
name: 'Parent',
726+
type: 'FRAME',
727+
children: ['child-1', 'child-2'],
728+
},
729+
{
730+
id: 'child-1',
731+
name: 'Child1',
732+
type: 'FRAME',
733+
},
734+
{
735+
id: 'child-2',
736+
name: 'Child2',
737+
type: 'RECTANGLE',
738+
},
739+
]
740+
741+
const rootNode = assembleNodeTree(nodes)
742+
743+
expect(rootNode.id).toBe('parent-1')
744+
expect(Array.isArray(rootNode.children)).toBe(true)
745+
expect(rootNode.children?.length).toBe(2)
746+
747+
// Children should be linked as objects, not strings
748+
const child1 = rootNode.children?.[0]
749+
expect(typeof child1).toBe('object')
750+
expect((child1 as { id: string })?.id).toBe('child-1')
751+
752+
const child2 = rootNode.children?.[1]
753+
expect(typeof child2).toBe('object')
754+
expect((child2 as { id: string })?.id).toBe('child-2')
755+
})
756+
757+
test('assembleNodeTree should filter out undefined children', () => {
758+
// Create nodes with children referencing non-existent nodes
759+
const nodes = [
760+
{
761+
id: 'parent-1',
762+
name: 'Parent',
763+
type: 'FRAME',
764+
children: ['child-1', 'non-existent-child'],
765+
},
766+
{
767+
id: 'child-1',
768+
name: 'Child1',
769+
type: 'FRAME',
770+
},
771+
]
772+
773+
const rootNode = assembleNodeTree(nodes)
774+
775+
expect(rootNode.id).toBe('parent-1')
776+
expect(Array.isArray(rootNode.children)).toBe(true)
777+
// Only child-1 should be in children, non-existent-child should be filtered out
778+
expect(rootNode.children?.length).toBe(1)
779+
expect((rootNode.children?.[0] as { id: string })?.id).toBe('child-1')
780+
})
781+
782+
test('assembleNodeTree TEXT node getStyledTextSegments should return stored segments', () => {
783+
// Create TEXT node with styledTextSegments data
784+
const nodes = [
785+
{
786+
id: 'text-1',
787+
name: 'TextNode',
788+
type: 'TEXT',
789+
characters: 'Hello World',
790+
styledTextSegments: [
791+
{
792+
start: 0,
793+
end: 5,
794+
characters: 'Hello',
795+
fontName: { family: 'Arial', style: 'Bold' },
796+
fontWeight: 700,
797+
fontSize: 20,
798+
},
799+
{
800+
start: 6,
801+
end: 11,
802+
characters: 'World',
803+
fontName: { family: 'Arial', style: 'Regular' },
804+
fontWeight: 400,
805+
fontSize: 16,
806+
},
807+
],
808+
},
809+
]
810+
811+
const rootNode = assembleNodeTree(nodes)
812+
813+
expect(rootNode.type).toBe('TEXT')
814+
815+
// Call getStyledTextSegments
816+
const getStyledTextSegments = (
817+
rootNode as unknown as Record<string, unknown>
818+
).getStyledTextSegments as () => unknown[]
819+
expect(typeof getStyledTextSegments).toBe('function')
820+
821+
const segments = getStyledTextSegments()
822+
expect(Array.isArray(segments)).toBe(true)
823+
expect(segments.length).toBe(2)
824+
expect((segments[0] as { characters: string }).characters).toBe('Hello')
825+
expect((segments[1] as { characters: string }).characters).toBe('World')
826+
})
827+
828+
test('assembleNodeTree TEXT node getStyledTextSegments should generate default segment when no styledTextSegments', () => {
829+
// Create TEXT node without styledTextSegments
830+
const nodes = [
831+
{
832+
id: 'text-1',
833+
name: 'TextNode',
834+
type: 'TEXT',
835+
characters: 'Test Text',
836+
fontName: { family: 'Inter', style: 'Regular' },
837+
fontWeight: 400,
838+
fontSize: 14,
839+
lineHeight: { unit: 'AUTO' },
840+
letterSpacing: { unit: 'PERCENT', value: 0 },
841+
fills: [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }],
842+
},
843+
]
844+
845+
const rootNode = assembleNodeTree(nodes)
846+
847+
expect(rootNode.type).toBe('TEXT')
848+
849+
// Call getStyledTextSegments - should generate default segment
850+
const getStyledTextSegments = (
851+
rootNode as unknown as Record<string, unknown>
852+
).getStyledTextSegments as () => unknown[]
853+
const segments = getStyledTextSegments()
854+
855+
expect(Array.isArray(segments)).toBe(true)
856+
expect(segments.length).toBe(1)
857+
858+
const segment = segments[0] as Record<string, unknown>
859+
expect(segment.characters).toBe('Test Text')
860+
expect(segment.start).toBe(0)
861+
expect(segment.end).toBe(9)
862+
expect(segment.textDecoration).toBe('NONE')
863+
expect(segment.textCase).toBe('ORIGINAL')
864+
})
719865
})

src/codegen/utils/node-proxy.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,12 @@ class NodeProxyTracker {
225225
>)[],
226226
)
227227
// 세그먼트 직렬화
228-
const serializedSegments = segments.map((seg) => ({
229-
...seg,
230-
fills: this.serializeArray(seg.fills as unknown[]),
231-
}))
228+
const serializedSegments = segments.map((seg) => {
229+
return {
230+
...seg,
231+
fills: this.serializeArray(seg.fills as unknown[]),
232+
}
233+
})
232234

233235
const log = this.accessLogs.get(node.id)
234236
if (log) {
@@ -264,14 +266,20 @@ class NodeProxyTracker {
264266
private isArrayLikeObject(obj: Record<string, unknown>): boolean {
265267
const keys = Object.keys(obj)
266268
if (keys.length === 0) return false
267-
return keys.every((key) => /^\d+$/.test(key))
269+
return keys.every((key) => {
270+
return /^\d+$/.test(key)
271+
})
268272
}
269273

270274
private arrayLikeToArray(obj: Record<string, unknown>): unknown[] {
271275
const keys = Object.keys(obj)
272276
.map(Number)
273-
.sort((a, b) => a - b)
274-
return keys.map((key) => obj[key])
277+
.sort((a, b) => {
278+
return a - b
279+
})
280+
return keys.map((key) => {
281+
return obj[key]
282+
})
275283
}
276284

277285
private serializeObject(
@@ -414,15 +422,19 @@ class NodeProxyTracker {
414422
return allNodes
415423
}
416424

417-
const rootNode = allNodes.find((n) => n.id === rootId)
425+
const rootNode = allNodes.find((n) => {
426+
return n.id === rootId
427+
})
418428
if (!rootNode) {
419429
return allNodes
420430
}
421431

422432
// 하위 노드 ID들을 수집 (children을 재귀적으로 탐색)
423433
const descendantIds = new Set<string>()
424434
const collectDescendants = (nodeId: string) => {
425-
const node = allNodes.find((n) => n.id === nodeId)
435+
const node = allNodes.find((n) => {
436+
return n.id === nodeId
437+
})
426438
if (!node) return
427439
if (Array.isArray(node.children)) {
428440
for (const childId of node.children) {
@@ -447,7 +459,9 @@ class NodeProxyTracker {
447459
const parentId =
448460
typeof rootNode.parent === 'string' ? rootNode.parent : undefined
449461
if (parentId) {
450-
const parentNode = allNodes.find((n) => n.id === parentId)
462+
const parentNode = allNodes.find((n) => {
463+
return n.id === parentId
464+
})
451465
if (parentNode && parentNode.type === 'SECTION') {
452466
parentNode.children = [rootId]
453467
result.push(parentNode)
@@ -465,7 +479,9 @@ class NodeProxyTracker {
465479
}
466480
}
467481
if (Array.isArray(value)) {
468-
return value.map((item) => this.resolveNodeRefs(item))
482+
return value.map((item) => {
483+
return this.resolveNodeRefs(item)
484+
})
469485
}
470486
return value
471487
}
@@ -519,7 +535,11 @@ function setupVariableMocks(variables: VariableInfo[]): void {
519535
const g = globalThis as { figma?: { variables?: Record<string, unknown> } }
520536
if (!g.figma) return
521537

522-
const variableMap = new Map(variables.map((v) => [v.id, v]))
538+
const variableMap = new Map(
539+
variables.map((v) => {
540+
return [v.id, v]
541+
}),
542+
)
523543

524544
// 기존 mock을 보존하면서 새로운 변수들 추가
525545
const originalGetVariable = g.figma.variables?.getVariableByIdAsync as
@@ -579,7 +599,9 @@ export function assembleNodeTree(
579599
}
580600
return childId
581601
})
582-
.filter((child): child is NodeData => child !== undefined)
602+
.filter((child): child is NodeData => {
603+
return child !== undefined
604+
})
583605
}
584606
// children이 undefined인 경우 그대로 유지 (RECTANGLE 등 원래 children이 없는 노드)
585607

@@ -668,7 +690,9 @@ export function assembleNodeTree(
668690
// 모든 노드에 getMainComponentAsync 메서드 추가 (아직 없는 경우에만)
669691
if (!(node as unknown as Record<string, unknown>).getMainComponentAsync) {
670692
;(node as unknown as Record<string, unknown>).getMainComponentAsync =
671-
async () => null
693+
async () => {
694+
return null
695+
}
672696
}
673697

674698
// TEXT 노드에 getStyledTextSegments mock 메서드 추가

0 commit comments

Comments
 (0)