- Support vectorPaths, vectorNetwork, and async network assignment - Normalize geometry bounds and preserve winding and region paint semantics - Validate network topology and expose handle mirroring only on vectors Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
114 lines
3.6 KiB
TypeScript
114 lines
3.6 KiB
TypeScript
import { describe, test, expect } from 'bun:test'
|
|
|
|
import {
|
|
normalizeVectorNetwork,
|
|
validateVectorNetwork,
|
|
type VectorNetwork
|
|
} from '@open-pencil/core'
|
|
|
|
describe('validateVectorNetwork', () => {
|
|
test('valid network returns no errors', () => {
|
|
const network: VectorNetwork = {
|
|
vertices: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 10, y: 10 }
|
|
],
|
|
segments: [{ start: 0, end: 1, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }],
|
|
regions: []
|
|
}
|
|
expect(validateVectorNetwork(network)).toEqual([])
|
|
})
|
|
|
|
test('segments without tangents are valid (normalize handles them)', () => {
|
|
const network = {
|
|
vertices: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 10, y: 10 }
|
|
],
|
|
segments: [{ start: 0, end: 1 }],
|
|
regions: []
|
|
} as VectorNetwork
|
|
expect(validateVectorNetwork(network)).toEqual([])
|
|
})
|
|
|
|
test('rejects segment with out-of-range start index', () => {
|
|
const network = {
|
|
vertices: [{ x: 0, y: 0 }],
|
|
segments: [{ start: 0, end: 5 }],
|
|
regions: []
|
|
} as VectorNetwork
|
|
const errors = validateVectorNetwork(network)
|
|
expect(errors.length).toBe(1)
|
|
expect(errors[0]).toContain('end index 5 out of range')
|
|
})
|
|
|
|
test('rejects missing vertices array', () => {
|
|
const network = { segments: [], regions: [] } as VectorNetwork
|
|
const errors = validateVectorNetwork(network)
|
|
expect(errors[0]).toContain('vertices must be an array')
|
|
})
|
|
|
|
test('rejects vertex with non-number coordinates', () => {
|
|
const network = {
|
|
vertices: [{ x: 'a', y: 0 }],
|
|
segments: [],
|
|
regions: []
|
|
} as VectorNetwork
|
|
const errors = validateVectorNetwork(network)
|
|
expect(errors[0]).toContain('x and y must be finite numbers')
|
|
})
|
|
|
|
test('rejects non-object input without throwing', () => {
|
|
expect(validateVectorNetwork(null)).toEqual(['network must be an object'])
|
|
expect(validateVectorNetwork('not a network')).toEqual(['network must be an object'])
|
|
})
|
|
|
|
test('accepts and normalizes omitted regions like the Figma Plugin API', () => {
|
|
const network = {
|
|
vertices: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 10, y: 0 }
|
|
],
|
|
segments: [{ start: 0, end: 1 }]
|
|
}
|
|
|
|
expect(validateVectorNetwork(network)).toEqual([])
|
|
expect(normalizeVectorNetwork(network).regions).toEqual([])
|
|
})
|
|
|
|
test('rejects a non-array regions value', () => {
|
|
const errors = validateVectorNetwork({ vertices: [], segments: [], regions: {} } as never)
|
|
expect(errors).toContain('regions must be an array when provided')
|
|
})
|
|
|
|
test('rejects invalid region topology', () => {
|
|
const errors = validateVectorNetwork({
|
|
vertices: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 10, y: 0 },
|
|
{ x: 20, y: 0 },
|
|
{ x: 30, y: 0 }
|
|
],
|
|
segments: [
|
|
{ start: 0, end: 1 },
|
|
{ start: 2, end: 3 },
|
|
{ start: 1, end: 2 }
|
|
],
|
|
regions: [
|
|
{ windingRule: 'INVALID', loops: [[3]] },
|
|
{ windingRule: 'NONZERO', loops: [] },
|
|
{ windingRule: 'NONZERO', loops: [[]] },
|
|
{ windingRule: 'NONZERO', loops: [[0, 0]] },
|
|
{ windingRule: 'NONZERO', loops: [[0, 1]] }
|
|
]
|
|
})
|
|
|
|
expect(errors).toContain('region[0]: windingRule must be NONZERO or EVENODD')
|
|
expect(errors).toContain('region[0].loop[0]: segment index 3 out of range')
|
|
expect(errors).toContain('region[1]: loops must contain at least one loop')
|
|
expect(errors).toContain('region[2].loop[0] must contain at least one segment')
|
|
expect(errors).toContain('region[3].loop[0] must not repeat segments')
|
|
expect(errors).toContain('region[4].loop[0] segments must form a continuous chain')
|
|
})
|
|
})
|