diff --git a/app/utils/markdown-export.test.ts b/app/utils/markdown-export.test.ts index 9868085..60ef3e6 100644 --- a/app/utils/markdown-export.test.ts +++ b/app/utils/markdown-export.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import { buildMarkdownExportFileName, + downloadMarkdownFile, normalizeMarkdownImageUrls, } from "./markdown-export"; @@ -55,3 +56,126 @@ describe("buildMarkdownExportFileName", () => { 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"); + } + } + }); +});