import assert from "node:assert"; import { describe, it } from "node:test"; import { Chalk } from "chalk"; import { Markdown } from "../src/components/markdown.js"; import { defaultMarkdownTheme } from "./test-themes.js"; // Force full color in CI so ANSI assertions are deterministic const chalk = new Chalk({ level: 3 }); describe("Markdown component", () => { describe("Nested lists", () => { it("should render simple nested list", () => { const markdown = new Markdown( `- Item 1 - Nested 1.1 - Nested 1.2 - Item 2`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); // Check that we have content assert.ok(lines.length > 0); // Strip ANSI codes for checking const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); // Check structure assert.ok(plainLines.some((line) => line.includes("- Item 1"))); assert.ok(plainLines.some((line) => line.includes(" - Nested 1.1"))); assert.ok(plainLines.some((line) => line.includes(" - Nested 1.2"))); assert.ok(plainLines.some((line) => line.includes("- Item 2"))); }); it("should render deeply nested list", () => { const markdown = new Markdown( `- Level 1 - Level 2 - Level 3 - Level 4`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); // Check proper indentation assert.ok(plainLines.some((line) => line.includes("- Level 1"))); assert.ok(plainLines.some((line) => line.includes(" - Level 2"))); assert.ok(plainLines.some((line) => line.includes(" - Level 3"))); assert.ok(plainLines.some((line) => line.includes(" - Level 4"))); }); it("should render ordered nested list", () => { const markdown = new Markdown( `1. First 1. Nested first 2. Nested second 2. Second`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); assert.ok(plainLines.some((line) => line.includes("1. First"))); assert.ok(plainLines.some((line) => line.includes(" 1. Nested first"))); assert.ok(plainLines.some((line) => line.includes(" 2. Nested second"))); assert.ok(plainLines.some((line) => line.includes("2. Second"))); }); it("should render mixed ordered and unordered nested lists", () => { const markdown = new Markdown( `1. Ordered item - Unordered nested - Another nested 2. Second ordered - More nested`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); assert.ok(plainLines.some((line) => line.includes("1. Ordered item"))); assert.ok(plainLines.some((line) => line.includes(" - Unordered nested"))); assert.ok(plainLines.some((line) => line.includes("2. Second ordered"))); }); }); describe("Tables", () => { it("should render simple table", () => { const markdown = new Markdown( `| Name | Age | | --- | --- | | Alice | 30 | | Bob | 25 |`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); // Check table structure assert.ok(plainLines.some((line) => line.includes("Name"))); assert.ok(plainLines.some((line) => line.includes("Age"))); assert.ok(plainLines.some((line) => line.includes("Alice"))); assert.ok(plainLines.some((line) => line.includes("Bob"))); // Check for table borders assert.ok(plainLines.some((line) => line.includes("│"))); assert.ok(plainLines.some((line) => line.includes("─"))); }); it("should render table with alignment", () => { const markdown = new Markdown( `| Left | Center | Right | | :--- | :---: | ---: | | A | B | C | | Long text | Middle | End |`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); // Check headers assert.ok(plainLines.some((line) => line.includes("Left"))); assert.ok(plainLines.some((line) => line.includes("Center"))); assert.ok(plainLines.some((line) => line.includes("Right"))); // Check content assert.ok(plainLines.some((line) => line.includes("Long text"))); }); it("should handle tables with varying column widths", () => { const markdown = new Markdown( `| Short | Very long column header | | --- | --- | | A | This is a much longer cell content | | B | Short |`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); // Should render without errors assert.ok(lines.length > 0); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); assert.ok(plainLines.some((line) => line.includes("Very long column header"))); assert.ok(plainLines.some((line) => line.includes("This is a much longer cell content"))); }); }); describe("Combined features", () => { it("should render lists and tables together", () => { const markdown = new Markdown( `# Test Document - Item 1 - Nested item - Item 2 | Col1 | Col2 | | --- | --- | | A | B |`, 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); // Check heading assert.ok(plainLines.some((line) => line.includes("Test Document"))); // Check list assert.ok(plainLines.some((line) => line.includes("- Item 1"))); assert.ok(plainLines.some((line) => line.includes(" - Nested item"))); // Check table assert.ok(plainLines.some((line) => line.includes("Col1"))); assert.ok(plainLines.some((line) => line.includes("│"))); }); }); describe("Pre-styled text (thinking traces)", () => { it("should preserve gray italic styling after inline code", () => { // This replicates how thinking content is rendered in assistant-message.ts const markdown = new Markdown( "This is thinking with `inline code` and more text after", 1, 0, defaultMarkdownTheme, { color: (text) => chalk.gray(text), italic: true, }, ); const lines = markdown.render(80); const joinedOutput = lines.join("\n"); // Should contain the inline code block assert.ok(joinedOutput.includes("inline code")); // The output should have ANSI codes for gray (90) and italic (3) assert.ok(joinedOutput.includes("\x1b[90m"), "Should have gray color code"); assert.ok(joinedOutput.includes("\x1b[3m"), "Should have italic code"); // Verify that inline code is styled (theme uses yellow) const hasCodeColor = joinedOutput.includes("\x1b[33m"); assert.ok(hasCodeColor, "Should style inline code"); }); it("should preserve gray italic styling after bold text", () => { const markdown = new Markdown( "This is thinking with **bold text** and more after", 1, 0, defaultMarkdownTheme, { color: (text) => chalk.gray(text), italic: true, }, ); const lines = markdown.render(80); const joinedOutput = lines.join("\n"); // Should contain bold text assert.ok(joinedOutput.includes("bold text")); // The output should have ANSI codes for gray (90) and italic (3) assert.ok(joinedOutput.includes("\x1b[90m"), "Should have gray color code"); assert.ok(joinedOutput.includes("\x1b[3m"), "Should have italic code"); // Should have bold codes (1 or 22 for bold on/off) assert.ok(joinedOutput.includes("\x1b[1m"), "Should have bold code"); }); }); describe("HTML-like tags in text", () => { it("should render content with HTML-like tags as text", () => { // When the model emits something like content in regular text, // marked might treat it as HTML and hide the content const markdown = new Markdown( "This is text with hidden content that should be visible", 0, 0, defaultMarkdownTheme, ); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); const joinedPlain = plainLines.join(" "); // The content inside the tags should be visible assert.ok( joinedPlain.includes("hidden content") || joinedPlain.includes(""), "Should render HTML-like tags or their content as text, not hide them", ); }); it("should render HTML tags in code blocks correctly", () => { const markdown = new Markdown("```html\n
Some HTML
\n```", 0, 0, defaultMarkdownTheme); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); const joinedPlain = plainLines.join("\n"); // HTML in code blocks should be visible assert.ok( joinedPlain.includes("
") && joinedPlain.includes("
"), "Should render HTML in code blocks", ); }); }); });