Skip to main content
Glama
1yhy
by 1yhy
figma-resources.test.ts16.8 kB
/** * Figma Resources Unit Tests * * Tests the resource handlers that extract lightweight data from Figma files. */ import { describe, it, expect, vi } from "vitest"; import { getFileMetadata, getStyleTokens, getComponentList, getAssetList, createFileMetadataTemplate, createStylesTemplate, createComponentsTemplate, createAssetsTemplate, FIGMA_MCP_HELP, } from "~/resources/figma-resources.js"; import type { SimplifiedDesign, SimplifiedNode } from "~/types/index.js"; import type { FigmaService } from "~/services/figma.js"; // ==================== Mock Types ==================== type MockFigmaService = Pick< FigmaService, "getFile" | "getNode" | "getImages" | "getImageFills" | "getRateLimitInfo" >; // ==================== Mock Data ==================== const createMockNode = (overrides: Partial<SimplifiedNode> = {}): SimplifiedNode => ({ id: "node-1", name: "Test Node", type: "FRAME", ...overrides, }); const createMockDesign = (overrides: Partial<SimplifiedDesign> = {}): SimplifiedDesign => ({ name: "Test Design", lastModified: "2024-01-15T10:30:00Z", thumbnailUrl: "https://figma.com/thumbnail.png", nodes: [], ...overrides, }); // Mock FigmaService const createMockFigmaService = (design: SimplifiedDesign): MockFigmaService => ({ getFile: vi.fn().mockResolvedValue(design), getNode: vi.fn().mockResolvedValue(design), getImages: vi.fn().mockResolvedValue([]), getImageFills: vi.fn().mockResolvedValue([]), getRateLimitInfo: vi.fn().mockReturnValue(null), }); // ==================== Tests ==================== describe("Figma Resources", () => { describe("getFileMetadata", () => { it("should extract basic file metadata", async () => { const mockDesign = createMockDesign({ name: "My Design File", lastModified: "2024-03-20T15:00:00Z", nodes: [ createMockNode({ id: "page-1", name: "Page 1", type: "CANVAS", children: [createMockNode(), createMockNode()], }), createMockNode({ id: "page-2", name: "Page 2", type: "CANVAS", children: [createMockNode()], }), ], }); const mockService = createMockFigmaService(mockDesign); const metadata = await getFileMetadata(mockService as FigmaService, "test-file-key"); expect(metadata.name).toBe("My Design File"); expect(metadata.lastModified).toBe("2024-03-20T15:00:00Z"); expect(metadata.pages).toHaveLength(2); expect(metadata.pages[0]).toEqual({ id: "page-1", name: "Page 1", childCount: 2, }); expect(metadata.pages[1]).toEqual({ id: "page-2", name: "Page 2", childCount: 1, }); }); it("should call getFile with depth 1", async () => { const mockDesign = createMockDesign(); const mockService = createMockFigmaService(mockDesign); await getFileMetadata(mockService as FigmaService, "test-key"); expect(mockService.getFile).toHaveBeenCalledWith("test-key", 1); }); it("should handle files with no pages", async () => { const mockDesign = createMockDesign({ nodes: [] }); const mockService = createMockFigmaService(mockDesign); const metadata = await getFileMetadata(mockService as FigmaService, "test-key"); expect(metadata.pages).toHaveLength(0); }); it("should filter out non-CANVAS nodes from pages", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "page-1", name: "Page", type: "CANVAS" }), createMockNode({ id: "frame-1", name: "Frame", type: "FRAME" }), createMockNode({ id: "page-2", name: "Page 2", type: "CANVAS" }), ], }); const mockService = createMockFigmaService(mockDesign); const metadata = await getFileMetadata(mockService as FigmaService, "test-key"); expect(metadata.pages).toHaveLength(2); expect(metadata.pages.every((p) => p.name.startsWith("Page"))).toBe(true); }); }); describe("getStyleTokens", () => { it("should extract colors from node CSS", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Primary Button", cssStyles: { backgroundColor: "#24C790", color: "#FFFFFF", }, }), ], }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); expect(styles.colors.length).toBeGreaterThan(0); expect(styles.colors.some((c) => c.hex === "#24C790")).toBe(true); }); it("should extract typography from node CSS", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Heading", cssStyles: { fontFamily: "Inter", fontSize: "24px", fontWeight: "700", lineHeight: "32px", }, }), ], }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); expect(styles.typography.length).toBeGreaterThan(0); expect(styles.typography[0]).toMatchObject({ fontFamily: "Inter", fontSize: 24, fontWeight: 700, }); }); it("should extract shadow effects", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Card", cssStyles: { boxShadow: "0px 4px 8px rgba(0, 0, 0, 0.1)", }, }), ], }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); expect(styles.effects.length).toBeGreaterThan(0); expect(styles.effects[0].type).toBe("shadow"); }); it("should deduplicate colors", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ cssStyles: { backgroundColor: "#FF0000" } }), createMockNode({ cssStyles: { backgroundColor: "#FF0000" } }), createMockNode({ cssStyles: { backgroundColor: "#00FF00" } }), ], }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); const redColors = styles.colors.filter((c) => c.hex === "#FF0000"); expect(redColors.length).toBe(1); }); it("should limit results to avoid token bloat", async () => { // Create many nodes with unique colors const nodes = Array.from({ length: 50 }, (_, i) => createMockNode({ name: `Node ${i}`, cssStyles: { backgroundColor: `#${i.toString(16).padStart(6, "0")}` }, }), ); const mockDesign = createMockDesign({ nodes }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); expect(styles.colors.length).toBeLessThanOrEqual(20); }); it("should recursively extract from children", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Parent", children: [ createMockNode({ name: "Child", cssStyles: { backgroundColor: "#AABBCC" }, }), ], }), ], }); const mockService = createMockFigmaService(mockDesign); const styles = await getStyleTokens(mockService as FigmaService, "test-key"); expect(styles.colors.some((c) => c.hex === "#AABBCC")).toBe(true); }); }); describe("getComponentList", () => { it("should find COMPONENT nodes", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "comp-1", name: "Button", type: "COMPONENT", }), ], }); const mockService = createMockFigmaService(mockDesign); const components = await getComponentList(mockService as FigmaService, "test-key"); expect(components).toHaveLength(1); expect(components[0]).toMatchObject({ id: "comp-1", name: "Button", type: "COMPONENT", }); }); it("should find COMPONENT_SET nodes with variants", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "set-1", name: "Button", type: "COMPONENT_SET", children: [ createMockNode({ name: "Primary" }), createMockNode({ name: "Secondary" }), createMockNode({ name: "Outline" }), ], }), ], }); const mockService = createMockFigmaService(mockDesign); const components = await getComponentList(mockService as FigmaService, "test-key"); expect(components).toHaveLength(1); expect(components[0].type).toBe("COMPONENT_SET"); expect(components[0].variants).toEqual(["Primary", "Secondary", "Outline"]); }); it("should limit variants to 5", async () => { const variants = Array.from({ length: 10 }, (_, i) => createMockNode({ name: `Variant ${i}` }), ); const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "set-1", name: "Button", type: "COMPONENT_SET", children: variants, }), ], }); const mockService = createMockFigmaService(mockDesign); const components = await getComponentList(mockService as FigmaService, "test-key"); expect(components[0].variants).toHaveLength(5); }); it("should find nested components", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Page", type: "CANVAS", children: [ createMockNode({ name: "Components", type: "FRAME", children: [ createMockNode({ id: "c1", name: "Button", type: "COMPONENT" }), createMockNode({ id: "c2", name: "Input", type: "COMPONENT" }), ], }), ], }), ], }); const mockService = createMockFigmaService(mockDesign); const components = await getComponentList(mockService as FigmaService, "test-key"); expect(components).toHaveLength(2); }); it("should limit to 50 components", async () => { const nodes = Array.from({ length: 60 }, (_, i) => createMockNode({ id: `comp-${i}`, name: `Component ${i}`, type: "COMPONENT", }), ); const mockDesign = createMockDesign({ nodes }); const mockService = createMockFigmaService(mockDesign); const components = await getComponentList(mockService as FigmaService, "test-key"); expect(components.length).toBeLessThanOrEqual(50); }); }); describe("getAssetList", () => { it("should find nodes with exportInfo", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "icon-1", name: "arrow-right", type: "VECTOR", exportInfo: { type: "IMAGE", format: "SVG" }, }), ], }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets).toHaveLength(1); expect(assets[0]).toMatchObject({ nodeId: "icon-1", name: "arrow-right", type: "icon", exportFormats: ["SVG"], }); }); it("should identify icons by type VECTOR", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "icon-1", name: "small-icon", type: "VECTOR", exportInfo: { type: "IMAGE", format: "SVG" }, }), ], }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets[0].type).toBe("icon"); }); it("should identify large exports as vector type", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "illustration-1", name: "hero-image", type: "FRAME", exportInfo: { type: "IMAGE_GROUP", format: "SVG" }, }), ], }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets[0].type).toBe("vector"); }); it("should find nodes with image fills", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ id: "img-1", name: "photo", type: "RECTANGLE", fills: [{ type: "IMAGE", imageRef: "img:abc123" }], }), ], }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets).toHaveLength(1); expect(assets[0]).toMatchObject({ nodeId: "img-1", name: "photo", type: "image", imageRef: "img:abc123", }); }); it("should find nested assets", async () => { const mockDesign = createMockDesign({ nodes: [ createMockNode({ name: "Card", children: [ createMockNode({ id: "icon", name: "icon", type: "VECTOR", exportInfo: { type: "IMAGE", format: "SVG" }, }), createMockNode({ id: "image", name: "thumbnail", fills: [{ type: "IMAGE", imageRef: "img:xyz" }], }), ], }), ], }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets).toHaveLength(2); }); it("should limit to 100 assets", async () => { const nodes = Array.from({ length: 150 }, (_, i) => createMockNode({ id: `asset-${i}`, name: `Asset ${i}`, type: "VECTOR", exportInfo: { type: "IMAGE", format: "SVG" }, }), ); const mockDesign = createMockDesign({ nodes }); const mockService = createMockFigmaService(mockDesign); const assets = await getAssetList(mockService as FigmaService, "test-key"); expect(assets.length).toBeLessThanOrEqual(100); }); }); describe("Resource Templates", () => { it("should create file metadata template with correct URI pattern", () => { const template = createFileMetadataTemplate(); expect(template.uriTemplate.toString()).toBe("figma://file/{fileKey}"); }); it("should create styles template with correct URI pattern", () => { const template = createStylesTemplate(); expect(template.uriTemplate.toString()).toBe("figma://file/{fileKey}/styles"); }); it("should create components template with correct URI pattern", () => { const template = createComponentsTemplate(); expect(template.uriTemplate.toString()).toBe("figma://file/{fileKey}/components"); }); it("should create assets template with correct URI pattern", () => { const template = createAssetsTemplate(); expect(template.uriTemplate.toString()).toBe("figma://file/{fileKey}/assets"); }); }); describe("Help Content", () => { it("should contain resource documentation", () => { expect(FIGMA_MCP_HELP).toContain("figma://file/{fileKey}"); expect(FIGMA_MCP_HELP).toContain("figma://file/{fileKey}/styles"); expect(FIGMA_MCP_HELP).toContain("figma://file/{fileKey}/components"); expect(FIGMA_MCP_HELP).toContain("figma://file/{fileKey}/assets"); }); it("should explain token costs", () => { expect(FIGMA_MCP_HELP).toContain("Token cost"); }); it("should explain how to get fileKey", () => { expect(FIGMA_MCP_HELP).toContain("fileKey"); expect(FIGMA_MCP_HELP).toContain("figma.com"); }); }); });

Latest Blog Posts

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/1yhy/Figma-Context-MCP'

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