import { afterEach, expect, test } from "bun:test"; import { Window } from "happy-dom"; import { firstOf, sameOriginSource } from "../src/convert/sources.ts"; import { configureConversion } from "../src/convert/config.ts"; import { handleConvert } from "../src/convert/service.ts"; import { converterFormats, convertUrl, resetConverterProbe } from "../src/convert/client.ts"; import { loadOptional } from "../src/optional/load.ts"; import { sanitizeHtml } from "../src/parse/html.ts"; import { waveformPeaks } from "../src/renderers/waveform.ts"; import { mountViewer } from "../src/runtime.ts"; import { registerRenderer } from "../src/registry.ts"; import { extractText } from "../src/text.ts"; afterEach(() => { configureConversion(undefined); resetConverterProbe(); }); const tick = () => new Promise((resolve) => setTimeout(resolve, 40)); test("runtime preserves overridden chrome and mounts inside the renderer presentation", async () => { const window = new Window(); const host = window.document.createElement("section"); window.document.body.append(host); // Trusted test fixture representing SSR output from an overridden component. host.innerHTML = '
'; const before = host.innerHTML; const dispose = mountViewer(host as unknown as HTMLElement, { name: "photo.png", blob: new Blob([new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])]), }); await tick(); expect(host.querySelector(".custom-toolbar [data-viewer-title]")?.textContent).toBe("photo.png"); expect( host.querySelector(".custom-content .custom-image [data-viewer-mount] img"), ).not.toBeNull(); dispose(); expect(host.innerHTML).toBe(before); await window.happyDOM.close(); }); test("filmstrip consumes the thumbnail endpoint and releases its object URLs", async () => { const window = new Window({ url: "https://thumb.test/demo" }); const host = window.document.createElement("section"); window.document.body.append(host); const saved = globalThis.fetch; const requests: string[] = []; globalThis.fetch = (async (input, init) => { const url = new URL(String(input)); requests.push(url.pathname); if (url.pathname.endsWith("/capabilities")) return Response.json({ formats: { ".psd": "image/png" } }); if (url.pathname.endsWith("/thumb")) return new Response(new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), { headers: { "content-type": "image/png" }, }); if (init?.method === "HEAD") return new Response(null, { headers: { "content-length": "8" } }); return new Response(new Uint8Array([56, 66, 80, 83, 0, 0, 0, 0])); }) as typeof fetch; let dispose: (() => void) | undefined; try { dispose = mountViewer(host as unknown as HTMLElement, [ { name: "art.psd", src: "https://thumb.test/art.psd" }, ]); await tick(); expect(requests).toContain("/api/viewer/thumb"); const image = host.querySelector(".wrn-viewer-file img") as unknown as HTMLImageElement; expect(image).not.toBeNull(); const url = image.src; dispose(); dispose = undefined; await expect(saved(url)).rejects.toThrow(); } finally { dispose?.(); globalThis.fetch = saved; await window.happyDOM.close(); } }); test("rotating links hit one stable cache entry, but denied access never reads it", async () => { let allowed = true; let conversions = 0; let cancellations = 0; configureConversion({ converters: [ { name: "stable-test", supports: () => true, outputType: () => "application/pdf", toPreview: async () => { conversions++; return { mime: "application/pdf", bytes: new TextEncoder().encode("%PDF") }; }, }, ], resolveSource: async () => allowed ? { identity: "reports:key:revision1", body: new ReadableStream({ cancel() { cancellations++; }, }), } : undefined, }); const request = (token: string) => new Request( `https://app.test/api/viewer/preview?name=a.docx&src=${encodeURIComponent("/api/viewer/file?token=" + token)}`, ); expect((await handleConvert(request("first"), "preview")).headers.get("x-viewer-cache")).toBe( "miss", ); expect((await handleConvert(request("rotated"), "preview")).headers.get("x-viewer-cache")).toBe( "hit", ); expect(conversions).toBe(1); expect(cancellations).toBeGreaterThanOrEqual(1); allowed = false; expect((await handleConvert(request("rotated"), "preview")).status).toBe(403); }); test("source composition falls through on undefined and stops on errors", async () => { const seen: string[] = []; const result = await firstOf( async () => { seen.push("upload"); return undefined; }, async () => { seen.push("static"); return { body: new Response("static").body!, identity: "static" }; }, )(new Request("https://app.test"), { src: "/a", name: "a" }); expect(result?.identity).toBe("static"); expect(seen).toEqual(["upload", "static"]); await expect( firstOf( async () => { throw new Error("denied"); }, async () => { throw new Error("must not run"); }, )(new Request("https://app.test"), { src: "/a", name: "a" }), ).rejects.toThrow("denied"); }); test("same-origin policy refuses viewer API links instead of bypassing private policy", async () => { const source = sameOriginSource(); for (const src of [ "/api/viewer/file?token=bad", "https://other.test/a", "https://user:pass@app.test/a", ]) expect(await source(new Request("https://app.test"), { src, name: "a.docx" })).toBeUndefined(); }); test("client helpers use the supplied document origin without global document", async () => { const fetched: string[] = []; const request = (async (url: string) => { fetched.push(String(url)); return Response.json({ formats: { ".docx": "application/pdf" } }); }) as unknown as typeof fetch; expect(await converterFormats({ baseURI: "https://one.test/page", fetch: request })).toEqual({ ".docx": "application/pdf", }); expect(fetched).toEqual(["https://one.test/api/viewer/capabilities"]); expect(convertUrl({ src: "/file", name: "a.docx" }, "text", "https://two.test/page")).toContain( "https://two.test/api/viewer/text", ); expect( await loadOptional<{ ok: boolean }>("viewer-sheet", "https://three.test/page", async (url) => { fetched.push(url); return { ok: true }; }), ).toEqual({ ok: true }); expect(fetched[1]).toBe("https://three.test/__wrnexus/assets/viewer-sheet.js"); }); test("HTML sanitizer preserves text and layout while removing active and external content", async () => { const window = new Window(); const html = sanitizeHtml( '

Hello

link

safe

', window as unknown as { DOMParser: new () => DOMParser }, ); expect(html).toContain("

Hello

"); for (const bad of [ "script", "onclick", "iframe", "javascript:", "https://evil", "onerror", " { expect( waveformPeaks([new Float32Array([0, 0.5, 0, 0]), new Float32Array([0, 0, -2, 0])], 2), ).toEqual([0.5, 1]); expect(waveformPeaks([new Float32Array(4)], 2)).toEqual([0, 0]); }); test("filmstrip switching disposes each active renderer and supports keyboard selection", async () => { const window = new Window(); const host = window.document.createElement("section"); window.document.body.append(host); const clean: string[] = []; const unregister = registerRenderer({ kind: "text", exts: [".txt"], tier: 2, component: "TextTest", mount: ({ parent, source }) => { parent.textContent = source.name; return () => { clean.push(source.name); }; }, }); const dispose = mountViewer(host as unknown as HTMLElement, [ { name: "one.txt", blob: new Blob(["one"]) }, { name: "two.txt", blob: new Blob(["two"]) }, ]); await tick(); const buttons = host.querySelectorAll(".wrn-viewer-file"); (buttons[1] as unknown as HTMLButtonElement).click(); await tick(); expect(clean).toEqual(["one.txt"]); expect(buttons[1]!.getAttribute("aria-pressed")).toBe("true"); buttons[1]!.dispatchEvent(new window.KeyboardEvent("keydown", { key: "Home", bubbles: true })); await tick(); expect(clean).toEqual(["one.txt", "two.txt"]); expect(buttons[0]!.getAttribute("aria-pressed")).toBe("true"); dispose(); expect(clean).toEqual(["one.txt", "two.txt", "one.txt"]); expect(host.children.length).toBe(0); unregister(); await window.happyDOM.close(); }); test("text extraction rejects non-text and enforces bounded output", async () => { const source = { src: "/a.docx", name: "a.docx" }; expect( await extractText(source, { baseURI: "https://app.test", fetch: (async () => new Response("search words", { headers: { "content-type": "text/plain" }, })) as unknown as typeof fetch, }), ).toBe("search words"); await expect( extractText(source, { baseURI: "https://app.test", fetch: (async () => new Response("%PDF", { headers: { "content-type": "application/pdf" }, })) as unknown as typeof fetch, }), ).rejects.toThrow("unavailable"); });