/** * KHAELOR * File: tests/tui/markdown.test.ts * Description: Markdown streaming tests — settled-block scanning, fenced-code integrity, block rendering. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { MarkdownStreamScanner } from "../../src/tui/markdown/scanner.js"; import { renderMarkdownBlock, renderTailLine } from "../../src/tui/markdown/render.js"; import { stripAnsi } from "../../src/tui/renderer/ansi.js"; import { resolveTheme } from "../../src/tui/theme.js"; const mono = resolveTheme({ colorDepth: "mono" }); const dark = resolveTheme({ colorDepth: "truecolor" }); describe("MarkdownStreamScanner", () => { it("settles a paragraph at a blank line, keeps the tail live", () => { const s = new MarkdownStreamScanner(); expect(s.append("first paragraph line\n")).toEqual([]); expect(s.tail()).toContain("first paragraph line"); const settled = s.append("\nsecond para"); expect(settled).toEqual(["first paragraph line"]); expect(s.tail()).toBe("second para"); }); it("does not settle across an open fence even at blank lines", () => { const s = new MarkdownStreamScanner(); const settled = s.append("```ts\nconst a = 1;\n\nconst b = 2;\n"); expect(settled).toEqual([]); const closed = s.append("```\n"); expect(closed).toEqual(["```ts\nconst a = 1;\n\nconst b = 2;\n```"]); }); it("preserves fence content byte-exactly across fragmented deltas", () => { const source = "```python\ndef f(x):\n return x * 2\n```"; const s = new MarkdownStreamScanner(); const settled: string[] = []; for (const ch of source + "\n\n") settled.push(...s.append(ch)); expect(settled).toEqual([source]); }); it("finish settles the remainder including a partial last line", () => { const s = new MarkdownStreamScanner(); s.append("done paragraph\n\nlive line\nno newline yet"); const settled = s.finish(); expect(settled).toEqual(["live line\nno newline yet"]); expect(s.tail()).toBe(""); }); it("settleHead flushes early at safe boundaries but never through a fence", () => { const s = new MarkdownStreamScanner(); s.append("a\nb\nc\nd\n"); expect(s.settleHead(2)).toBe("a\nb"); expect(s.tail()).toContain("c"); const fenced = new MarkdownStreamScanner(); fenced.append("```\nx\ny\n"); expect(fenced.insideFence()).toBe(true); expect(fenced.settleHead(2)).toBeNull(); // fence integrity is load-bearing }); it("settles multiple blocks from one append", () => { const s = new MarkdownStreamScanner(); const settled = s.append("one\n\ntwo\n\nthree is live\n"); expect(settled).toEqual(["one", "two"]); }); }); describe("renderMarkdownBlock", () => { it("renders headings, lists, and quotes legibly in monochrome", () => { const lines = renderMarkdownBlock("## Plan\n1. first step\n- bullet\n> quoted words", 80, mono); const text = lines.join("\n"); expect(text).toContain("Plan"); expect(text).toContain("1. first step"); expect(text).toContain("• bullet"); expect(text).toContain("│ quoted words"); expect(text).not.toContain("\x1b["); // mono emits zero escapes }); it("renders fenced code with a gutter and keeps content copy-clean", () => { const lines = renderMarkdownBlock("```ts\nconst n = 42;\n```", 80, dark); expect(lines).toHaveLength(1); const plain = stripAnsi(lines[0] as string); expect(plain).toBe("│ const n = 42;"); }); it("hard-wraps long code lines with the ↪ continuation marker", () => { const long = "x".repeat(120); const lines = renderMarkdownBlock("```\n" + long + "\n```", 60, mono); expect(lines.length).toBeGreaterThan(1); expect(lines[1]).toContain("↪"); expect(lines.map((l) => stripAnsi(l).replace(/^│ (↪ )?/, "")).join("")).toBe(long); }); it("renders aligned tables when they fit", () => { const md = "| step | risk |\n| --- | --- |\n| retry | low |"; const lines = renderMarkdownBlock(md, 80, mono).map(stripAnsi); expect(lines[0]).toContain("step"); expect(lines[0]).toContain("│"); expect(lines[2]).toContain("retry"); }); it("styles inline code and bold via the theme", () => { const out = renderMarkdownBlock("call `fn()` **now**", 80, dark).join("\n"); expect(stripAnsi(out)).toBe("call fn() now"); expect(out).toContain("\x1b[1m"); // bold }); it("renders links as text (url)", () => { const out = renderMarkdownBlock("see [docs](https://example.com)", 80, mono).join("\n"); expect(out).toBe("see docs (https://example.com)"); }); }); describe("renderTailLine", () => { it("applies only cheap inline styling, structure untouched", () => { const styled = renderTailLine("use `retry` and **fsync**", dark); expect(stripAnsi(styled)).toBe("use retry and fsync"); expect(renderTailLine("## not a heading yet", mono)).toBe("## not a heading yet"); }); });