markdown.test.ts
| 1 | import { describe, expect, test } from "bun:test"; |
| 2 | import { resolveMarkdownHref } from "../src/services/markdown.ts"; |
| 3 | |
| 4 | describe("resolveMarkdownHref", () => { |
| 5 | describe("protocol-absolute URLs (returns null)", () => { |
| 6 | test("http", () => { |
| 7 | expect(resolveMarkdownHref("", "http://example.com/img.png")).toBeNull(); |
| 8 | expect(resolveMarkdownHref("docs", "http://example.com/img.png")).toBeNull(); |
| 9 | }); |
| 10 | test("https", () => { |
| 11 | expect(resolveMarkdownHref("", "https://example.com/img.png")).toBeNull(); |
| 12 | }); |
| 13 | test("ftp", () => { |
| 14 | expect(resolveMarkdownHref("", "ftp://files.example.com/")).toBeNull(); |
| 15 | }); |
| 16 | test("mailto", () => { |
| 17 | expect(resolveMarkdownHref("", "mailto:foo@bar.com")).toBeNull(); |
| 18 | }); |
| 19 | test("data", () => { |
| 20 | expect(resolveMarkdownHref("", "data:image/png;base64,abc123")).toBeNull(); |
| 21 | }); |
| 22 | test("ssh", () => { |
| 23 | expect(resolveMarkdownHref("", "ssh://git@example.com/repo.git")).toBeNull(); |
| 24 | }); |
| 25 | test("custom protocol", () => { |
| 26 | expect(resolveMarkdownHref("", "myapp://open/something")).toBeNull(); |
| 27 | }); |
| 28 | }); |
| 29 | |
| 30 | describe("anchor links (returns null)", () => { |
| 31 | test("simple anchor", () => { |
| 32 | expect(resolveMarkdownHref("", "#section-heading")).toBeNull(); |
| 33 | }); |
| 34 | test("anchor with any dir", () => { |
| 35 | expect(resolveMarkdownHref("docs/guide", "#toc")).toBeNull(); |
| 36 | }); |
| 37 | test("bare hash", () => { |
| 38 | expect(resolveMarkdownHref("", "#")).toBeNull(); |
| 39 | }); |
| 40 | }); |
| 41 | |
| 42 | describe("root-relative URLs (strips leading /)", () => { |
| 43 | test("single file at root", () => { |
| 44 | expect(resolveMarkdownHref("", "/img.png")).toBe("img.png"); |
| 45 | }); |
| 46 | test("subdir file — dir is ignored", () => { |
| 47 | expect(resolveMarkdownHref("docs", "/subdir/img.png")).toBe("subdir/img.png"); |
| 48 | }); |
| 49 | test("deep path — any dir is ignored", () => { |
| 50 | expect(resolveMarkdownHref("x/y/z", "/a/b/c.png")).toBe("a/b/c.png"); |
| 51 | }); |
| 52 | test("root-relative with no filename (trailing slash)", () => { |
| 53 | expect(resolveMarkdownHref("docs", "/assets/")).toBe("assets/"); |
| 54 | }); |
| 55 | }); |
| 56 | |
| 57 | describe("path-relative from root dir (dir = '')", () => { |
| 58 | test("bare filename", () => { |
| 59 | expect(resolveMarkdownHref("", "img.png")).toBe("img.png"); |
| 60 | }); |
| 61 | test("./filename", () => { |
| 62 | expect(resolveMarkdownHref("", "./img.png")).toBe("img.png"); |
| 63 | }); |
| 64 | test("subdir/file", () => { |
| 65 | expect(resolveMarkdownHref("", "subdir/img.png")).toBe("subdir/img.png"); |
| 66 | }); |
| 67 | test("./subdir/file", () => { |
| 68 | expect(resolveMarkdownHref("", "./subdir/img.png")).toBe("subdir/img.png"); |
| 69 | }); |
| 70 | test("../ from root clamps to root", () => { |
| 71 | // URL API resolves http://x/../img.png → http://x/img.png |
| 72 | expect(resolveMarkdownHref("", "../img.png")).toBe("img.png"); |
| 73 | }); |
| 74 | }); |
| 75 | |
| 76 | describe("path-relative from one-level dir (dir = 'docs')", () => { |
| 77 | test("bare filename", () => { |
| 78 | expect(resolveMarkdownHref("docs", "img.png")).toBe("docs/img.png"); |
| 79 | }); |
| 80 | test("./filename", () => { |
| 81 | expect(resolveMarkdownHref("docs", "./img.png")).toBe("docs/img.png"); |
| 82 | }); |
| 83 | test("../ traversal to root", () => { |
| 84 | expect(resolveMarkdownHref("docs", "../img.png")).toBe("img.png"); |
| 85 | }); |
| 86 | test("subdir/file", () => { |
| 87 | expect(resolveMarkdownHref("docs", "subdir/img.png")).toBe("docs/subdir/img.png"); |
| 88 | }); |
| 89 | test("./subdir/file", () => { |
| 90 | expect(resolveMarkdownHref("docs", "./subdir/img.png")).toBe("docs/subdir/img.png"); |
| 91 | }); |
| 92 | }); |
| 93 | |
| 94 | describe("path-relative from nested dir (dir = 'docs/guide')", () => { |
| 95 | test("bare filename", () => { |
| 96 | expect(resolveMarkdownHref("docs/guide", "img.png")).toBe("docs/guide/img.png"); |
| 97 | }); |
| 98 | test("one level up", () => { |
| 99 | expect(resolveMarkdownHref("docs/guide", "../img.png")).toBe("docs/img.png"); |
| 100 | }); |
| 101 | test("two levels up", () => { |
| 102 | expect(resolveMarkdownHref("docs/guide", "../../img.png")).toBe("img.png"); |
| 103 | }); |
| 104 | test("sibling directory", () => { |
| 105 | expect(resolveMarkdownHref("docs/guide", "../assets/img.png")).toBe("docs/assets/img.png"); |
| 106 | }); |
| 107 | test("./filename", () => { |
| 108 | expect(resolveMarkdownHref("docs/guide", "./img.png")).toBe("docs/guide/img.png"); |
| 109 | }); |
| 110 | test("deeper subdir", () => { |
| 111 | expect(resolveMarkdownHref("docs/guide", "sub/img.png")).toBe("docs/guide/sub/img.png"); |
| 112 | }); |
| 113 | }); |
| 114 | }); |
| 115 |