Skip to main content
Glama

Claude Talk to Figma MCP

by arinspunk
set-fill-color.test.ts8.49 kB
import { z } from 'zod'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { registerModificationTools } from '../../src/talk_to_figma_mcp/tools/modification-tools'; jest.mock('../../src/talk_to_figma_mcp/utils/websocket', () => ({ sendCommandToFigma: jest.fn().mockResolvedValue({ name: "MockNode" }) })); describe("set_fill_color tool integration", () => { let server: McpServer; let mockSendCommand: jest.Mock; let toolHandler: Function; let toolSchema: z.ZodObject<any>; beforeEach(() => { server = new McpServer( { name: 'test-server', version: '1.0.0' }, { capabilities: { tools: {} } } ); mockSendCommand = require('../../src/talk_to_figma_mcp/utils/websocket').sendCommandToFigma; mockSendCommand.mockClear(); const originalTool = server.tool.bind(server); jest.spyOn(server, 'tool').mockImplementation((...args: any[]) => { if (args.length === 4) { const [name, description, schema, handler] = args; if (name === 'set_fill_color') { toolHandler = handler; toolSchema = z.object(schema); } } return (originalTool as any)(...args); }); registerModificationTools(server); }); async function callToolWithValidation(args: any) { const validatedArgs = toolSchema.parse(args); const result = await toolHandler(validatedArgs, { meta: {} }); return result; } describe("opacity handling (the critical fix)", () => { it("defaults `a` to 1 when `a` is undefined", async () => { const response = await callToolWithValidation({ nodeId: "nodeA", r: 0.2, g: 0.4, b: 0.6, // a is undefined }); expect(mockSendCommand).toHaveBeenCalledTimes(1); const [command, payload] = mockSendCommand.mock.calls[0]; expect(command).toBe("set_fill_color"); expect(payload).toEqual({ nodeId: "nodeA", color: { r: 0.2, g: 0.4, b: 0.6, a: 1 }, }); expect(response.content[0].text).toContain("RGBA(0.2, 0.4, 0.6, 1)"); }); it("preserves `a = 0` when explicitly provided (the main bug fix)", async () => { const response = await callToolWithValidation({ nodeId: "nodeB", r: 0.1, g: 0.3, b: 0.5, a: 0, // This should be preserved as 0, not converted to 1 }); expect(mockSendCommand).toHaveBeenCalledTimes(1); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.a).toBe(0); // Critical: should be 0, not 1 expect(response.content[0].text).toContain("RGBA(0.1, 0.3, 0.5, 0)"); }); it("preserves semi-transparent values", async () => { const response = await callToolWithValidation({ nodeId: "nodeC", r: 1, g: 0, b: 0, a: 0.5, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.a).toBe(0.5); expect(response.content[0].text).toContain("RGBA(1, 0, 0, 0.5)"); }); }); describe("RGB component handling", () => { it("preserves pure black (0,0,0)", async () => { await callToolWithValidation({ nodeId: "nodeD", r: 0, g: 0, b: 0, a: 1, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.r).toBe(0); expect(payload.color.g).toBe(0); expect(payload.color.b).toBe(0); }); it("preserves zero red component", async () => { await callToolWithValidation({ nodeId: "nodeE1", r: 0, g: 0.5, b: 0.8, a: 1, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.r).toBe(0); expect(payload.color.g).toBe(0.5); }); it("preserves zero green component", async () => { await callToolWithValidation({ nodeId: "nodeE2", r: 0.5, g: 0, b: 0.8, a: 1, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.g).toBe(0); }); it("preserves zero blue component", async () => { await callToolWithValidation({ nodeId: "nodeE3", r: 0.5, g: 0.8, b: 0, a: 1, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.b).toBe(0); }); }); describe("Zod validation (real validation layer)", () => { it("rejects undefined r component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF1", // r is missing g: 0.5, b: 0.8, a: 1, })).rejects.toThrow(); // WebSocket should not be called if validation fails expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects undefined g component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF2", r: 0.5, // g is missing b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects undefined b component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF3", r: 0.5, g: 0.8, // b is missing a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects string r component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF4", r: "red", // Invalid type g: 0.5, b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects null g component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF5", r: 0.5, g: null, // Invalid type b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects boolean b component", async () => { await expect(callToolWithValidation({ nodeId: "nodeF6", r: 0.5, g: 0.8, b: true, // Invalid type a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects NaN values", async () => { await expect(callToolWithValidation({ nodeId: "nodeF7", r: NaN, // Invalid value g: 0.5, b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects out-of-range values", async () => { await expect(callToolWithValidation({ nodeId: "nodeF8", r: 1.5, // Out of 0-1 range g: 0.5, b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); it("rejects negative values", async () => { await expect(callToolWithValidation({ nodeId: "nodeF9", r: -0.1, // Negative value g: 0.5, b: 0.8, a: 1, })).rejects.toThrow(); expect(mockSendCommand).not.toHaveBeenCalled(); }); }); describe("edge cases", () => { it("handles transparent black correctly", async () => { const response = await callToolWithValidation({ nodeId: "nodeG", r: 0, g: 0, b: 0, a: 0, // Transparent black }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color).toEqual({ r: 0, g: 0, b: 0, a: 0 }); expect(response.content[0].text).toContain("RGBA(0, 0, 0, 0)"); }); it("handles boundary values", async () => { const response = await callToolWithValidation({ nodeId: "nodeH", r: 1, g: 1, b: 1, a: 1, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color).toEqual({ r: 1, g: 1, b: 1, a: 1 }); }); it("accepts valid decimal values", async () => { await callToolWithValidation({ nodeId: "nodeI", r: 0.123, g: 0.456, b: 0.789, a: 0.5, }); const [command, payload] = mockSendCommand.mock.calls[0]; expect(payload.color.r).toBeCloseTo(0.123); expect(payload.color.g).toBeCloseTo(0.456); expect(payload.color.b).toBeCloseTo(0.789); expect(payload.color.a).toBeCloseTo(0.5); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/arinspunk/claude-talk-to-figma-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server