- Add AST-based lint checks for broad unknown object assertions and local JsonObject aliases - Centralize JsonObject in core and package-local MCP RPC JSON typing - Replace baseline Record<string, unknown> assertions with named shared/domain types
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
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)
|
|
})
|
|
})
|