import { afterEach, describe, expect, it, vi } from "vitest"; import { decodeLocalImage } from "../../src/palette/decode"; const nativeUrl = globalThis.URL; afterEach(() => { vi.unstubAllGlobals(); }); const installImageElementDecoder = (loadImmediately: boolean) => { const createObjectURL = vi.fn(() => "blob:local-image"); const revokeObjectURL = vi.fn(); class MockUrl extends nativeUrl { static createObjectURL = createObjectURL; static revokeObjectURL = revokeObjectURL; } class MockImage { decoding = "auto"; naturalWidth = 320; naturalHeight = 180; onload: (() => void) | null = null; onerror: (() => void) | null = null; private source = ""; set src(value: string) { this.source = value; if (value && loadImmediately) queueMicrotask(() => this.onload?.()); } get src(): string { return this.source; } } vi.stubGlobal("URL", MockUrl); vi.stubGlobal("Image", MockImage); vi.stubGlobal("createImageBitmap", undefined); return { createObjectURL, revokeObjectURL }; }; describe("local image decoding", () => { it("revokes its object URL as soon as the image has decoded", async () => { const spies = installImageElementDecoder(true); const image = await decodeLocalImage( new File(["image"], "sample.png", { type: "image/png" }), ); expect(image).toMatchObject({ width: 320, height: 180 }); expect(spies.createObjectURL).toHaveBeenCalledOnce(); expect(spies.revokeObjectURL).toHaveBeenCalledWith("blob:local-image"); image.dispose(); }); it("revokes its object URL when decoding is cancelled", async () => { const spies = installImageElementDecoder(false); const controller = new AbortController(); const decoding = decodeLocalImage( new File(["image"], "sample.png", { type: "image/png" }), controller.signal, ); controller.abort(); await expect(decoding).rejects.toMatchObject({ name: "AbortError" }); expect(spies.revokeObjectURL).toHaveBeenCalledWith("blob:local-image"); }); });