// @vitest-environment happy-dom import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { downloadBlob, downloadTextFile } from "./download"; // Capture the real appendChild before any spy replaces it, so the // happy-dom implementation is preserved when the appendChild spy calls // through (otherwise removeChild inside downloadTextFile can't find the // node we appended). const originalAppendChild = document.body.appendChild.bind(document.body); /** * `downloadTextFile` drives a hidden `` and clicks it. The * observable side effects we care about: * * 1. An `` element with the right `href` (object URL) and `download` * (filename) is appended to `document.body`. * 2. The element is removed from the DOM (cleanup). * 3. The object URL is revoked (memory hygiene). * * We don't try to assert on the browser's actual download behavior — that's * not observable from the test environment. We assert on the contract the * implementation honors. */ describe("downloadTextFile", () => { let createSpy: ReturnType; let revokeSpy: ReturnType; beforeEach(() => { createSpy = vi .spyOn(URL, "createObjectURL") .mockReturnValue("blob:test-url"); revokeSpy = vi.spyOn(URL, "revokeObjectURL"); }); afterEach(() => { vi.restoreAllMocks(); }); it("test_appends_anchor_with_object_url_and_filename_then_cleans_up", async () => { // Spy on appendChild to capture the anchor that's about to be appended // (downloadTextFile removes it again synchronously, so lastElementChild // is back to null by the time the call returns). const captured: HTMLAnchorElement[] = []; const appendSpy = vi .spyOn(document.body, "appendChild") .mockImplementation((node) => { if (node instanceof HTMLAnchorElement) captured.push(node); // Preserve the real append side-effect so removeChild later in // downloadTextFile can find the node. return originalAppendChild.call(document.body, node); }); downloadTextFile("claim-CLM-1.x12", "text/x12", "ISA*00*~"); expect(captured).toHaveLength(1); const a = captured[0]; expect(a.tagName).toBe("A"); expect(a.getAttribute("download")).toBe("claim-CLM-1.x12"); expect(a.getAttribute("href")).toBe("blob:test-url"); expect(a.style.display).toBe("none"); // removeChild ran synchronously inside downloadTextFile — the anchor // is no longer a child of document.body. expect(document.body.contains(a)).toBe(false); expect(appendSpy).toHaveBeenCalledTimes(1); expect(createSpy).toHaveBeenCalledTimes(1); // The revoke is scheduled via queueMicrotask — flush the microtask // queue so the assertion sees it. await Promise.resolve(); expect(revokeSpy).toHaveBeenCalledWith("blob:test-url"); }); it("test_blob_carries_supplied_mime_type_and_text_bytes", () => { downloadTextFile("a.txt", "text/plain", "hello world"); expect(createSpy).toHaveBeenCalledTimes(1); const blob = createSpy.mock.calls[0][0] as Blob; expect(blob.type).toBe("text/plain;charset=utf-8"); expect(blob.size).toBe("hello world".length); }); it("test_noop_when_document_undefined", () => { // SSR-style guard: with no `document`, the function returns silently // instead of throwing. const originalDocument = globalThis.document; // @ts-expect-error - intentionally strip document for this case delete (globalThis as { document?: Document }).document; try { expect(() => downloadTextFile("a.txt", "text/plain", "x"), ).not.toThrow(); } finally { (globalThis as { document?: Document }).document = originalDocument; } }); it("test_download_blob_uses_supplied_blob_and_filename", async () => { // The blob variant exists for binary payloads (ZIP, PDF, ...) where // the caller already has a Blob and just wants the browser to save // it. The anchor's href must point at the supplied blob's object // URL and the download attribute must be the filename. const captured: HTMLAnchorElement[] = []; const appendSpy = vi .spyOn(document.body, "appendChild") .mockImplementation((node) => { if (node instanceof HTMLAnchorElement) captured.push(node); return originalAppendChild.call(document.body, node); }); const blob = new Blob(["PK\u0003\u0004fake-zip"], { type: "application/zip", }); downloadBlob("resubmit-2-claims.zip", blob); expect(captured).toHaveLength(1); const a = captured[0]; expect(a.getAttribute("download")).toBe("resubmit-2-claims.zip"); expect(a.getAttribute("href")).toBe("blob:test-url"); expect(a.style.display).toBe("none"); expect(appendSpy).toHaveBeenCalledTimes(1); expect(createSpy).toHaveBeenCalledTimes(1); await Promise.resolve(); expect(revokeSpy).toHaveBeenCalledWith("blob:test-url"); }); });