openpencil/packages/fig/tests/document/bindings/expression.test.ts
Danila Poyarkov 2101279e1d
refactor(fig): group prefixed siblings into folders
* refactor(tools): group the oracle captures and mirror the bindings tests

Two names broke the rule that a multi-file domain gets a subfolder
rather than repeated sibling prefixes. `document-capture.ts` repeated
its own folder and sat beside `capture-scene.ts`, so both become
`document/capture/{document,scene}.ts` behind `#visual/capture/*`.

The document bindings tests were split between `document/binding/` and
a `text-bindings.test.ts` one level above, while their source is
`document/bindings/`. They move into `document/bindings/` and take the
source file names, so the test tree mirrors it.

* refactor(fig): group the node-change prefix families into folders

`node-change/` held five pairs of files sharing a leading segment —
derived-text, style, text, variable, vector — plus `instance-geometry`
beside an `instance-linkage/` folder. The guide asks for a subfolder
rather than a repeated prefix, so each becomes one: `style/{refs,runs}`,
`variable/{bindings,expression}`, and so on, with `instance-linkage/`
folded in beside `instance/geometry.ts`.

Imports across seventeen files follow. Two of the moved files reached
out of the package tree with `../../` once they were a level deeper;
those use `#fig/*` instead.
2026-10-01 16:52:40 +04:00

73 lines
2.3 KiB
TypeScript

import { expect, test } from 'bun:test'
import { linearVariableExpression } from '#fig/node-change/variable/expression'
import { nodeChangeToProps } from '@open-pencil/fig/node-change'
import type { VariableDataEntry } from '@open-pencil/kiwi/fig/codec'
const alias: VariableDataEntry = {
dataType: 'ALIAS',
value: { alias: { guid: { sessionID: 1, localID: 2 } } }
}
const literal = (floatValue: number): VariableDataEntry => ({
dataType: 'FLOAT',
value: { floatValue }
})
const multiply = (...expressionArguments: VariableDataEntry[]): VariableDataEntry => ({
dataType: 'EXPRESSION',
value: { expressionValue: { expressionFunction: 'MULTIPLY', expressionArguments } }
})
test('linear products retain their symbolic alias and exact coefficient', () => {
expect(linearVariableExpression(multiply(literal(2), multiply(alias, literal(0.25))))).toEqual({
reference: alias.value?.alias,
multiplier: 0.5
})
const props = nodeChangeToProps(
{
type: 'FRAME',
parameterConsumptionMap: {
entries: [
{
variableField: 'OPACITY',
variableData: multiply(literal(0.5), alias)
}
]
}
},
[]
)
expect(props.boundVariables).toEqual({ opacity: '1:2' })
expect(props.variableBindingScales).toEqual({ opacity: 0.005 })
})
test('nonlinear, unsupported, overflowing, and unbounded expressions fail rather than lose bindings', () => {
expect(() => linearVariableExpression(multiply(alias, alias))).toThrow('one alias')
expect(() => linearVariableExpression({ dataType: 'EXPRESSION' })).toThrow('Missing')
expect(() =>
nodeChangeToProps(
{
type: 'FRAME',
parameterConsumptionMap: {
entries: [
{
variableField: 'WIDTH',
variableData: multiply(literal(2), literal(3))
}
]
}
},
[]
)
).toThrow('one alias')
expect(() =>
linearVariableExpression({ value: { expressionValue: { expressionFunction: 'ADD' } } })
).toThrow('Unsupported')
expect(() =>
linearVariableExpression(multiply(literal(Number.MAX_VALUE), literal(2), alias))
).toThrow('non-finite')
let deep = alias
for (let index = 0; index < 66; index++) deep = multiply(literal(1), deep)
expect(() => linearVariableExpression(deep)).toThrow('maximum depth')
})