Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254import { describe, expect, it } from "vitest";import { isMarkdownFile } from "$lib/markup/format";import { renderMarkdown } from "$lib/markup/markdown";import type { MarkupContext } from "$lib/markup/paths";
const ctx: MarkupContext = { repo: "ada.test/infra", ref: "main", host: "tangled.org" };const render = (source: string, overrides: Partial<MarkupContext> = {}) => renderMarkdown(source, { ...ctx, ...overrides });
describe("isMarkdownFile", () => { it("knows the markdown extensions from the plain text ones", async () => { expect(isMarkdownFile("README.md")).toBe(true); expect(isMarkdownFile("readme.MARKDOWN")).toBe(true); expect(isMarkdownFile("readme.mkd")).toBe(true); expect(isMarkdownFile("README")).toBe(false); expect(isMarkdownFile("README.rst")).toBe(false); });});
describe("renderMarkdown", () => { it("renders gfm tables and strikethrough", async () => { const html = await render("| a | b |\n| - | - |\n| 1 | 2 |\n\n~~gone~~"); expect(html).toContain("<table>"); expect(html).toContain("<s>gone</s>"); });
it("gives headings github's slugs and an anchor link", async () => { const html = await render("## Hello, World!"); expect(html).toContain('id="hello-world"'); expect(html).toContain('<a class="anchor" href="#hello-world">#</a>'); });
it("points relative links at the file browser", async () => { expect(await render("[docs](./docs/setup.md)")).toContain( 'href="/ada.test/infra/tree/main/docs/setup.md"' ); });
it("resolves a link against the directory the document is in", async () => { const html = await render("[sibling](../other.md)", { dir: "docs/guide" }); expect(html).toContain('href="/ada.test/infra/tree/main/docs/other.md"'); });
it("treats an absolute path as repo relative, not host relative", async () => { expect(await render("[root](/LICENSE)", { dir: "docs" })).toContain( 'href="/ada.test/infra/tree/main/LICENSE"' ); });
it("keeps a fragment on a rewritten link", async () => { expect(await render("[part](./setup.md#install)")).toContain( 'href="/ada.test/infra/tree/main/setup.md#install"' ); });
it("leaves fragments, mail and absolute urls alone", async () => { const html = await render("[a](#top) [b](mailto:ada@test) [c](https://example.com)"); expect(html).toContain('href="#top"'); expect(html).toContain('href="mailto:ada@test"'); expect(html).toContain('href="https://example.com"'); });
it("marks off-site links as untrusted", async () => { expect(await render("[c](https://example.com)")).toContain( 'rel="nofollow noopener noreferrer"' ); expect(await render("[a](./x.md)")).not.toContain("nofollow"); });
it("points relative images at the raw file, including raw html ones", async () => { expect(await render("")).toContain( 'src="/ada.test/infra/raw/main/assets/logo.png"' ); expect(await render('<img src="assets/logo.png" alt="logo">')).toContain( 'src="/ada.test/infra/raw/main/assets/logo.png"' ); });
it("rewrites every candidate in a srcset", async () => { const html = await render('<picture><source srcset="a.png 1x, b.png 2x"></picture>'); expect(html).toContain( 'srcset="/ada.test/infra/raw/main/a.png 1x, /ada.test/infra/raw/main/b.png 2x"' ); });
it("leaves off-site images alone when there is no camo", async () => { expect(await render("")).toContain( 'src="https://example.com/a.png"' ); });
describe("with camo", () => { const camo = (source: string) => render(source, { camo: true }); // hex of https://example.com/a.png, which is what camo signs const hex = "68747470733a2f2f6578616d706c652e636f6d2f612e706e67";
it("sends an off-site image to the route that signs for camo", async () => { expect(await camo("")).toContain(`src="/camo/${hex}"`); });
it("proxies raw html images and srcset candidates too", async () => { expect(await camo('<img src="https://example.com/a.png">')).toContain( `src="/camo/${hex}"` ); expect(await camo('<source srcset="https://example.com/a.png 2x">')).toContain( `srcset="/camo/${hex} 2x"` ); });
it("leaves our own images and repo files direct", async () => { expect(await camo("")).toContain( 'src="https://tangled.org/a.png"' ); expect(await camo("")).toContain( 'src="/ada.test/infra/raw/main/assets/a.png"' ); });
it("does not proxy links, only what the page loads by itself", async () => { expect(await camo("[x](https://example.com/a.png)")).toContain( 'href="https://example.com/a.png"' ); }); });
it("links a bare handle to its profile", async () => { const html = await render("thanks @ada.test for the fix"); expect(html).toContain('<a href="/ada.test" class="mention">@ada.test</a>'); });
it("leaves a handle inside a link label as text", async () => { expect(await render("[ask @ada.test](https://example.com)")).not.toContain("mention"); });
it("does not read an email address as a mention", async () => { expect(await render("mail ada@ada.test now")).not.toContain("mention"); });
it("shortens our own commit urls to a sha", async () => { const url = "https://tangled.org/ada.test/infra/commit/0c4d0e9b07940033721395a434b5873f0fb9e6c8"; expect(await render(url)).toContain("<code>0c4d0e9b</code>"); });
it("leaves a commit url that has its own label", async () => { const html = await render( "[the fix](https://tangled.org/ada.test/infra/commit/0c4d0e9b07940033721395a434b5873f0fb9e6c8)" ); expect(html).toContain("the fix"); expect(html).not.toContain("<code>"); });
it("leaves another host's commit urls as urls", async () => { const url = "https://github.com/ada/infra/commit/0c4d0e9b07940033721395a434b5873f0fb9e6c8"; expect(await render(url)).not.toContain("<code>"); });
it("renders task lists as disabled checkboxes", async () => { const html = await render("- [x] done\n- [ ] not"); expect(html).toContain('type="checkbox"'); expect(html).toContain('checked="checked"'); expect(html).toContain('disabled="disabled"'); });
it("renders footnotes with their backlinks", async () => { const html = await render("a claim[^1]\n\n[^1]: the source"); expect(html).toContain('class="footnote-ref"'); expect(html).toContain('class="footnote-backref"'); expect(html).toContain("the source"); });
it("renders github style alerts", async () => { const html = await render("> [!WARNING]\n> careful"); expect(html).toContain('class="markdown-alert markdown-alert-warning"'); expect(html).toContain("careful"); });
it("renders emoji shortcodes", async () => { expect(await render("ship it :tada:")).toContain("🎉"); });
it("keeps the language on a fenced block", async () => { expect(await render("```rust\nfn main() {}\n```")).toContain('class="language-rust"'); });
it("highlights a fenced block but keeps its language class", async () => { const html = await render("```rust\nfn main() {}\n```"); expect(html).toContain('class="language-rust"'); expect(html).toContain("--diffs-token-"); expect(html).toContain("<span"); });
it("leaves fenced blocks without a known language plain", async () => { const unknown = await render("```nosuchlang\nhello\n```"); expect(unknown).toContain('class="language-nosuchlang"'); expect(unknown).not.toContain("--diffs-token-");
const plain = await render("```\nhello\n```"); expect(plain).not.toContain("--diffs-token-"); });
it("leaves oversized blocks plain for the client instead of stalling ssr", async () => { const html = await render("```rust\n" + "x".repeat(60_000) + "\n```"); expect(html).toContain('class="language-rust"'); expect(html).not.toContain("--diffs-token-"); });
it("does not let highlighted code smuggle styles past the sanitizer", async () => { const html = await render("```rust\nfn main() {}\n```"); expect(html).not.toContain("background-color"); expect(html).not.toContain("shiki"); });
it("strips scripts, event handlers and javascript urls", async () => { expect(await render("<script>alert(1)</script>")).not.toContain("alert"); expect(await render('<img src="x" onerror="alert(1)">')).not.toContain("onerror"); // markdown-it refuses the destination outright, so this stays plain text expect(await render("[x](javascript:alert(1))")).not.toContain("<a"); expect(await render('<a href="data:text/html,hi">x</a>')).not.toContain("data:"); });
it("drops classes it does not recognise", async () => { expect(await render('<div class="fixed inset-0 bg-black">boo</div>')).not.toContain( "inset-0" ); });
it("drops inputs that are not task list checkboxes", async () => { expect(await render('<input type="password" name="p">')).not.toContain("<input"); });
it("keeps the html a readme actually uses for layout", async () => { const html = await render( '<div align="center"><h1>infra</h1></div>\n\n<details><summary>more</summary>\n\nhidden\n\n</details>' ); expect(html).toContain('<div align="center">'); expect(html).toContain("<details><summary>more</summary>"); });
it("tags blocks with their source line only when asked", async () => { const source = "# Title\n\ntext\n\n- one\n- two\n\n```\ncode\n```"; const lines = await render(source, { sourceLines: true }); expect(lines).toContain('<h1 id="title" data-line="0">'); expect(lines).toContain('<p data-line="2">'); expect(lines).toContain('<li data-line="5">'); expect(lines).toContain('data-line="7"'); expect(await render(source)).not.toContain("data-line"); });
it("drops source lines written as raw html", async () => { expect(await render('<p data-line="9">x</p>')).not.toContain("data-line"); });});