145 lines
5.0 KiB
TypeScript
145 lines
5.0 KiB
TypeScript
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);
|
|
});
|
|
});
|