",
);
expect(materializeTelegramRichHtmlLineBreaks("a\nb")).toBe("a\nb");
expect(materializeTelegramRichHtmlLineBreaks("x\ny")).toBe(
"x\ny",
);
});
it("preserves structural newlines that only separate block tags", () => {
// Block tags already break; a stray would add a blank line or land as an
// invalid container child. Mixed text hugging a block keeps its boundary \n too.
const blocks = "
Plan
\n
A
";
expect(materializeTelegramRichHtmlLineBreaks(blocks)).toBe(blocks);
expect(
materializeTelegramRichHtmlLineBreaks(
'A\n\n\n\nB',
),
).toBe('A\n\n\n\nB');
});
it("does not let a self-closing literal tag swallow later line breaks", () => {
expect(materializeTelegramRichHtmlLineBreaks("\na\nb")).toBe(" a b");
});
it("does not inject into pretty-printed rich containers", () => {
// Explicit rich HTML can arrive pretty-printed; newlines between or inside
// table/figure/details container children are layout, not prose, and the
// block-counting set omits thead/tbody/td/th/caption/figcaption/summary.
const table =
"
\n\n
H
\n\n\n
A
\n\n
";
expect(materializeTelegramRichHtmlLineBreaks(table)).toBe(table);
const figure =
'\n\n\nCap\n\n';
expect(materializeTelegramRichHtmlLineBreaks(figure)).toBe(figure);
const details = "\n\nMore\n\nBody\n";
expect(materializeTelegramRichHtmlLineBreaks(details)).toBe(details);
});
it("keeps existing tags intact without doubling adjacent newlines", () => {
expect(materializeTelegramRichHtmlLineBreaks("a b\nc")).toBe("a b c");
// A newline hugging an existing stays literal — the break already exists.
expect(materializeTelegramRichHtmlLineBreaks("line1 \nline2")).toBe("line1 \nline2");
});
it("preserves rich table, details, quote, checklist, anchor, and math HTML", () => {
const input = [
'',
"
");
expect(sanitized.degradationReasons).toEqual(["table-ascii"]);
});
it("falls back malformed raw rich HTML tables", () => {
const sanitized = normalizeTelegramOutboundRichHtml("
Broken
");
expect(sanitized.html).toContain("
Broken
");
expect(sanitized.degradationReasons).toEqual(["table-ascii"]);
});
it("clamps raw rich HTML table colspans before fallback", () => {
const html = '
");
});
it("renders multiline blockquotes as a single Telegram blockquote", () => {
const res = markdownToTelegramHtml("> first\n> second");
expect(res).toBe("
first\nsecond
");
});
it("renders separated quoted paragraphs as distinct blockquotes", () => {
const res = markdownToTelegramHtml("> first\n\n> second");
expect(res).toContain("
first");
expect(res).toContain("
second
");
expect(res.match(/
/g)).toHaveLength(2);
});
it("renders fenced code block languages for Telegram native copy buttons", () => {
const res = markdownToTelegramHtml('```bash\necho "hello"\n```');
expect(res).toBe('
echo "hello"\n
');
});
it("properly nests overlapping bold and autolink (#4071)", () => {
const res = markdownToTelegramHtml("**start https://example.com** end");
expect(res).toMatch(
/start https:\/\/example\.com<\/a><\/b> end/,
);
});
it("properly nests link inside bold", () => {
const res = markdownToTelegramHtml("**bold [link](https://example.com) text**");
expect(res).toBe('bold link text');
});
it("properly nests bold wrapping a link with trailing text", () => {
const res = markdownToTelegramHtml("**[link](https://example.com) rest**");
expect(res).toBe('link rest');
});
it("properly nests bold inside a link", () => {
const res = markdownToTelegramHtml("[**bold**](https://example.com)");
expect(res).toBe('bold');
});
it("wraps punctuated file references in code tags", () => {
const res = markdownToTelegramHtml("See README.md. Also (backup.sh).");
expect(res).toContain("README.md.");
expect(res).toContain("(backup.sh).");
});
it("renders spoiler tags", () => {
const res = markdownToTelegramHtml("the answer is ||42||");
expect(res).toBe("the answer is 42");
});
it("renders spoiler with nested formatting", () => {
const res = markdownToTelegramHtml("||**secret** text||");
expect(res).toBe("secret text");
});
it("preserves spacing between Telegram bullet blocks and following numbered sections", () => {
const input = [
"2. Main invariants:",
"",
" • Raw Log is source of truth.",
" • Autonomy starts only with report/draft.",
"3. Cognee is a candidate:",
"",
" • bake-off first;",
" • decide keep/adopt/hybrid later.",
"4. Project Flow slices:",
].join("\n");
const res = markdownToTelegramHtml(input, { wrapFileRefs: false });
expect(res).toContain("report/draft.\n\n3. Cognee");
expect(res).toContain("keep/adopt/hybrid later.\n\n4. Project");
});
it("preserves Telegram list boundary spacing in chunked rendering", () => {
const input = [
"2. Main invariants:",
"",
" • Raw Log is source of truth.",
" • Autonomy starts only with report/draft.",
"3. Cognee is a candidate:",
].join("\n");
const res = markdownToTelegramChunks(input, 4096)
.map((chunk) => chunk.html)
.join("");
expect(res).toContain("report/draft.\n\n3. Cognee");
});
it("does not insert Telegram list boundary spacing inside fenced code", () => {
const input = ["```", " • literal bullet", "3. literal number", "```"].join("\n");
const res = markdownToTelegramHtml(input, { wrapFileRefs: false });
expect(res).toBe("