import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, test } from "vitest"; import { MessageMarkdown } from "../../src/renderer/src/pages/chat/MessageMarkdown"; const render = (body: string, mentions: Array<{ start: number; end: number }> = []) => renderToStaticMarkup(createElement(MessageMarkdown, { body, mentions, renderMention: (index: number) => createElement("b", { "data-chip": index }, "CHIP"), })); describe("message markdown", () => { test("renders code blocks, tables and nested lists instead of raw markup", () => { const html = render("Plan:\n\n```ts\nconst a = 1;\n```\n\n| A | B |\n| - | - |\n| 1 | 2 |\n\n- one\n - nested\n- two"); expect(html).toContain("2"); expect(html).toMatch(/
  • one\s*]*>\s*
  • nested<\/li>/); expect(html).not.toContain("```"); }); test("keeps line breaks, drops raw HTML and images, and opens links outside", () => { const html = render("line one\nline two ![x](https://e.com/x.png) [site](https://e.com)"); expect(html).toContain("line one\nline two"); expect(html).not.toContain(" { const body = "Hi @Dana see `@Dana` and **@Dana**"; const at = (from: number) => ({ start: body.indexOf("@Dana", from), end: body.indexOf("@Dana", from) + 5 }); const first = at(0); const inCode = at(first.end); const bold = at(inCode.end); const html = render(body, [first, inCode, bold]); expect(html.match(/CHIP/g)).toHaveLength(2); expect(html).toContain("]*>@Dana<\/code>/); expect(html).toMatch(/CHIP<\/b><\/strong>/); }); test("finds mentions in text that entities or escapes changed", () => { for (const body of ["Use & @Dana now", "Stars \\* and @Dana"]) { const start = body.indexOf("@Dana"); expect(render(body, [{ start, end: start + 5 }]).match(/CHIP/g)).toHaveLength(1); } }); test("chips the mention the server resolved, not a look-alike copy before it", () => { for (const body of ["@Dana and @Dana", "\\@Dana and @Dana", "x@Dana and @Dana"]) { const start = body.lastIndexOf("@Dana"); const html = render(body, [{ start, end: start + 5 }]); expect(html.match(/CHIP/g)).toHaveLength(1); expect(html).toMatch(/@Dana and CHIP<\/b>/); } }); test("a mention inside a link URL stays the URL's text", () => { const body = "[profile](https://e.com/@Dana)"; const start = body.indexOf("@Dana"); expect(render(body, [{ start, end: start + 5 }])).toContain('href="https://e.com/@Dana"'); }); test("an entity in the body cannot forge a mention chip", () => { for (const forged of ["0", "@0"]) { expect(render(`Hello ${forged}`).match(/CHIP/g)).toBeNull(); } }); });