import { describe, expect, test } from "bun:test"; import { markdownToPlaintext, renderMarkdown, resolveMarkdownHref, } from "../src/services/markdown.ts"; describe("resolveMarkdownHref", () => { describe("protocol-absolute URLs (returns null)", () => { test("http", () => { expect(resolveMarkdownHref("", "http://example.com/img.png")).toBeNull(); expect(resolveMarkdownHref("docs", "http://example.com/img.png")).toBeNull(); }); test("https", () => { expect(resolveMarkdownHref("", "https://example.com/img.png")).toBeNull(); }); test("ftp", () => { expect(resolveMarkdownHref("", "ftp://files.example.com/")).toBeNull(); }); test("mailto", () => { expect(resolveMarkdownHref("", "mailto:foo@bar.com")).toBeNull(); }); test("data", () => { expect(resolveMarkdownHref("", "data:image/png;base64,abc123")).toBeNull(); }); test("ssh", () => { expect(resolveMarkdownHref("", "ssh://git@example.com/repo.git")).toBeNull(); }); test("custom protocol", () => { expect(resolveMarkdownHref("", "myapp://open/something")).toBeNull(); }); }); describe("anchor links (returns null)", () => { test("simple anchor", () => { expect(resolveMarkdownHref("", "#section-heading")).toBeNull(); }); test("anchor with any dir", () => { expect(resolveMarkdownHref("docs/guide", "#toc")).toBeNull(); }); test("bare hash", () => { expect(resolveMarkdownHref("", "#")).toBeNull(); }); }); describe("root-relative URLs (strips leading /)", () => { test("single file at root", () => { expect(resolveMarkdownHref("", "/img.png")).toBe("img.png"); }); test("subdir file — dir is ignored", () => { expect(resolveMarkdownHref("docs", "/subdir/img.png")).toBe("subdir/img.png"); }); test("deep path — any dir is ignored", () => { expect(resolveMarkdownHref("x/y/z", "/a/b/c.png")).toBe("a/b/c.png"); }); test("root-relative with no filename (trailing slash)", () => { expect(resolveMarkdownHref("docs", "/assets/")).toBe("assets/"); }); }); describe("path-relative from root dir (dir = '')", () => { test("bare filename", () => { expect(resolveMarkdownHref("", "img.png")).toBe("img.png"); }); test("./filename", () => { expect(resolveMarkdownHref("", "./img.png")).toBe("img.png"); }); test("subdir/file", () => { expect(resolveMarkdownHref("", "subdir/img.png")).toBe("subdir/img.png"); }); test("./subdir/file", () => { expect(resolveMarkdownHref("", "./subdir/img.png")).toBe("subdir/img.png"); }); test("../ from root clamps to root", () => { // URL API resolves http://x/../img.png → http://x/img.png expect(resolveMarkdownHref("", "../img.png")).toBe("img.png"); }); }); describe("path-relative from one-level dir (dir = 'docs')", () => { test("bare filename", () => { expect(resolveMarkdownHref("docs", "img.png")).toBe("docs/img.png"); }); test("./filename", () => { expect(resolveMarkdownHref("docs", "./img.png")).toBe("docs/img.png"); }); test("../ traversal to root", () => { expect(resolveMarkdownHref("docs", "../img.png")).toBe("img.png"); }); test("subdir/file", () => { expect(resolveMarkdownHref("docs", "subdir/img.png")).toBe("docs/subdir/img.png"); }); test("./subdir/file", () => { expect(resolveMarkdownHref("docs", "./subdir/img.png")).toBe("docs/subdir/img.png"); }); }); describe("path-relative from nested dir (dir = 'docs/guide')", () => { test("bare filename", () => { expect(resolveMarkdownHref("docs/guide", "img.png")).toBe("docs/guide/img.png"); }); test("one level up", () => { expect(resolveMarkdownHref("docs/guide", "../img.png")).toBe("docs/img.png"); }); test("two levels up", () => { expect(resolveMarkdownHref("docs/guide", "../../img.png")).toBe("img.png"); }); test("sibling directory", () => { expect(resolveMarkdownHref("docs/guide", "../assets/img.png")).toBe("docs/assets/img.png"); }); test("./filename", () => { expect(resolveMarkdownHref("docs/guide", "./img.png")).toBe("docs/guide/img.png"); }); test("deeper subdir", () => { expect(resolveMarkdownHref("docs/guide", "sub/img.png")).toBe("docs/guide/sub/img.png"); }); }); }); describe("renderMarkdown", () => { test("GFM table with column alignment", () => { expect(renderMarkdown("| a | b |\n|---|:-:|\n| 1 | 2 |")).toBe( '
| a | b |
|---|---|
| 1 | 2 |
gone
see https://example.com ok
\n', ); }); test("fenced code keeps its language class", () => { expect(renderMarkdown("```js\nlet x=1;\n```")).toBe( 'let x=1;\n\n',
);
});
test("details/summary survive sanitization", () => {
expect(renderMarkdown("r' +
' abs' +
' ext' +
' an' +
' 