import { describe, expect, test } from "bun:test"; import { buildMarkdownExportFileName, downloadMarkdownFile, normalizeMarkdownImageUrls, } from "./markdown-export"; describe("normalizeMarkdownImageUrls", () => { test("converts /static/media image links to absolute URLs", () => { const markdown = "![cover](/static/media/posts/cover.png)"; const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); expect(result).toBe("![cover](https://example.com/static/media/posts/cover.png)"); }); test("converts other site-relative image links to absolute URLs", () => { const markdown = "![hero](/images/a.png)"; const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); expect(result).toBe("![hero](https://example.com/images/a.png)"); }); test("keeps absolute http/https image links unchanged", () => { const markdown = [ "![a](http://cdn.example.com/a.png)", "![b](https://cdn.example.com/b.png)", ].join("\n"); const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); expect(result).toBe(markdown); }); test("keeps protocol-relative and data URI image links unchanged", () => { const markdown = [ "![protocol](//cdn.example.com/p.png)", "![inline](data:image/png;base64,AAAB)", ].join("\n"); const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); expect(result).toBe(markdown); }); test("does not change normal markdown links", () => { const markdown = "[read more](/static/media/posts/cover.png)"; const result = normalizeMarkdownImageUrls(markdown, "https://example.com"); expect(result).toBe(markdown); }); }); describe("buildMarkdownExportFileName", () => { test("prefers slug when slug is not empty", () => { const result = buildMarkdownExportFileName({ slug: "hello-world", id: 42 }); expect(result).toBe("hello-world.md"); }); test("falls back to post-.md when slug is empty", () => { const result = buildMarkdownExportFileName({ slug: "", id: 42 }); expect(result).toBe("post-42.md"); }); }); describe("downloadMarkdownFile", () => { test("does not throw when window/document are unavailable", () => { const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window"); const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document"); Object.defineProperty(globalThis, "window", { value: undefined, configurable: true, writable: true, }); Object.defineProperty(globalThis, "document", { value: undefined, configurable: true, writable: true, }); try { expect(() => downloadMarkdownFile("post.md", "# hello")).not.toThrow(); } finally { if (windowDescriptor) { Object.defineProperty(globalThis, "window", windowDescriptor); } else { Reflect.deleteProperty(globalThis, "window"); } if (documentDescriptor) { Object.defineProperty(globalThis, "document", documentDescriptor); } else { Reflect.deleteProperty(globalThis, "document"); } } }); test("creates object URL, clicks anchor, and revokes URL in browser flow", () => { const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window"); const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document"); const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL"); let createElementArg = ""; let appendedNode: unknown; let clicked = false; let removed = false; let revokedArg = ""; const blobUrl = "blob:test-url"; const anchor = { href: "", download: "", style: { display: "" }, click: () => { clicked = true; }, remove: () => { removed = true; }, }; Object.defineProperty(globalThis, "window", { value: {}, configurable: true, writable: true, }); Object.defineProperty(globalThis, "document", { value: { createElement: (tag: string) => { createElementArg = tag; return anchor; }, body: { appendChild: (node: unknown) => { appendedNode = node; }, }, }, configurable: true, writable: true, }); Object.defineProperty(globalThis, "URL", { value: { createObjectURL: (_blob: Blob) => blobUrl, revokeObjectURL: (url: string) => { revokedArg = url; }, }, configurable: true, writable: true, }); try { downloadMarkdownFile("hello.md", "# hello"); expect(createElementArg).toBe("a"); expect(appendedNode).toBe(anchor); expect(anchor.href).toBe(blobUrl); expect(anchor.download).toBe("hello.md"); expect(anchor.style.display).toBe("none"); expect(clicked).toBe(true); expect(removed).toBe(true); expect(revokedArg).toBe(blobUrl); } finally { if (windowDescriptor) { Object.defineProperty(globalThis, "window", windowDescriptor); } else { Reflect.deleteProperty(globalThis, "window"); } if (documentDescriptor) { Object.defineProperty(globalThis, "document", documentDescriptor); } else { Reflect.deleteProperty(globalThis, "document"); } if (urlDescriptor) { Object.defineProperty(globalThis, "URL", urlDescriptor); } else { Reflect.deleteProperty(globalThis, "URL"); } } }); test("still revokes object URL when anchor click throws", () => { const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window"); const documentDescriptor = Object.getOwnPropertyDescriptor(globalThis, "document"); const urlDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL"); let revokedArg = ""; let removed = false; const blobUrl = "blob:error-case"; const clickError = new Error("click failed"); const anchor = { href: "", download: "", style: { display: "" }, click: () => { throw clickError; }, remove: () => { removed = true; }, }; Object.defineProperty(globalThis, "window", { value: {}, configurable: true, writable: true, }); Object.defineProperty(globalThis, "document", { value: { createElement: (_tag: string) => anchor, body: { appendChild: (_node: unknown) => undefined, }, }, configurable: true, writable: true, }); Object.defineProperty(globalThis, "URL", { value: { createObjectURL: (_blob: Blob) => blobUrl, revokeObjectURL: (url: string) => { revokedArg = url; }, }, configurable: true, writable: true, }); try { expect(() => downloadMarkdownFile("broken.md", "# broken")).toThrow("click failed"); expect(removed).toBe(true); expect(revokedArg).toBe(blobUrl); } finally { if (windowDescriptor) { Object.defineProperty(globalThis, "window", windowDescriptor); } else { Reflect.deleteProperty(globalThis, "window"); } if (documentDescriptor) { Object.defineProperty(globalThis, "document", documentDescriptor); } else { Reflect.deleteProperty(globalThis, "document"); } if (urlDescriptor) { Object.defineProperty(globalThis, "URL", urlDescriptor); } else { Reflect.deleteProperty(globalThis, "URL"); } } }); });