| 1 | import assert from "node:assert/strict"; |
| 2 | import { describe, it } from "node:test"; |
| 3 | import { renderMarkdown } from "../markdown"; |
| 4 | |
| 5 | describe("renderMarkdown", () => { |
| 6 | it("escapes HTML so model output cannot inject markup", () => { |
| 7 | const { html } = renderMarkdown('<script>alert("x")</script>'); |
| 8 | assert.ok(!html.includes("<script>")); |
| 9 | assert.ok(html.includes("<script>")); |
| 10 | }); |
| 11 | |
| 12 | it("renders fenced code blocks with copy/insert actions", () => { |
| 13 | const { html, codeBlocks } = renderMarkdown("```ts\nconst x = 1;\nconst y = 2;\n```"); |
| 14 | assert.deepEqual(codeBlocks, ["const x = 1;\nconst y = 2;"]); |
| 15 | assert.ok(html.includes('class="codeblock"')); |
| 16 | assert.ok(html.includes(">Copy</button>")); |
| 17 | assert.ok(html.includes(">Insert</button>")); |
| 18 | assert.ok(html.includes("codeblock-lang")); |
| 19 | assert.ok(html.includes("const x = 1;")); |
| 20 | }); |
| 21 | |
| 22 | it("renders consecutive fenced blocks in order", () => { |
| 23 | const { codeBlocks } = renderMarkdown("```\na\n```\n\n```python\nb\n```"); |
| 24 | assert.deepEqual(codeBlocks, ["a", "b"]); |
| 25 | }); |
| 26 | |
| 27 | it("renders inline code, bold, and headings", () => { |
| 28 | const { html } = renderMarkdown("## Title\nUse `foo()` and **bold** text."); |
| 29 | assert.ok(html.includes("<h4>Title</h4>")); |
| 30 | assert.ok(html.includes("<code>foo()</code>")); |
| 31 | assert.ok(html.includes("<strong>bold</strong>")); |
| 32 | }); |
| 33 | |
| 34 | it("renders http(s) links only", () => { |
| 35 | const { html } = renderMarkdown("[site](https://example.com) and [bad](javascript:alert(1))"); |
| 36 | assert.ok(html.includes('<a href="https://example.com">site</a>')); |
| 37 | assert.ok(!html.includes("href=\"javascript:")); |
| 38 | }); |
| 39 | |
| 40 | it("renders bullet and ordered lists", () => { |
| 41 | const { html } = renderMarkdown("- one\n- two\n\n1. first\n2. second"); |
| 42 | assert.ok(html.includes("<ul><li>one</li><li>two</li></ul>")); |
| 43 | assert.ok(html.includes("<ol><li>first</li><li>second</li></ol>")); |
| 44 | }); |
| 45 | |
| 46 | it("does not treat list items inside fenced code as markup", () => { |
| 47 | const { html, codeBlocks } = renderMarkdown("```\n- not a list\n```"); |
| 48 | assert.deepEqual(codeBlocks, ["- not a list"]); |
| 49 | assert.ok(!html.includes("<li>")); |
| 50 | }); |
| 51 | |
| 52 | it("escapes double quotes inside code attributes", () => { |
| 53 | const { html } = renderMarkdown('```\nsay("hi")\n```'); |
| 54 | assert.ok(html.includes("say("hi")")); |
| 55 | }); |
| 56 | }); |
| 57 |