test: streamline auto-reply and tts suites

This commit is contained in:
Peter Steinberger
2026-02-21 21:43:16 +00:00
parent 5d9e7c942c
commit 52ddb6ae18
7 changed files with 1530 additions and 1809 deletions

View File

@@ -154,56 +154,48 @@ describe("chunkMarkdownText", () => {
expectFencesBalanced(chunks);
});
it("reopens fenced blocks when forced to split inside them", () => {
const text = `\`\`\`txt\n${"a".repeat(500)}\n\`\`\``;
const limit = 120;
const chunks = chunkMarkdownText(text, limit);
expect(chunks.length).toBeGreaterThan(1);
for (const chunk of chunks) {
expect(chunk.length).toBeLessThanOrEqual(limit);
expect(chunk.startsWith("```txt\n")).toBe(true);
expect(chunk.trimEnd().endsWith("```")).toBe(true);
}
expectFencesBalanced(chunks);
});
it("handles multiple fence marker styles when splitting inside fences", () => {
const cases = [
{
name: "backtick fence",
text: `\`\`\`txt\n${"a".repeat(500)}\n\`\`\``,
limit: 120,
expectedPrefix: "```txt\n",
expectedSuffix: "```",
},
{
name: "tilde fence",
text: `~~~sh\n${"x".repeat(600)}\n~~~`,
limit: 140,
expectedPrefix: "~~~sh\n",
expectedSuffix: "~~~",
},
{
name: "long backtick fence",
text: `\`\`\`\`md\n${"y".repeat(600)}\n\`\`\`\``,
limit: 140,
expectedPrefix: "````md\n",
expectedSuffix: "````",
},
{
name: "indented fence",
text: ` \`\`\`js\n ${"z".repeat(600)}\n \`\`\``,
limit: 160,
expectedPrefix: " ```js\n",
expectedSuffix: " ```",
},
] as const;
it("supports tilde fences", () => {
const text = `~~~sh\n${"x".repeat(600)}\n~~~`;
const limit = 140;
const chunks = chunkMarkdownText(text, limit);
expect(chunks.length).toBeGreaterThan(1);
for (const chunk of chunks) {
expect(chunk.length).toBeLessThanOrEqual(limit);
expect(chunk.startsWith("~~~sh\n")).toBe(true);
expect(chunk.trimEnd().endsWith("~~~")).toBe(true);
for (const testCase of cases) {
const chunks = chunkMarkdownText(testCase.text, testCase.limit);
expect(chunks.length, testCase.name).toBeGreaterThan(1);
for (const chunk of chunks) {
expect(chunk.length, testCase.name).toBeLessThanOrEqual(testCase.limit);
expect(chunk.startsWith(testCase.expectedPrefix), testCase.name).toBe(true);
expect(chunk.trimEnd().endsWith(testCase.expectedSuffix), testCase.name).toBe(true);
}
expectFencesBalanced(chunks);
}
expectFencesBalanced(chunks);
});
it("supports longer fence markers for close", () => {
const text = `\`\`\`\`md\n${"y".repeat(600)}\n\`\`\`\``;
const limit = 140;
const chunks = chunkMarkdownText(text, limit);
expect(chunks.length).toBeGreaterThan(1);
for (const chunk of chunks) {
expect(chunk.length).toBeLessThanOrEqual(limit);
expect(chunk.startsWith("````md\n")).toBe(true);
expect(chunk.trimEnd().endsWith("````")).toBe(true);
}
expectFencesBalanced(chunks);
});
it("preserves indentation for indented fences", () => {
const text = ` \`\`\`js\n ${"z".repeat(600)}\n \`\`\``;
const limit = 160;
const chunks = chunkMarkdownText(text, limit);
expect(chunks.length).toBeGreaterThan(1);
for (const chunk of chunks) {
expect(chunk.length).toBeLessThanOrEqual(limit);
expect(chunk.startsWith(" ```js\n")).toBe(true);
expect(chunk.trimEnd().endsWith(" ```")).toBe(true);
}
expectFencesBalanced(chunks);
});
it("never produces an empty fenced chunk when splitting", () => {
@@ -269,12 +261,10 @@ describe("chunkByNewline", () => {
expect(chunks).toEqual([text]);
});
it("returns empty array for empty input", () => {
expect(chunkByNewline("", 100)).toEqual([]);
});
it("returns empty array for whitespace-only input", () => {
expect(chunkByNewline(" \n\n ", 100)).toEqual([]);
it("returns empty array for empty and whitespace-only input", () => {
for (const text of ["", " \n\n "]) {
expect(chunkByNewline(text, 100)).toEqual([]);
}
});
it("preserves trailing blank lines on the last chunk", () => {
@@ -291,83 +281,107 @@ describe("chunkByNewline", () => {
});
describe("chunkTextWithMode", () => {
it("uses length-based chunking for length mode", () => {
const text = "Line one\nLine two";
const chunks = chunkTextWithMode(text, 1000, "length");
expect(chunks).toEqual(["Line one\nLine two"]);
});
it("applies mode-specific chunking behavior", () => {
const cases = [
{
name: "length mode",
text: "Line one\nLine two",
mode: "length" as const,
expected: ["Line one\nLine two"],
},
{
name: "newline mode (single paragraph)",
text: "Line one\nLine two",
mode: "newline" as const,
expected: ["Line one\nLine two"],
},
{
name: "newline mode (blank-line split)",
text: "Para one\n\nPara two",
mode: "newline" as const,
expected: ["Para one", "Para two"],
},
] as const;
it("uses paragraph-based chunking for newline mode", () => {
const text = "Line one\nLine two";
const chunks = chunkTextWithMode(text, 1000, "newline");
expect(chunks).toEqual(["Line one\nLine two"]);
});
it("splits on blank lines for newline mode", () => {
const text = "Para one\n\nPara two";
const chunks = chunkTextWithMode(text, 1000, "newline");
expect(chunks).toEqual(["Para one", "Para two"]);
for (const testCase of cases) {
const chunks = chunkTextWithMode(testCase.text, 1000, testCase.mode);
expect(chunks, testCase.name).toEqual(testCase.expected);
}
});
});
describe("chunkMarkdownTextWithMode", () => {
it("uses markdown-aware chunking for length mode", () => {
const text = "Line one\nLine two";
expect(chunkMarkdownTextWithMode(text, 1000, "length")).toEqual(chunkMarkdownText(text, 1000));
it("applies markdown/newline mode behavior", () => {
const cases = [
{
name: "length mode uses markdown-aware chunker",
text: "Line one\nLine two",
mode: "length" as const,
expected: chunkMarkdownText("Line one\nLine two", 1000),
},
{
name: "newline mode keeps single paragraph",
text: "Line one\nLine two",
mode: "newline" as const,
expected: ["Line one\nLine two"],
},
{
name: "newline mode splits by blank line",
text: "Para one\n\nPara two",
mode: "newline" as const,
expected: ["Para one", "Para two"],
},
] as const;
for (const testCase of cases) {
expect(chunkMarkdownTextWithMode(testCase.text, 1000, testCase.mode), testCase.name).toEqual(
testCase.expected,
);
}
});
it("uses paragraph-based chunking for newline mode", () => {
const text = "Line one\nLine two";
expect(chunkMarkdownTextWithMode(text, 1000, "newline")).toEqual(["Line one\nLine two"]);
});
it("splits on blank lines for newline mode", () => {
const text = "Para one\n\nPara two";
expect(chunkMarkdownTextWithMode(text, 1000, "newline")).toEqual(["Para one", "Para two"]);
});
it("does not split single-newline code fences in newline mode", () => {
const text = "```js\nconst a = 1;\nconst b = 2;\n```\nAfter";
expect(chunkMarkdownTextWithMode(text, 1000, "newline")).toEqual([text]);
});
it("defers long markdown paragraphs to markdown chunking in newline mode", () => {
const text = `\`\`\`js\n${"const a = 1;\n".repeat(20)}\`\`\``;
expect(chunkMarkdownTextWithMode(text, 40, "newline")).toEqual(chunkMarkdownText(text, 40));
});
it("does not split on blank lines inside a fenced code block", () => {
const text = "```python\ndef my_function():\n x = 1\n\n y = 2\n return x + y\n```";
expect(chunkMarkdownTextWithMode(text, 1000, "newline")).toEqual([text]);
});
it("splits on blank lines between a code fence and following paragraph", () => {
it("handles newline mode fence splitting rules", () => {
const fence = "```python\ndef my_function():\n x = 1\n\n y = 2\n return x + y\n```";
const text = `${fence}\n\nAfter`;
expect(chunkMarkdownTextWithMode(text, 1000, "newline")).toEqual([fence, "After"]);
const longFence = `\`\`\`js\n${"const a = 1;\n".repeat(20)}\`\`\``;
const cases = [
{
name: "keeps single-newline fence+paragraph together",
text: "```js\nconst a = 1;\nconst b = 2;\n```\nAfter",
limit: 1000,
expected: ["```js\nconst a = 1;\nconst b = 2;\n```\nAfter"],
},
{
name: "keeps blank lines inside fence together",
text: fence,
limit: 1000,
expected: [fence],
},
{
name: "splits between fence and following paragraph",
text: `${fence}\n\nAfter`,
limit: 1000,
expected: [fence, "After"],
},
{
name: "defers long markdown blocks to markdown chunker",
text: longFence,
limit: 40,
expected: chunkMarkdownText(longFence, 40),
},
] as const;
for (const testCase of cases) {
expect(
chunkMarkdownTextWithMode(testCase.text, testCase.limit, "newline"),
testCase.name,
).toEqual(testCase.expected);
}
});
});
describe("resolveChunkMode", () => {
it("returns length as default", () => {
expect(resolveChunkMode(undefined, "telegram")).toBe("length");
expect(resolveChunkMode({}, "discord")).toBe("length");
expect(resolveChunkMode(undefined, "bluebubbles")).toBe("length");
});
it("returns length for internal channel", () => {
const cfg = { channels: { bluebubbles: { chunkMode: "newline" as const } } };
expect(resolveChunkMode(cfg, "__internal__")).toBe("length");
});
it("supports provider-level overrides for slack", () => {
const cfg = { channels: { slack: { chunkMode: "newline" as const } } };
expect(resolveChunkMode(cfg, "slack")).toBe("newline");
expect(resolveChunkMode(cfg, "discord")).toBe("length");
});
it("supports account-level overrides for slack", () => {
const cfg = {
it("resolves default, provider, account, and internal channel modes", () => {
const providerCfg = { channels: { slack: { chunkMode: "newline" as const } } };
const accountCfg = {
channels: {
slack: {
chunkMode: "length" as const,
@@ -377,7 +391,21 @@ describe("resolveChunkMode", () => {
},
},
};
expect(resolveChunkMode(cfg, "slack", "primary")).toBe("newline");
expect(resolveChunkMode(cfg, "slack", "other")).toBe("length");
const cases = [
{ cfg: undefined, provider: "telegram", accountId: undefined, expected: "length" },
{ cfg: {}, provider: "discord", accountId: undefined, expected: "length" },
{ cfg: undefined, provider: "bluebubbles", accountId: undefined, expected: "length" },
{ cfg: providerCfg, provider: "__internal__", accountId: undefined, expected: "length" },
{ cfg: providerCfg, provider: "slack", accountId: undefined, expected: "newline" },
{ cfg: providerCfg, provider: "discord", accountId: undefined, expected: "length" },
{ cfg: accountCfg, provider: "slack", accountId: "primary", expected: "newline" },
{ cfg: accountCfg, provider: "slack", accountId: "other", expected: "length" },
] as const;
for (const testCase of cases) {
expect(resolveChunkMode(testCase.cfg as never, testCase.provider, testCase.accountId)).toBe(
testCase.expected,
);
}
});
});