ToolResultRenderer.test.tsx (6249B)
1 import { describe, it, expect, beforeAll } from "vitest"; 2 import { render } from "@testing-library/react"; 3 import { detectContentType, extractPreamble, getOperationCategory, ToolResultRenderer } from "./ToolResultRenderer"; 4 5 // Mock canvas for @git-diff-view/react 6 beforeAll(() => { 7 HTMLCanvasElement.prototype.getContext = vi.fn(() => ({ 8 measureText: vi.fn(() => ({ width: 0 })), 9 fillText: vi.fn(), 10 fillRect: vi.fn(), 11 clearRect: vi.fn(), 12 createImageData: vi.fn(), 13 putImageData: vi.fn(), 14 getImageData: vi.fn(() => ({ data: [] })), 15 setTransform: vi.fn(), 16 resetTransform: vi.fn(), 17 drawImage: vi.fn(), 18 save: vi.fn(), 19 restore: vi.fn(), 20 beginPath: vi.fn(), 21 closePath: vi.fn(), 22 moveTo: vi.fn(), 23 lineTo: vi.fn(), 24 clip: vi.fn(), 25 stroke: vi.fn(), 26 fill: vi.fn(), 27 rect: vi.fn(), 28 arc: vi.fn(), 29 font: "", 30 fillStyle: "", 31 strokeStyle: "", 32 lineWidth: 1, 33 globalAlpha: 1, 34 textAlign: "start" as CanvasTextAlign, 35 textBaseline: "alphabetic" as CanvasTextBaseline, 36 })) as any; 37 }); 38 39 describe("detectContentType", () => { 40 it("detects pure JSON object", () => { 41 expect(detectContentType('{"key": "value", "count": 42}')).toBe("json"); 42 }); 43 44 it("detects pure JSON array", () => { 45 expect(detectContentType("[1, 2, 3]")).toBe("json"); 46 }); 47 48 it("detects JSON with short preamble", () => { 49 const content = 'Here are the results:\n{"status": "ok", "count": 5}'; 50 expect(detectContentType(content)).toBe("json"); 51 }); 52 53 it("detects unified diff", () => { 54 const diff = `--- a/file.ts 55 +++ b/file.ts 56 @@ -1,3 +1,4 @@ 57 line1 58 +added line 59 line2 60 line3`; 61 expect(detectContentType(diff)).toBe("diff"); 62 }); 63 64 it("detects diff even with curly braces in code", () => { 65 const diff = `--- a/file.ts 66 +++ b/file.ts 67 @@ -1,3 +1,4 @@ 68 const x = { 69 key: "value" 70 }; 71 +// new comment`; 72 expect(detectContentType(diff)).toBe("diff"); 73 }); 74 75 it("returns markdown for plain text", () => { 76 expect(detectContentType("Hello world, this is plain text.")).toBe("markdown"); 77 }); 78 79 it("returns markdown for malformed JSON", () => { 80 expect(detectContentType("{not valid json")).toBe("markdown"); 81 }); 82 83 it("returns markdown for empty string", () => { 84 expect(detectContentType("")).toBe("markdown"); 85 }); 86 87 it("returns markdown for fenced code blocks", () => { 88 const content = '```javascript\nconst x = 1;\n```'; 89 expect(detectContentType(content)).toBe("markdown"); 90 }); 91 92 it("handles JSON with nested objects", () => { 93 const json = '{"outer": {"inner": {"deep": true}}}'; 94 expect(detectContentType(json)).toBe("json"); 95 }); 96 97 it("handles JSON with preamble text containing newlines", () => { 98 const content = 'Ran the health check.\nResults:\n{"status": "healthy", "uptime": 999}'; 99 expect(detectContentType(content)).toBe("json"); 100 }); 101 }); 102 103 describe("extractPreamble", () => { 104 it("returns empty string when content is pure JSON", () => { 105 const result = extractPreamble('{"key": "value"}'); 106 expect(result.preamble).toBe(""); 107 expect(result.jsonContent).toBe('{"key": "value"}'); 108 }); 109 110 it("extracts preamble before JSON", () => { 111 const content = 'Here are the results:\n{"status": "ok"}'; 112 const result = extractPreamble(content); 113 expect(result.preamble).toBe("Here are the results:"); 114 expect(result.jsonContent).toBe('{"status": "ok"}'); 115 }); 116 117 it("returns full content as preamble when no JSON found", () => { 118 const result = extractPreamble("Just plain text"); 119 expect(result.preamble).toBe("Just plain text"); 120 expect(result.jsonContent).toBeNull(); 121 }); 122 123 it("handles empty string", () => { 124 const result = extractPreamble(""); 125 expect(result.preamble).toBe(""); 126 expect(result.jsonContent).toBeNull(); 127 }); 128 }); 129 130 describe("getOperationCategory", () => { 131 it("returns READ for read_file", () => { 132 const result = getOperationCategory("read_file"); 133 expect(result.label).toBe("READ"); 134 expect(result.color).toBe("blue"); 135 }); 136 137 it("returns WRITE for write_file", () => { 138 const result = getOperationCategory("write_file"); 139 expect(result.label).toBe("WRITE"); 140 expect(result.color).toBe("green"); 141 }); 142 143 it("returns PATCH for patch", () => { 144 const result = getOperationCategory("patch"); 145 expect(result.label).toBe("PATCH"); 146 expect(result.color).toBe("yellow"); 147 }); 148 149 it("returns SEARCH for search_files", () => { 150 const result = getOperationCategory("search_files"); 151 expect(result.label).toBe("SEARCH"); 152 expect(result.color).toBe("purple"); 153 }); 154 155 it("returns EXEC for terminal", () => { 156 const result = getOperationCategory("terminal"); 157 expect(result.label).toBe("EXEC"); 158 expect(result.color).toBe("orange"); 159 }); 160 161 it("returns gray with tool name for unknown tools", () => { 162 const result = getOperationCategory("custom_tool"); 163 expect(result.label).toBe("custom_tool"); 164 expect(result.color).toBe("gray"); 165 }); 166 }); 167 168 describe("ToolResultRenderer", () => { 169 it("renders JSON content with JsonView", () => { 170 const { container } = render(<ToolResultRenderer content='{"key": "value"}' />); 171 expect(container.querySelector("[data-testid='json-tree']")).toBeTruthy(); 172 }); 173 174 it("renders JSON with preamble", () => { 175 const { container } = render(<ToolResultRenderer content='Results:\n{"key": "value"}' />); 176 expect(container.textContent).toContain("Results:"); 177 expect(container.querySelector("[data-testid='json-tree']")).toBeTruthy(); 178 }); 179 180 it("renders diff content with DiffView", () => { 181 const diff = `--- a/file.ts\n+++ b/file.ts\n@@ -1,3 +1,4 @@\n line1\n+added\n line2\n line3`; 182 const { container } = render(<ToolResultRenderer content={diff} />); 183 expect(container.querySelector("[data-testid='diff-view']")).toBeTruthy(); 184 }); 185 186 it("renders plain text with MarkdownRenderer", () => { 187 const { container } = render(<ToolResultRenderer content="Hello world" />); 188 expect(container.textContent).toContain("Hello world"); 189 }); 190 191 it("passes toolName prop through", () => { 192 const { container } = render(<ToolResultRenderer content="test" toolName="read_file" />); 193 expect(container.textContent).toContain("test"); 194 }); 195 });