import { describe, expect, test } from 'bun:test' import { exportFigFile, parseFigFile, SceneGraph, sceneNodeToKiwi } from '@open-pencil/core' import { expectDefined, getNodeOrThrow } from '#tests/helpers/assert' import { ROOT_GUID, pageId } from './helpers' describe('Integration: auto-layout component with all fixes', () => { test('full component roundtrip preserves layout structure', async () => { const graph = new SceneGraph() // Parent auto-layout frame (like a card component) const card = graph.createNode('FRAME', pageId(graph), { name: 'StatCard', x: 100, y: 100, width: 328, height: 200, layoutMode: 'VERTICAL', itemSpacing: 8, paddingTop: 16, paddingLeft: 16, paddingBottom: 16, paddingRight: 16, primaryAxisSizing: 'FIXED', counterAxisSizing: 'FIXED', clipsContent: true, strokesIncludedInLayout: true, fills: [{ type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1, visible: true }] }) // Title text graph.createNode('TEXT', card.id, { name: 'Title', x: 16, y: 16, width: 296, height: 20, text: 'Total Active Users', fontFamily: 'DM Sans 9pt', fontWeight: 600, fontSize: 14, lineHeight: 20 }) // Value text graph.createNode('TEXT', card.id, { name: 'Value', x: 16, y: 44, width: 296, height: 40, text: '12,345', fontFamily: 'Inter', fontWeight: 700, fontSize: 32, lineHeight: 40 }) // Verify kiwi output directly const blobs: Uint8Array[] = [] const changes = sceneNodeToKiwi( getNodeOrThrow(graph, card.id), ROOT_GUID, 0, { value: 100 }, graph, blobs ) const cardNc = changes[0] const titleNc = expectDefined( changes.find((nc) => nc.name === 'Title'), 'Title node change' ) const valueNc = expectDefined( changes.find((nc) => nc.name === 'Value'), 'Value node change' ) // Fix 1: children preserve their stored transform offsets expect(titleNc.transform.m02).toBe(16) expect(titleNc.transform.m12).toBe(16) expect(valueNc.transform.m02).toBe(16) expect(valueNc.transform.m12).toBe(44) // Fix 2: frameMaskDisabled is inverse of clipsContent expect(cardNc.frameMaskDisabled).toBe(false) // clipsContent=true → frameMaskDisabled=false expect(titleNc.frameMaskDisabled).toBe(true) // text nodes don't clip // Fix 3: bordersTakeSpace expect(cardNc.bordersTakeSpace).toBe(true) // Fix 4: lineHeight on text nodes expect(titleNc.lineHeight).toEqual({ value: 20, units: 'PIXELS' }) expect(valueNc.lineHeight).toEqual({ value: 40, units: 'PIXELS' }) // Fix 5: font family normalized expect(titleNc.fontName.family).toBe('DM Sans') expect(valueNc.fontName.family).toBe('Inter') // Roundtrip through export/parse const exported = await exportFigFile(graph) const reimported = await parseFigFile(exported.buffer as ArrayBuffer) const nodes = [...reimported.nodes.values()] const cardNode = expectDefined( nodes.find((n) => n.name === 'StatCard'), 'StatCard node' ) const titleNode = expectDefined( nodes.find((n) => n.name === 'Title'), 'Title node' ) expect(cardNode.layoutMode).toBe('VERTICAL') expect(cardNode.clipsContent).toBe(true) expect(titleNode.fontFamily).toBe('DM Sans') expect(titleNode.lineHeight).toBe(20) }) })