import { describe, expect, test } from 'bun:test' import { FigmaAPI } from '@open-pencil/core' import { getTool, setupToolTest, type ALL_TOOLS, type ToolResult } from '#tests/helpers/tools' describe('find_nodes', () => { test('finds by name', () => { const { figma } = setupToolTest() const rect = figma.createRectangle() rect.name = 'Button Primary' const text = figma.createText() text.name = 'Label' const tool = getTool('find_nodes') const result = tool.execute(figma, { name: 'button' }) as ToolResult expect(result.count).toBe(1) expect(result.nodes[0].name).toBe('Button Primary') }) test('finds by type', () => { const { figma } = setupToolTest() figma.createRectangle() figma.createRectangle() figma.createText() const tool = getTool('find_nodes') const result = tool.execute(figma, { type: 'RECTANGLE' }) as ToolResult expect(result.count).toBe(2) }) }) describe('query_nodes', () => { test('finds all frames with //FRAME', async () => { const { figma } = setupToolTest() const f1 = figma.createFrame() f1.resize(200, 200) f1.name = 'Frame A' const f2 = figma.createFrame() f2.resize(300, 300) f2.name = 'Frame B' figma.createRectangle() const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '//FRAME' })) as ToolResult expect(result.count).toBe(2) expect(result.nodes?.every((n) => n.type === 'FRAME')).toBe(true) }) test('finds by attribute //RECTANGLE[@width < 200]', async () => { const { figma } = setupToolTest() const small = figma.createRectangle() small.resize(100, 50) small.name = 'Small' const big = figma.createRectangle() big.resize(400, 400) big.name = 'Big' const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '//RECTANGLE[@width < 200]' })) as ToolResult expect(result.count).toBe(1) expect(result.nodes[0].name).toBe('Small') }) test('finds by name with contains', async () => { const { figma } = setupToolTest() const t1 = figma.createText() t1.name = 'Label Primary' const t2 = figma.createText() t2.name = 'Title' const t3 = figma.createText() t3.name = 'Label Secondary' const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '//TEXT[contains(@name, "Label")]' })) as ToolResult expect(result.count).toBe(2) expect(result.nodes?.every((n) => n.name.includes('Label'))).toBe(true) }) test('returns error for invalid xpath', async () => { const { figma } = setupToolTest() const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '///invalid[[[[' })) as ToolResult expect(result.error).toBeTruthy() expect(result.error).toContain('XPath error') }) test('respects limit param', async () => { const { figma } = setupToolTest() for (let i = 0; i < 10; i++) { const r = figma.createRectangle() r.name = `Rect ${i}` } const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '//RECTANGLE', limit: 3 })) as ToolResult expect(result.count).toBe(3) }) test('returns empty array when nothing matches', async () => { const { figma } = setupToolTest() figma.createRectangle() const tool = getTool('query_nodes') const result = (await tool.execute(figma, { selector: '//ELLIPSE' })) as ToolResult expect(result.count).toBe(0) expect(result.nodes).toEqual([]) }) }) describe('get_node', () => { test('returns node details', () => { const { figma } = setupToolTest() const rect = figma.createRectangle() rect.name = 'Test Rect' rect.resize(100, 50) const tool = getTool('get_node') const result = tool.execute(figma, { id: rect.id }) as ToolResult expect(result.name).toBe('Test Rect') expect(result.width).toBe(100) expect(result.height).toBe(50) }) }) describe('page tools', () => { test('list_pages returns pages', () => { const { figma } = setupToolTest() const tool = getTool('list_pages') const result = tool.execute(figma, {}) as ToolResult expect(result.pages.length).toBeGreaterThanOrEqual(1) }) test('switch_page changes page', () => { const { figma } = setupToolTest() const page2 = figma.createPage() page2.name = 'Page 2' const tool = getTool('switch_page') tool.execute(figma, { page: 'Page 2' }) expect(figma.currentPage.name).toBe('Page 2') }) test('switch_page persists across separate FigmaAPI instances (RPC simulation)', () => { const { graph } = setupToolTest() const switchPage = getTool('switch_page') const getCurrentPage = getTool('get_current_page') const createPage = getTool('create_page') let currentPageId = graph.getPages()[0].id function rpcCall(tool: (typeof ALL_TOOLS)[number], args: Record) { const figma = new FigmaAPI(graph) figma.currentPage = figma.wrapNode(currentPageId) const result = tool.execute(figma, args) if (figma.currentPageId !== currentPageId) { currentPageId = figma.currentPageId } return result } rpcCall(createPage, { name: 'Second' }) rpcCall(switchPage, { page: 'Second' }) const result = rpcCall(getCurrentPage, {}) as { id: string; name: string } expect(result.name).toBe('Second') }) }) describe('eval', () => { test('executes code with figma api', async () => { const { figma } = setupToolTest() const tool = getTool('eval') const result = await tool.execute(figma, { code: 'const r = figma.createRectangle(); r.name = "FromEval"; return r.name;' }) expect(result).toBe('FromEval') }) })