openpencil/tests/engine/pen/layout.test.ts
2026-09-10 17:02:56 +03:00

154 lines
4 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { computeAllLayouts } from '@open-pencil/core/layout'
import { parsePenFile, parseSize, type PenDocument, type PenNode } from '@open-pencil/pen'
function parseLayoutDocument(children: PenNode[]) {
const document: PenDocument = { version: '2.17', children }
const graph = parsePenFile(JSON.stringify(document))
computeAllLayouts(graph)
return graph
}
function fixedChild(id: string): PenNode {
return { id, type: 'frame', width: 620, height: 420 }
}
describe('parsePenFile — Pen layout defaults (#564)', () => {
test('treats an omitted frame layout as horizontal', () => {
const graph = parseLayoutDocument([
{
id: 'screen',
type: 'frame',
width: 1440,
layout: 'vertical',
children: [
{
id: 'section',
type: 'frame',
width: 'fill_container',
gap: 56,
padding: [72, 80],
alignItems: 'center',
children: [
fixedChild('fixed'),
{
id: 'fill',
type: 'frame',
width: 'fill_container',
height: 420
}
]
}
]
}
])
expect(graph.getNode('section')).toMatchObject({
layoutMode: 'HORIZONTAL',
width: 1440,
height: 564,
paddingTop: 72,
paddingRight: 80,
paddingBottom: 72,
paddingLeft: 80
})
expect(graph.getNode('fixed')).toMatchObject({ x: 80, y: 72, width: 620, height: 420 })
expect(graph.getNode('fill')).toMatchObject({ x: 756, y: 72, width: 604, height: 420 })
})
test.each(['fit_content', 'fit_content(100)'])('maps %s to HUG sizing', (height) => {
const graph = parseLayoutDocument([
{
id: 'section',
type: 'frame',
width: 1440,
height,
layout: 'horizontal',
padding: [72, 80],
children: [fixedChild('fixed')]
}
])
expect(graph.getNode('section')).toMatchObject({
counterAxisSizing: 'HUG',
height: 564
})
})
test('uses the parameterized fit-content value when a frame has no content', () => {
const graph = parseLayoutDocument([
{
id: 'empty',
type: 'frame',
width: 'fit_content(240)',
height: 'fit_content(120)'
}
])
expect(graph.getNode('empty')).toMatchObject({
layoutMode: 'HORIZONTAL',
primaryAxisSizing: 'HUG',
counterAxisSizing: 'HUG',
width: 240,
height: 120,
minWidth: 240,
minHeight: 120
})
})
test('ignores the parameterized fallback when content determines the size', () => {
const graph = parseLayoutDocument([
{
id: 'content-frame',
type: 'frame',
width: 'fit_content(240)',
height: 'fit_content(120)',
padding: 10,
children: [{ id: 'content', type: 'frame', width: 20, height: 30 }]
}
])
expect(graph.getNode('content-frame')).toMatchObject({
width: 40,
height: 50,
minWidth: null,
minHeight: null
})
})
test('keeps explicit freeform frames out of auto-layout', () => {
const graph = parseLayoutDocument([
{
id: 'freeform',
type: 'frame',
layout: 'none',
children: [fixedChild('fixed')]
}
])
expect(graph.getNode('freeform')?.layoutMode).toBe('NONE')
})
})
describe('parseSize — Pen sizing fallbacks (#564)', () => {
test('preserves parameterized fill-container fallback values', () => {
expect(parseSize('fill_container(900)', 100)).toEqual({ value: 900, sizing: 'FILL' })
})
test('preserves parameterized fit-content fallback metadata', () => {
expect(parseSize('fit_content(900)', 100)).toEqual({
value: 900,
sizing: 'HUG',
fitContentFallback: 900
})
})
test.each(['fit_content()', 'fit_content(nope)', 'fit_content(100)garbage'])(
'rejects malformed sizing behavior %s',
(value) => {
expect(parseSize(value, 100)).toEqual({ value: 100, sizing: 'FIXED' })
}
)
})