import { describe, expect, it } from "vitest"; import type { MarkdownTransformContext } from "../src/core/extensions/types.ts"; import type { MermaidRenderingMode } from "../src/core/settings-manager.ts"; import { createMermaidMarkdownTransformer } from "../src/modes/interactive/components/mermaid.ts"; import type { Theme } from "../src/modes/interactive/theme/theme.ts"; interface TransformOptions { maxWidth?: number; isStreaming?: boolean; messageType?: MarkdownTransformContext["messageType"]; mode?: MermaidRenderingMode; theme?: Theme; } function transformMermaid(markdown: string, options: TransformOptions = {}): string { const transformer = createMermaidMarkdownTransformer({ getMode: () => options.mode ?? "streaming", theme: options.theme, }); return transformer(markdown, { availableWidth: options.maxWidth ?? 100, isStreaming: options.isStreaming ?? false, messageType: options.messageType ?? "assistant", }); } describe("Mermaid rendering", () => { it("replaces Mermaid code blocks with Unicode diagrams", () => { const markdown = "Before\n\n```mermaid\nflowchart LR\n A[Start] --> B[Done]\n```\nAfter"; const rendered = transformMermaid(markdown); expect(rendered).toContain("Before"); expect(rendered).toContain("┌───────┐"); expect(rendered).toContain("│ Start ├───▶│ Done │"); expect(rendered).toContain("└───────┘ └──────┘`\nAfter"); expect(rendered).not.toContain("```mermaid"); expect(rendered).toContain("After"); }); it("leaves unsupported and oversized diagrams unchanged", () => { const unsupported = '```mermaid\npie\n title Pets\n "Dogs" : 4\n```'; const oversized = "```mermaid\nflowchart LR\n A[Start] --> B[Done]\n```"; expect(transformMermaid(unsupported)).toBe(unsupported); expect(transformMermaid(oversized, { maxWidth: 10 })).toBe(oversized); }); it("maps semantic spans through the Pi theme", () => { const theme = { fg: (color: string, text: string) => `<${color}>${text}`, bold: (text: string) => `${text}`, } as Theme; const rendered = transformMermaid("```mermaid\nflowchart LR\n A --> B\n```", { theme }); expect(rendered).toContain(""); expect(rendered).toContain(""); }); it("renders incomplete Mermaid blocks during streaming", () => { const partialMarkdown = "```mermaid\nflowchart LR\n A --> B"; expect(transformMermaid(partialMarkdown, { isStreaming: true })).toContain("───▶"); }); it("renders diagrams with class assignments", () => { const markdown = "```mermaid\nflowchart LR\n A[Foo]:::highlight --> B[Bar]\n```"; const rendered = transformMermaid(markdown); expect(rendered).not.toContain("```mermaid"); expect(rendered).not.toContain("Mermaid diagram not rendered"); expect(rendered).toContain("│ Foo ├───▶│ Bar │"); }); it("falls back to the code block with a warning after streaming", () => { const markdown = "```mermaid\nflowchart LR\n A[Foo] invalid\n```"; const final = transformMermaid(markdown); const followedByText = transformMermaid(`${markdown}\nFollowing text`); const streaming = transformMermaid(markdown, { isStreaming: true }); expect(final).toContain(markdown); expect(final).toContain("```\n`Mermaid diagram not rendered"); expect(final).toContain('dropped, expected a link: "invalid"'); expect(final).not.toContain("more)"); expect(followedByText).toContain(" \nFollowing text"); expect(streaming).not.toContain("Mermaid diagram not rendered"); expect(streaming).not.toContain("```mermaid"); expect(streaming).toContain("│ Foo │"); }); it("summarizes additional partial-render warnings", () => { const markdown = "```mermaid\nflowchart LR\n A[Foo] invalid\n B[Bar] also-invalid\n```"; const rendered = transformMermaid(markdown); expect(rendered).toContain(markdown); expect(rendered).toContain('dropped, expected a link: "invalid"'); expect(rendered).toContain("(+1 more)"); expect(rendered).not.toContain('dropped, expected a link: "also-invalid"'); }); it("respects rendering modes and skips thinking blocks", () => { const markdown = "```mermaid\nflowchart LR\n A --> B\n```"; expect(transformMermaid(markdown, { mode: "off" })).toBe(markdown); expect(transformMermaid(markdown, { mode: "final", isStreaming: true })).toBe(markdown); expect(transformMermaid(markdown, { mode: "final" })).not.toContain("```mermaid"); expect(transformMermaid(markdown, { messageType: "assistant-thinking" })).toBe(markdown); }); });