import { act, assertEquals, cleanup, render } from "./setup.ts"; import { EditorMode } from "../../islands/Editor.tsx"; import Editor from "../../islands/Editor.tsx"; function setupMocks() { const clickSpy: { href: string; download: string }[] = []; const createObjectURLCalls: string[] = []; let revokeObjectURLCalled = false; const originalCreateElement = document.createElement.bind(document); document.createElement = (tag: string) => { const el = originalCreateElement(tag) as HTMLAnchorElement; if (tag === "a") { el.click = () => { clickSpy.push({ href: el.href, download: el.download }); }; } return el; }; const origCreateObjectURL = URL.createObjectURL; const origRevokeObjectURL = URL.revokeObjectURL; URL.createObjectURL = () => { createObjectURLCalls.push("called"); return "blob:fake-url"; }; URL.revokeObjectURL = () => { revokeObjectURLCalled = true; }; const origFetch = globalThis.fetch; globalThis.fetch = (() => Promise.resolve( new Response("/* mock css */", { status: 200, headers: { "Content-Type": "text/css" }, }), )) as typeof fetch; const listeners: { type: string; listener: EventListener }[] = []; const origAddEventListener = globalThis.addEventListener; const origRemoveEventListener = globalThis.removeEventListener; globalThis.addEventListener = ( type: string, listener: EventListenerOrEventListenerObject, ) => { listeners.push({ type, listener: listener as EventListener, }); return origAddEventListener.call(globalThis, type, listener); }; globalThis.removeEventListener = ( type: string, listener: EventListenerOrEventListenerObject, ) => { const idx = listeners.findIndex((l) => l.type === type && l.listener === (listener as EventListener) ); if (idx !== -1) listeners.splice(idx, 1); return origRemoveEventListener.call(globalThis, type, listener); }; const emitEvent = (type: string, detail: string) => { const match = listeners.filter((l) => l.type === type); for (const { listener } of match) { listener({ type, detail } as unknown as Event); } }; return { clickSpy, createObjectURLCalls, get revokeObjectURLCalled() { return revokeObjectURLCalled; }, emitEvent, getListenerCount(type: string) { return listeners.filter((l) => l.type === type).length; }, restore() { document.createElement = originalCreateElement; URL.createObjectURL = origCreateObjectURL; URL.revokeObjectURL = origRevokeObjectURL; globalThis.fetch = origFetch; globalThis.addEventListener = origAddEventListener; globalThis.removeEventListener = origRemoveEventListener; }, }; } Deno.test({ name: "Editor - DownloadRequest event triggers download with correct filename", fn() { const mocks = setupMocks(); render(); act(() => { mocks.emitEvent("DownloadRequest", "My Post"); }); assertEquals(mocks.clickSpy.length, 1); assertEquals(mocks.clickSpy[0].download, "My Post.md"); assertEquals(mocks.createObjectURLCalls.length, 1); assertEquals(mocks.revokeObjectURLCalled, true); mocks.restore(); cleanup(); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "Editor - DownloadRequest uses Untitled for empty title", fn() { const mocks = setupMocks(); render( , ); act(() => { mocks.emitEvent("DownloadRequest", ""); }); assertEquals(mocks.clickSpy.length, 1); assertEquals(mocks.clickSpy[0].download, "Untitled.md"); mocks.restore(); cleanup(); }, sanitizeResources: false, sanitizeOps: false, }); Deno.test({ name: "Editor - removes DownloadRequest listener on unmount", fn() { const mocks = setupMocks(); const { unmount } = render( , ); assertEquals(mocks.getListenerCount("DownloadRequest"), 1); act(() => { unmount(); }); assertEquals(mocks.getListenerCount("DownloadRequest"), 0); mocks.restore(); cleanup(); }, sanitizeResources: false, sanitizeOps: false, });