mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 18:01:22 +00:00
292 lines
8.8 KiB
TypeScript
292 lines
8.8 KiB
TypeScript
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 <thinking>content</thinking> in regular text,
|
|
// marked might treat it as HTML and hide the content
|
|
const markdown = new Markdown(
|
|
"This is text with <thinking>hidden content</thinking> 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("<thinking>"),
|
|
"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<div>Some HTML</div>\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("<div>") && joinedPlain.includes("</div>"),
|
|
"Should render HTML in code blocks",
|
|
);
|
|
});
|
|
});
|
|
});
|