* fix(pen): resolve ordinary variable references Recognize nonempty dollar-prefixed variable names while preserving double-dash references and existing bindings. Add parser regressions, a reproducible .pen fixture, and visual coverage comparing variable references with literal values. Update the Unreleased changelog for #563. * test(pen): strengthen variable reference coverage --------- Co-authored-by: Danila Poyarkov <dev@dannote.net>
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import { parsePenFile, type PenDocument, type PenNode } from '@open-pencil/pen'
|
|
|
|
const BLUE = { r: 64 / 255, g: 148 / 255, b: 208 / 255, a: 1 }
|
|
|
|
function parseVariableDocument(prefix: string, children: PenNode[]) {
|
|
const document: PenDocument = {
|
|
version: '2.14',
|
|
variables: {
|
|
[`${prefix}merk-blauw`]: { type: 'color', value: '#4094D0' },
|
|
[`${prefix}font-tekst`]: { type: 'string', value: 'Barlow' },
|
|
[`${prefix}spacing`]: { type: 'number', value: 24 },
|
|
[`${prefix}size`]: { type: 'number', value: 240 }
|
|
},
|
|
children
|
|
}
|
|
return parsePenFile(JSON.stringify(document))
|
|
}
|
|
|
|
test('keeps plain and double-dash variable names distinct in one document', () => {
|
|
const document: PenDocument = {
|
|
version: '2.14',
|
|
variables: {
|
|
blue: { type: 'color', value: '#4094D0' },
|
|
'--blue': { type: 'color', value: '#FFFFFF' }
|
|
},
|
|
children: [
|
|
{ id: 'plain', type: 'frame', fill: '$blue' },
|
|
{ id: 'dashed', type: 'frame', fill: '$--blue' }
|
|
]
|
|
}
|
|
const graph = parsePenFile(JSON.stringify(document))
|
|
const plain = graph.getNode('plain')
|
|
const dashed = graph.getNode('dashed')
|
|
const plainId = plain?.boundVariables['fills[0]']
|
|
const dashedId = dashed?.boundVariables['fills[0]']
|
|
|
|
expect(plain?.fills[0]?.color).toEqual(BLUE)
|
|
expect(dashed?.fills[0]?.color).toEqual({ r: 1, g: 1, b: 1, a: 1 })
|
|
expect(plainId).toBeDefined()
|
|
expect(dashedId).toBeDefined()
|
|
expect(plainId).not.toBe(dashedId)
|
|
expect(graph.variables.get(plainId ?? '')?.name).toBe('blue')
|
|
expect(graph.variables.get(dashedId ?? '')?.name).toBe('--blue')
|
|
})
|
|
|
|
describe.each(['', '--'])('parsePenFile — $%sname references (#563)', (prefix) => {
|
|
const colorRef = `$${prefix}merk-blauw`
|
|
const fontRef = `$${prefix}font-tekst`
|
|
const spacingRef = `$${prefix}spacing`
|
|
const sizeRef = `$${prefix}size`
|
|
|
|
test.each([
|
|
['string', colorRef],
|
|
['object', { type: 'color', color: colorRef }],
|
|
['array', [{ type: 'color', color: colorRef }]]
|
|
] satisfies [string, PenNode['fill']][])(
|
|
'resolves %s fills and preserves color bindings',
|
|
(_name, fill) => {
|
|
const graph = parseVariableDocument(prefix, [
|
|
{ id: 'literal', type: 'frame', fill: '#4094D0', width: 240, height: 120 },
|
|
{
|
|
id: 'bound',
|
|
type: 'frame',
|
|
fill,
|
|
stroke: { align: 'inside', thickness: 2, fill: colorRef },
|
|
width: 240,
|
|
height: 120
|
|
}
|
|
])
|
|
const node = graph.getNode('bound')
|
|
const variable = [...graph.variables.values()].find(
|
|
(candidate) => candidate.name === `${prefix}merk-blauw`
|
|
)
|
|
|
|
expect(variable).toBeDefined()
|
|
expect(node?.fills).toEqual(graph.getNode('literal')?.fills)
|
|
expect(node?.fills[0]?.color).toEqual(BLUE)
|
|
expect(node?.strokes[0]?.color).toEqual(BLUE)
|
|
expect(node?.boundVariables).toEqual({
|
|
'fills[0]': variable?.id,
|
|
'strokes[0]': variable?.id
|
|
})
|
|
expect(graph.getNode('literal')?.boundVariables).toEqual({})
|
|
}
|
|
)
|
|
|
|
test('resolves and binds font families without expanding text content', () => {
|
|
const graph = parseVariableDocument(prefix, [
|
|
{ id: 'literal', type: 'text', content: colorRef, fontFamily: 'Barlow' },
|
|
{ id: 'bound', type: 'text', content: colorRef, fontFamily: fontRef }
|
|
])
|
|
const node = graph.getNode('bound')
|
|
const fontId = node?.boundVariables.fontFamily
|
|
|
|
expect(node?.fontFamily).toBe('Barlow')
|
|
expect(node?.fontFamily).toBe(graph.getNode('literal')?.fontFamily)
|
|
expect(node?.text).toBe(colorRef)
|
|
expect(fontId).toBeDefined()
|
|
expect(graph.variables.get(fontId ?? '')?.name).toBe(`${prefix}font-tekst`)
|
|
expect(graph.getNode('literal')?.boundVariables).toEqual({})
|
|
})
|
|
|
|
test('resolves numeric references in sizing, spacing, padding, and corners', () => {
|
|
const graph = parseVariableDocument(prefix, [
|
|
{
|
|
id: 'frame',
|
|
type: 'frame',
|
|
layout: 'horizontal',
|
|
width: sizeRef,
|
|
height: sizeRef,
|
|
gap: spacingRef,
|
|
padding: spacingRef,
|
|
cornerRadius: spacingRef
|
|
}
|
|
])
|
|
|
|
expect(graph.getNode('frame')).toMatchObject({
|
|
width: 240,
|
|
height: 240,
|
|
itemSpacing: 24,
|
|
paddingTop: 24,
|
|
paddingRight: 24,
|
|
paddingBottom: 24,
|
|
paddingLeft: 24,
|
|
cornerRadius: 24
|
|
})
|
|
})
|
|
|
|
test('uses existing fallbacks without binding unknown variables', () => {
|
|
const unknown = `$${prefix}unknown`
|
|
const graph = parseVariableDocument(prefix, [
|
|
{ id: 'frame', type: 'frame', fill: unknown, width: unknown },
|
|
{ id: 'text', type: 'text', fontFamily: unknown }
|
|
])
|
|
|
|
expect(graph.getNode('frame')).toMatchObject({
|
|
fills: [{ color: { r: 0, g: 0, b: 0, a: 1 } }],
|
|
width: 0,
|
|
boundVariables: {}
|
|
})
|
|
expect(graph.getNode('text')).toMatchObject({ fontFamily: '', boundVariables: {} })
|
|
})
|
|
})
|