disclaw/tests/unit/split-for-discord.test.ts
Nick Tabeling 00132c6d22
Some checks failed
CI / build-and-test (ubuntu-latest) (pull_request) Has been cancelled
CI / build-and-test (windows-latest) (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
feat(DIS-105): codeblock-aware message splitting for Discord 2000-char limit
Add splitForDiscord() in src/discord/split.ts.
Fences are properly closed and reopened across chunk boundaries.
Router uses splitForDiscord instead of internal splitMessage().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 17:48:58 +02:00

104 lines
4.1 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { splitForDiscord } from "../../src/discord/split";
const DISCORD_HARD_LIMIT = 2000;
describe("splitForDiscord", () => {
// (a) Short message below limit → returned as single unchanged chunk
it("returns a single chunk for short messages", () => {
const text = "Hello, world!";
const result = splitForDiscord(text);
expect(result).toHaveLength(1);
expect(result[0]).toBe(text);
});
// (b) Text exactly at the limit → single chunk
it("returns a single chunk when text is exactly at the limit", () => {
const text = "a".repeat(1900);
const result = splitForDiscord(text, 1900);
expect(result).toHaveLength(1);
expect(result[0]).toBe(text);
});
// (c) Long plain text → split at line boundaries
it("splits long plain text at line boundaries", () => {
const line = "x".repeat(100);
// 25 lines = 25*100 + 24 newlines = 2524 chars → must split with limit 1900
const text = Array.from({ length: 25 }, () => line).join("\n");
const result = splitForDiscord(text, 1900);
expect(result.length).toBeGreaterThan(1);
// Reassembled text must equal original
expect(result.join("\n")).toBe(text);
});
// (d) Code block spanning a split → fence is closed and reopened with lang tag
it("closes and reopens fenced code blocks across chunk boundaries", () => {
const preamble = "Here is some code:\n";
const fenceOpen = "```typescript\n";
// Fill enough code lines to force a split inside the fence
const codeLine = "const x = 1; // " + "y".repeat(80) + "\n";
const codeLines = Array.from({ length: 20 }, () => codeLine).join("");
const fenceClose = "```\n";
const epilogue = "End of message.";
const text = preamble + fenceOpen + codeLines + fenceClose + epilogue;
const result = splitForDiscord(text, 1900);
expect(result.length).toBeGreaterThan(1);
// Every chunk that starts mid-fence must open with ```typescript
// Every chunk that ends mid-fence must close with ```
for (let i = 0; i < result.length - 1; i++) {
const chunk = result[i];
// Count fence toggles to determine if we end inside a fence
const fenceToggles = (chunk.match(/^```/gm) ?? []).length;
if (fenceToggles % 2 === 1) {
// Odd number means we opened but didn't close — the splitter must close it
expect(chunk.trimEnd()).toMatch(/```$/);
// And the next chunk must reopen with the same language
expect(result[i + 1]).toMatch(/^```typescript/);
}
}
});
// (e) No chunk exceeds 2000 characters
it("never produces a chunk exceeding 2000 characters", () => {
const line = "z".repeat(150);
const text = Array.from({ length: 50 }, () => line).join("\n");
const result = splitForDiscord(text, 1900);
for (const chunk of result) {
expect(chunk.length).toBeLessThanOrEqual(DISCORD_HARD_LIMIT);
}
});
// (f) Empty input → no crash, returns consistent value
it("handles empty input without crashing", () => {
expect(() => splitForDiscord("")).not.toThrow();
const result = splitForDiscord("");
expect(Array.isArray(result)).toBe(true);
});
// Additional: fence language tag is preserved correctly
it("preserves the fence language tag when reopening after a split", () => {
const limit = 200;
const header = "Intro\n";
const fenceOpen = "```python\n";
// Enough lines to exceed 200 chars inside the fence
const codeLine = "print('hello')\n";
const codeLines = Array.from({ length: 20 }, () => codeLine).join("");
const fenceClose = "```";
const text = header + fenceOpen + codeLines + fenceClose;
const result = splitForDiscord(text, limit);
// Find the first chunk that ends with ``` (closed fence)
const closingChunk = result.find(
(c, i) => i < result.length - 1 && c.trimEnd().endsWith("```")
);
expect(closingChunk).toBeDefined();
// The chunk following a mid-fence close should start with ```python
const idx = result.indexOf(closingChunk!);
expect(result[idx + 1]).toMatch(/^```python/);
});
});