Define native Valibot inputs and execution/exposure metadata on each tool. Derive effects and default capabilities, consume upstream Standard Schema conversion, and validate finite numeric inputs consistently across adapters. Move atomic execution to Core and restore failures from Scene Graph checkpoints without relying on a property diff. Preserve topology, collections, indexes and surviving object identities during rollback. BREAKING CHANGE: custom tools use input schemas and execution metadata instead of params, ParamDef and independently declared mutation flags. Direct tool execution validates inputs before invoking the handler.
56 lines
2.4 KiB
TypeScript
56 lines
2.4 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
// eslint-disable-next-line open-pencil/no-mixed-case-acronym-identifiers -- Upstream export spelling.
|
|
import { toStandardJsonSchema as toStandardJSONSchema } from '@valibot/to-json-schema'
|
|
import * as v from 'valibot'
|
|
|
|
import { toolNumber } from '@open-pencil/core/tools'
|
|
|
|
import { getTool, setupToolTest } from '#tests/helpers/tools'
|
|
|
|
describe('native tool input contracts', () => {
|
|
const schema = v.object({ value: toolNumber(v.pipe(v.number(), v.minValue(0), v.maxValue(100))) })
|
|
|
|
test.each([Infinity, -Infinity, Number.NaN, 'Infinity', '-Infinity', 'NaN', '1e999', 'abc'])(
|
|
'rejects non-finite or invalid numeric input %s',
|
|
async (value) => {
|
|
expect(v.safeParse(schema, { value }).success).toBe(false)
|
|
const standard = toStandardJSONSchema(schema)
|
|
expect((await standard['~standard'].validate({ value })).issues).toBeDefined()
|
|
}
|
|
)
|
|
|
|
test('numeric strings share numeric bounds and output types', () => {
|
|
expect(v.parse(schema, { value: '42' })).toEqual({ value: 42 })
|
|
expect(v.safeParse(schema, { value: '101' }).success).toBe(false)
|
|
expect(v.safeParse(schema, { value: '-1' }).success).toBe(false)
|
|
})
|
|
|
|
test('JSON Schema advertises canonical numeric bounds and the string compatibility input', () => {
|
|
const standard = toStandardJSONSchema(schema)
|
|
expect(standard['~standard'].jsonSchema.input({ target: 'draft-07' })).toMatchObject({
|
|
properties: {
|
|
value: { anyOf: [{ type: 'number', minimum: 0, maximum: 100 }, { type: 'string' }] }
|
|
}
|
|
})
|
|
})
|
|
|
|
test('direct execution validates and normalizes the same declared schema', () => {
|
|
const { figma } = setupToolTest()
|
|
const node = figma.createRectangle()
|
|
const tool = getTool('set_opacity')
|
|
tool.execute(figma, { id: node.id, value: '0.5' })
|
|
expect(node.opacity).toBe(0.5)
|
|
expect(() => tool.execute(figma, { id: node.id, value: 'Infinity' })).toThrow()
|
|
expect(node.opacity).toBe(0.5)
|
|
})
|
|
|
|
test('defaults and enum aliases remain schema-owned', () => {
|
|
const tool = getTool('create_shape')
|
|
const args = v.parse(tool.input, { type: 'RECTANGLE', x: 0, y: 0, width: 10, height: 10 })
|
|
expect(args.type).toBe('RECTANGLE')
|
|
expect(v.parse(getTool('get_components').input, {}).source).toBe('all')
|
|
expect(v.safeParse(tool.input, { type: 'not-a-shape' }).success).toBe(false)
|
|
})
|
|
})
|