diff --git a/app/utils/markdown-export.test.ts b/app/utils/markdown-export.test.ts index f754c8e..3cd6950 100644 --- a/app/utils/markdown-export.test.ts +++ b/app/utils/markdown-export.test.ts @@ -185,4 +185,79 @@ describe("downloadMarkdownFile", () => { } } }); + + 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"); + } + } + }); }); diff --git a/app/utils/markdown-export.ts b/app/utils/markdown-export.ts index 72be5db..db49adc 100644 --- a/app/utils/markdown-export.ts +++ b/app/utils/markdown-export.ts @@ -36,11 +36,14 @@ export function downloadMarkdownFile(filename: string, content: string): void { const blob = new Blob([content], { type: "text/markdown;charset=utf-8" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); - anchor.href = url; - anchor.download = filename; - anchor.style.display = "none"; - document.body.appendChild(anchor); - anchor.click(); - anchor.remove(); - URL.revokeObjectURL(url); + try { + anchor.href = url; + anchor.download = filename; + anchor.style.display = "none"; + document.body.appendChild(anchor); + anchor.click(); + } finally { + anchor.remove(); + URL.revokeObjectURL(url); + } }