import { describe, expect, it } from "vitest"; import { renderMarkdownToHtml } from "@/client/components/MarkdownContent"; const markdownContext = { owner: "alice", repo: "demo", ref: "main", baseDir: "docs/guide", }; describe("renderMarkdownToHtml", () => { it("drops dangerous text-bearing elements and their children", () => { const html = renderMarkdownToHtml( [ "", "", "", "", "

visible

", ].join(""), markdownContext ); expect(html).toContain("

visible

"); expect(html).not.toContain("alert"); expect(html).not.toContain(".bad"); expect(html).not.toContain("secret"); expect(html).not.toContain("choice"); expect(html).not.toContain(" { const html = renderMarkdownToHtml( "before kept after", markdownContext ); expect(html).toContain("before kept after"); expect(html).not.toContain(" { const html = renderMarkdownToHtml( 'Logo', markdownContext ); expect(html).toContain('Logo'); expect(html).not.toContain("onerror"); expect(html).not.toContain("alert"); }); it("removes unsafe URL attributes", () => { const html = renderMarkdownToHtml( [ 'plain', 'encoded', 'inline', ].join(""), markdownContext ); expect(html).toContain("plain"); expect(html).toContain("encoded"); expect(html).toContain('inline'); expect(html).not.toContain("href="); expect(html).not.toContain("src="); expect(html).not.toContain("javascript"); expect(html).not.toContain("data:text/html"); }); it("preserves allowed URL shapes and normalizes attribute escaping", () => { const html = renderMarkdownToHtml( [ 'http', 'encoded', 'ftp', 'mailto', 'tel', 'protocol', 'root', 'relative', 'fragment', ].join(""), markdownContext ); expect(html).toContain('href="http://example.com/a?x=1&y=2"'); expect(html).toContain('href="https://example.com/a?x=1&y=2"'); expect(html).not.toContain("&"); expect(html).toContain('href="ftp://example.com/file"'); expect(html).toContain('href="mailto:dev@example.com"'); expect(html).toContain('href="tel:+15551234567"'); expect(html).toContain('href="//cdn.example.com/file"'); expect(html).toContain('href="/root/path"'); expect(html).toContain('href="../relative/path"'); expect(html).toContain('href="#section"'); }); it("filters classes to the current highlighting allowlist", () => { const html = renderMarkdownToHtml( [ '
',
        '',
        'const',
        "",
        "
", ].join(""), markdownContext ); expect(html).toContain( '
const
' ); expect(html).not.toContain("noise"); expect(html).not.toContain("extra"); }); it("rewrites relative Markdown links and images through repo routes", () => { const html = renderMarkdownToHtml( "[Doc](../README.md#intro)\n\n![Logo](assets/logo.png)", markdownContext ); expect(html).toContain('href="/alice/demo/blob?ref=main&path=docs%2FREADME.md#intro"'); expect(html).toContain( 'src="/alice/demo/rawpath?ref=main&path=docs%2Fguide%2Fassets%2Flogo.png&name=logo.png"' ); expect(html).toContain('alt="Logo"'); expect(html).toContain('loading="lazy"'); }); it("keeps Markdown after raw details blocks outside the disclosure", () => { const html = renderMarkdownToHtml( [ "
", "Rationale", "Text before the list.", "", "1. first", "2. second", "
", "", "## After", "", "Visible outside.", ].join("\n"), markdownContext ); const detailsCloseIndex = html.indexOf(""); const afterHeadingIndex = html.indexOf("

After

"); expect(html).toContain("
    "); expect(detailsCloseIndex).toBeGreaterThan(-1); expect(afterHeadingIndex).toBeGreaterThan(detailsCloseIndex); expect(html).toContain("

    Visible outside.

    "); }); it("preserves highlighted code block classes", () => { const html = renderMarkdownToHtml("```js\nconst answer = 1;\n```", markdownContext); expect(html).toContain('
    ');
        expect(html).toContain('');
        expect(html).toContain('class="hljs-keyword"');
        expect(html).toContain("const");
      });
    });