import { describe, it, expect } from "vitest"; import { splitMessage } from "../../src/response-formatter.js"; describe("splitMessage", () => { it("should return empty array for empty text", () => { expect(splitMessage("")).toEqual([]); }); it("should return single chunk for text shorter than maxLength", () => { const text = "Hello, world!"; expect(splitMessage(text)).toEqual([text]); }); it("should return single chunk for text exactly at maxLength", () => { const text = "a".repeat(2000); expect(splitMessage(text)).toEqual([text]); }); it("should split text exceeding maxLength into multiple chunks", () => { const text = "a".repeat(3000); const chunks = splitMessage(text, 2000); expect(chunks.length).toBeGreaterThan(1); for (const chunk of chunks) { expect(chunk.length).toBeLessThanOrEqual(2000); } }); it("should prefer splitting at line boundaries", () => { const line = "x".repeat(80) + "\n"; // 25 lines of 81 chars each = 2025 chars total const text = line.repeat(25); const chunks = splitMessage(text, 2000); // Each chunk should end at a newline boundary for (const chunk of chunks.slice(0, -1)) { expect(chunk.endsWith("\n") || chunk.endsWith("```")).toBe(true); } }); it("should handle custom maxLength", () => { const text = "Hello\nWorld\nFoo\nBar"; const chunks = splitMessage(text, 10); for (const chunk of chunks) { expect(chunk.length).toBeLessThanOrEqual(10); } }); it("should preserve content when splitting plain text", () => { const lines = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}`); const text = lines.join("\n"); const chunks = splitMessage(text, 100); const reassembled = chunks.join(""); expect(reassembled).toBe(text); }); it("should close and reopen code blocks across splits", () => { const codeContent = "x\n".repeat(1500); const text = "Before\n```typescript\n" + codeContent + "```\nAfter"; const chunks = splitMessage(text, 2000); expect(chunks.length).toBeGreaterThan(1); // First chunk should contain the opening fence expect(chunks[0]).toContain("```typescript"); // First chunk should end with a closing fence since it splits mid-block expect(chunks[0].trimEnd().endsWith("```")).toBe(true); // Second chunk should reopen the code block expect(chunks[1].startsWith("```typescript")).toBe(true); }); it("should handle multiple code blocks in the same text", () => { const block1 = "```js\nconsole.log('hello');\n```"; const block2 = "```python\nprint('world')\n```"; const text = block1 + "\n\nSome text\n\n" + block2; const chunks = splitMessage(text, 2000); // Should fit in one chunk expect(chunks).toEqual([text]); }); it("should handle code block that fits entirely in one chunk", () => { const text = "Hello\n```js\nconst x = 1;\n```\nGoodbye"; const chunks = splitMessage(text, 2000); expect(chunks).toEqual([text]); }); it("should handle text with no newlines", () => { const text = "a".repeat(5000); const chunks = splitMessage(text, 2000); expect(chunks.length).toBeGreaterThanOrEqual(3); for (const chunk of chunks) { expect(chunk.length).toBeLessThanOrEqual(2000); } expect(chunks.join("")).toBe(text); }); it("should ensure every chunk is within maxLength", () => { const codeContent = "x\n".repeat(2000); const text = "```\n" + codeContent + "```"; const chunks = splitMessage(text, 2000); for (const chunk of chunks) { expect(chunk.length).toBeLessThanOrEqual(2000); } }); it("should handle code block with language tag across splits", () => { const longCode = ("const x = 1;\n").repeat(200); const text = "```typescript\n" + longCode + "```"; const chunks = splitMessage(text, 500); // All continuation chunks should reopen with the language tag for (let i = 1; i < chunks.length; i++) { if (chunks[i].startsWith("```")) { expect(chunks[i].startsWith("```typescript")).toBe(true); } } }); it("should produce chunks that reconstruct original text modulo fence delimiters", () => { const longCode = ("line\n").repeat(300); const text = "Start\n```\n" + longCode + "```\nEnd"; const chunks = splitMessage(text, 500); // Remove inserted fence closers/openers and rejoin const cleaned = chunks.map((chunk, i) => { let c = chunk; // Remove reopened fence at start (for continuation chunks) if (i > 0 && c.startsWith("```")) { const newlineIdx = c.indexOf("\n"); if (newlineIdx !== -1) { c = c.slice(newlineIdx + 1); } } // Remove inserted closing fence at end (for non-last chunks that close a block) if (i < chunks.length - 1 && c.trimEnd().endsWith("```")) { const lastFence = c.lastIndexOf("\n```"); if (lastFence !== -1) { c = c.slice(0, lastFence); } } return c; }); expect(cleaned.join("")).toBe(text); }); });