openpencil/tests/engine/kiwi/serialize-fixes/auto-layout/transforms.test.ts
Danila Poyarkov 1ed7de6056 chore(lint): reject broad unknown type assertions
- 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
2026-05-25 00:26:02 +03:00

232 lines
5.8 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { SceneGraph, sceneNodeToKiwi } from '@open-pencil/core'
import { expectDefined, getNodeOrThrow } from '#tests/helpers/assert'
import { ROOT_GUID, pageId } from '../helpers'
describe('Fix 1: auto-layout child transforms', () => {
test('auto-layout child preserves its stored transform offsets', () => {
const graph = new SceneGraph()
const parent = graph.createNode('FRAME', pageId(graph), {
name: 'AutoLayout',
x: 0,
y: 0,
width: 300,
height: 400,
layoutMode: 'VERTICAL',
itemSpacing: 8,
paddingTop: 16,
paddingLeft: 16,
paddingBottom: 16,
paddingRight: 16
})
graph.createNode('FRAME', parent.id, {
name: 'Child',
x: 50,
y: 100,
width: 200,
height: 60
})
const blobs: Uint8Array[] = []
// Serialize the parent — children are serialized recursively
const changes = sceneNodeToKiwi(
getNodeOrThrow(graph, parent.id),
ROOT_GUID,
0,
{ value: 100 },
graph,
blobs
)
// changes[0] = parent, changes[1] = child
const childNc = expectDefined(
changes.find((nc) => nc.name === 'Child'),
'child node change'
)
expect(childNc).toBeDefined()
expect(childNc.transform.m02).toBe(50)
expect(childNc.transform.m12).toBe(100)
})
test('absolute-positioned child inside auto-layout keeps its real x/y', () => {
const graph = new SceneGraph()
const parent = graph.createNode('FRAME', pageId(graph), {
name: 'AutoLayout',
x: 0,
y: 0,
width: 300,
height: 400,
layoutMode: 'VERTICAL',
itemSpacing: 8
})
graph.createNode('FRAME', parent.id, {
name: 'AbsChild',
x: 75,
y: 120,
width: 50,
height: 50,
layoutPositioning: 'ABSOLUTE'
})
const blobs: Uint8Array[] = []
const changes = sceneNodeToKiwi(
getNodeOrThrow(graph, parent.id),
ROOT_GUID,
0,
{ value: 100 },
graph,
blobs
)
const absNc = expectDefined(
changes.find((nc) => nc.name === 'AbsChild'),
'absolute child node change'
)
expect(absNc).toBeDefined()
expect(absNc.transform.m02).toBe(75)
expect(absNc.transform.m12).toBe(120)
})
test('child in non-auto-layout parent keeps its real x/y', () => {
const graph = new SceneGraph()
const parent = graph.createNode('FRAME', pageId(graph), {
name: 'PlainFrame',
x: 0,
y: 0,
width: 300,
height: 400
// layoutMode defaults to 'NONE'
})
graph.createNode('FRAME', parent.id, {
name: 'Child',
x: 30,
y: 45,
width: 100,
height: 80
})
const blobs: Uint8Array[] = []
const changes = sceneNodeToKiwi(
getNodeOrThrow(graph, parent.id),
ROOT_GUID,
0,
{ value: 100 },
graph,
blobs
)
const childNc = expectDefined(
changes.find((nc) => nc.name === 'Child'),
'child node change'
)
expect(childNc).toBeDefined()
expect(childNc.transform.m02).toBe(30)
expect(childNc.transform.m12).toBe(45)
})
test('preserves imported Figma layout metadata for instance roundtrips', () => {
const graph = new SceneGraph()
const parent = graph.createNode('FRAME', pageId(graph), {
name: 'Checked List',
width: 878,
height: 24,
layoutMode: 'HORIZONTAL',
counterAxisSizing: 'HUG',
layoutAlignSelf: 'STRETCH'
})
parent.source.fig.layout = {
stackMode: 'HORIZONTAL',
stackPrimarySizing: 'FIXED',
stackCounterSizing: 'RESIZE_TO_FIT_WITH_IMPLICIT_SIZE',
stackChildAlignSelf: 'STRETCH'
}
const child = graph.createNode('INSTANCE', parent.id, {
name: 'Lists',
width: 282.6666564941406,
height: 24,
layoutMode: 'VERTICAL',
layoutGrow: 1
})
child.source.fig.layout = {
stackMode: 'VERTICAL',
stackChildPrimaryGrow: 1
}
const blobs: Uint8Array[] = []
const changes = sceneNodeToKiwi(
getNodeOrThrow(graph, parent.id),
ROOT_GUID,
0,
{ value: 100 },
graph,
blobs
)
const parentNc = expectDefined(
changes.find((nc) => nc.name === 'Checked List'),
'parent node change'
)
expect(parentNc.stackCounterSizing).toBe('RESIZE_TO_FIT_WITH_IMPLICIT_SIZE')
const instanceNc = expectDefined(
changes.find((nc) => nc.name === 'Lists'),
'instance node change'
)
expect(instanceNc.stackMode).toBe('VERTICAL')
expect(instanceNc.stackChildPrimaryGrow).toBe(1)
expect(instanceNc.stackPrimarySizing).toBeUndefined()
expect(instanceNc.stackCounterSizing).toBeUndefined()
expect(instanceNc.stackPrimaryAlignItems).toBeUndefined()
expect(instanceNc.stackCounterAlignItems).toBeUndefined()
})
test('horizontal auto-layout child also preserves its stored transform offsets', () => {
const graph = new SceneGraph()
const parent = graph.createNode('FRAME', pageId(graph), {
name: 'HorizontalLayout',
x: 0,
y: 0,
width: 600,
height: 100,
layoutMode: 'HORIZONTAL',
itemSpacing: 12
})
graph.createNode('RECTANGLE', parent.id, {
name: 'Item',
x: 200,
y: 50,
width: 80,
height: 80
})
const blobs: Uint8Array[] = []
const changes = sceneNodeToKiwi(
getNodeOrThrow(graph, parent.id),
ROOT_GUID,
0,
{ value: 100 },
graph,
blobs
)
const itemNc = expectDefined(
changes.find((nc) => nc.name === 'Item'),
'item node change'
)
expect(itemNc.transform.m02).toBe(200)
expect(itemNc.transform.m12).toBe(50)
})
})
// ---------------------------------------------------------------------------
// Fix 2 — frameMaskDisabled always true
// ---------------------------------------------------------------------------