Spaces:
Running
Running
| import { describe, test, expect, beforeEach } from "bun:test"; | |
| import { | |
| mcpClientManager, | |
| setMCPWebSocketConnection, | |
| } from "../src/lib/server/mcp-client"; | |
| import { | |
| updateEditorContent, | |
| observeConsoleTool, | |
| } from "../src/lib/server/tools"; | |
| import { consoleBuffer } from "../src/lib/server/console-buffer"; | |
| import type { WebSocket } from "ws"; | |
| describe("MCP Editor Tools", () => { | |
| const defaultContent = `<canvas id="game-canvas"></canvas> | |
| <world canvas="#game-canvas"> | |
| <static-part pos="0 0 0" shape="box"></static-part> | |
| </world>`; | |
| let tools: ReturnType<typeof mcpClientManager.getTools>; | |
| beforeEach(async () => { | |
| updateEditorContent(defaultContent); | |
| consoleBuffer.clear(); | |
| await mcpClientManager.initialize(); | |
| tools = mcpClientManager.getTools(); | |
| }); | |
| test("reads complete editor content", async () => { | |
| const readTool = tools.find((t) => t.name === "read_editor"); | |
| expect(readTool).toBeDefined(); | |
| const result = await readTool!.func({}); | |
| expect(result).toContain("game-canvas"); | |
| expect(result).toContain("static-part"); | |
| }); | |
| test("reads specific lines from editor", async () => { | |
| const readLinesTool = tools.find((t) => t.name === "read_editor_lines"); | |
| expect(readLinesTool).toBeDefined(); | |
| const result = await readLinesTool!.func({ startLine: 2, endLine: 3 }); | |
| expect(result).toContain("Lines 2-3"); | |
| expect(result).toContain("world"); | |
| expect(result).toContain("static-part"); | |
| }); | |
| test("handles line reading out of bounds", async () => { | |
| const readLinesTool = tools.find((t) => t.name === "read_editor_lines"); | |
| const result = await readLinesTool!.func({ startLine: 999 }); | |
| expect(result).toContain("Error:"); | |
| expect(result).toContain("exceeds total lines"); | |
| }); | |
| test("searches for text in editor", async () => { | |
| const searchTool = tools.find((t) => t.name === "search_editor"); | |
| expect(searchTool).toBeDefined(); | |
| const result = await searchTool!.func({ query: "canvas" }); | |
| expect(result).toContain("Found"); | |
| expect(result).toContain("canvas"); | |
| expect(result).toContain("game-canvas"); | |
| }); | |
| test("handles search with no results", async () => { | |
| const searchTool = tools.find((t) => t.name === "search_editor"); | |
| const result = await searchTool!.func({ query: "nonexistent" }); | |
| expect(result).toContain("No matches found"); | |
| }); | |
| test("edits existing content", async () => { | |
| const editTool = tools.find((t) => t.name === "edit_editor"); | |
| const readTool = tools.find((t) => t.name === "read_editor"); | |
| expect(editTool).toBeDefined(); | |
| await editTool!.func({ | |
| oldText: 'shape="box"', | |
| newText: 'shape="sphere"', | |
| }); | |
| const content = await readTool!.func({}); | |
| expect(content).toContain("sphere"); | |
| expect(content).not.toContain('shape="box"'); | |
| }); | |
| test("handles edit with non-matching text", async () => { | |
| const editTool = tools.find((t) => t.name === "edit_editor"); | |
| const result = await editTool!.func({ | |
| oldText: "nonexistent", | |
| newText: "replacement", | |
| }); | |
| expect(result).toContain("Error"); | |
| }); | |
| test("writes complete new content", async () => { | |
| const writeTool = tools.find((t) => t.name === "write_editor"); | |
| const readTool = tools.find((t) => t.name === "read_editor"); | |
| expect(writeTool).toBeDefined(); | |
| const newContent = "<div>New content</div>"; | |
| await writeTool!.func({ content: newContent }); | |
| const result = await readTool!.func({}); | |
| expect(result).toContain("New content"); | |
| expect(result).not.toContain("game-canvas"); | |
| }); | |
| }); | |
| describe("Console Tool", () => { | |
| beforeEach(() => { | |
| consoleBuffer.clear(); | |
| }); | |
| test("observes empty console", async () => { | |
| const result = await observeConsoleTool.func(""); | |
| expect(result).toContain("No new console messages"); | |
| }); | |
| test("observes console messages", async () => { | |
| consoleBuffer.addMessage({ | |
| id: "1", | |
| type: "log", | |
| message: "Test message", | |
| timestamp: Date.now(), | |
| }); | |
| const result = await observeConsoleTool.func(""); | |
| expect(result).toContain("Test message"); | |
| expect(result).toContain("[log]"); | |
| }); | |
| test("marks messages as read", async () => { | |
| consoleBuffer.addMessage({ | |
| id: "1", | |
| type: "error", | |
| message: "Error occurred", | |
| timestamp: Date.now(), | |
| }); | |
| await observeConsoleTool.func(""); | |
| const secondRead = await observeConsoleTool.func(""); | |
| expect(secondRead).toContain("No new console messages"); | |
| }); | |
| test("detects game state from console", async () => { | |
| consoleBuffer.addMessage({ | |
| id: "1", | |
| type: "log", | |
| message: "🎮 Starting game", | |
| timestamp: Date.now(), | |
| }); | |
| const result = await observeConsoleTool.func(""); | |
| expect(result).toContain("Loading: true"); | |
| consoleBuffer.addMessage({ | |
| id: "2", | |
| type: "log", | |
| message: "✅ Game started", | |
| timestamp: Date.now() + 100, | |
| }); | |
| const result2 = await observeConsoleTool.func(""); | |
| expect(result2).toContain("Ready: true"); | |
| }); | |
| }); | |
| describe("MCP WebSocket Connection", () => { | |
| test("sets WebSocket connection for MCP client", () => { | |
| const mockWs = { | |
| readyState: 1, // OPEN | |
| OPEN: 1, | |
| send: (data: string) => { | |
| const message = JSON.parse(data); | |
| expect(message.type).toBeDefined(); | |
| expect(message.payload).toBeDefined(); | |
| }, | |
| } as unknown as WebSocket; | |
| setMCPWebSocketConnection(mockWs); | |
| }); | |
| }); | |