import { describe, expect, test } from 'bun:test' import { valibotSchema } from '@ai-sdk/valibot' import { ALL_TOOLS, FigmaAPI, SceneGraph, toolsToAI } from '@open-pencil/core' import { tool } from 'ai' import * as v from 'valibot' function setup() { const graph = new SceneGraph() const figma = new FigmaAPI(graph) const tools = toolsToAI( ALL_TOOLS, { getFigma: () => figma, onAfterExecute: () => {} }, { v, valibotSchema, tool } ) return { graph, figma, tools } } describe('AI adapter', () => { test('generates tool for every definition', () => { const { tools } = setup() for (const def of ALL_TOOLS) { expect(tools[def.name]).toBeDefined() } expect(Object.keys(tools).length).toBe(ALL_TOOLS.length) }) test('each tool has description and execute', () => { const { tools } = setup() for (const [name, t] of Object.entries(tools)) { const aiTool = t as { description: string; execute: Function } expect(aiTool.description).toBeTruthy() expect(typeof aiTool.execute).toBe('function') } }) test('create_shape tool works through adapter', async () => { const { tools, figma } = setup() const createShape = tools.create_shape as { execute: Function } const result = (await createShape.execute({ type: 'RECTANGLE', x: 10, y: 20, width: 100, height: 50, name: 'Test Rect' })) as any expect(result.id).toBeTruthy() expect(result.type).toBe('RECTANGLE') expect(result.name).toBe('Test Rect') const node = figma.getNodeById(result.id)! expect(node.x).toBe(10) expect(node.y).toBe(20) expect(node.width).toBe(100) }) test('set_fill tool works through adapter', async () => { const { tools, figma } = setup() const rect = figma.createRectangle() rect.resize(100, 100) const setFill = tools.set_fill as { execute: Function } await setFill.execute({ id: rect.id, color: '#00ff00' }) const fills = figma.getNodeById(rect.id)!.fills expect(fills.length).toBe(1) expect(fills[0].color.g).toBeCloseTo(1) }) test('get_page_tree tool returns structure', async () => { const { tools, figma } = setup() const frame = figma.createFrame() frame.name = 'TestFrame' frame.resize(200, 200) const rect = figma.createRectangle() rect.resize(50, 50) frame.appendChild(rect) const getTree = tools.get_page_tree as { execute: Function } const result = (await getTree.execute({})) as any expect(result.page).toBeTruthy() expect(result.children.length).toBeGreaterThan(0) }) test('onBeforeExecute and onAfterExecute are called', async () => { const graph = new SceneGraph() const figma = new FigmaAPI(graph) const calls: string[] = [] const tools = toolsToAI( ALL_TOOLS, { getFigma: () => figma, onBeforeExecute: () => calls.push('before'), onAfterExecute: () => calls.push('after') }, { v, valibotSchema, tool } ) const listPages = tools.list_pages as { execute: Function } await listPages.execute({}) expect(calls).toEqual(['before', 'after']) }) test('onAfterExecute called even on error', async () => { const graph = new SceneGraph() const figma = new FigmaAPI(graph) let afterCalled = false const tools = toolsToAI( ALL_TOOLS, { getFigma: () => figma, onAfterExecute: () => { afterCalled = true } }, { v, valibotSchema, tool } ) const evalTool = tools.eval as { execute: Function } try { await evalTool.execute({ code: 'throw new Error("test")' }) } catch { // expected } expect(afterCalled).toBe(true) }) test('find_nodes works through adapter', async () => { const { tools, figma } = setup() figma.createRectangle().name = 'Button' figma.createText().name = 'Label' figma.createRectangle().name = 'Button Secondary' const findNodes = tools.find_nodes as { execute: Function } const result = (await findNodes.execute({ name: 'button' })) as any expect(result.count).toBe(2) }) test('set_layout works through adapter', async () => { const { tools, figma } = setup() const frame = figma.createFrame() frame.resize(300, 200) const setLayout = tools.set_layout as { execute: Function } await setLayout.execute({ id: frame.id, direction: 'HORIZONTAL', spacing: 8, padding: 16 }) const node = figma.getNodeById(frame.id)! expect(node.layoutMode).toBe('HORIZONTAL') expect(node.itemSpacing).toBe(8) expect(node.paddingLeft).toBe(16) }) test('render JSX works through adapter', async () => { const { tools } = setup() const render = tools.render as { execute: Function } const result = (await render.execute({ jsx: 'Hello' })) as any expect(result.name).toBe('Card') expect(result.type).toBe('FRAME') }) test('delete + get returns error for removed node', async () => { const { tools, figma } = setup() const rect = figma.createRectangle() const id = rect.id const deleteTool = tools.delete_node as { execute: Function } await deleteTool.execute({ id }) const getNode = tools.get_node as { execute: Function } const result = (await getNode.execute({ id })) as any expect(result.error).toContain('not found') }) })