import { afterEach, expect, rs, test } from "@rstest/core"; import { prepareHtmlPreview } from "@/core/artifacts/html-assets"; const url = "https://app.example/api/threads/chat/artifacts/mnt/user-data/outputs/report.html"; const request = rs.spyOn(globalThis, "fetch"); afterEach(() => { request.mockReset(); }); test("preserves missing and legacy doctypes and comments outside the HTML element", async () => { for (const declaration of [ "", "", '', '', ]) { const content = `${declaration}

Report

`; const result = await prepareHtmlPreview(content, url); const expected = new DOMParser().parseFromString(content, "text/html"); const actual = new DOMParser().parseFromString(result.content, "text/html"); expect(actual.doctype?.name).toBe(expected.doctype?.name); expect(actual.doctype?.publicId).toBe(expected.doctype?.publicId); expect(actual.doctype?.systemId).toBe(expected.doctype?.systemId); expect(result.content).toContain(""); expect(result.content).toContain(""); expect(actual.querySelector("base")?.getAttribute("href")).toBe( url.replace("report.html", ""), ); } }); test("rewrites URLs inside SVG style elements and inline style attributes", async () => { request.mockImplementation(async () => new Response("image")); // happy-dom's HTML parser drops SVG children; create the namespace nodes // directly. The real parser is also exercised by the browser regression. const fixture = document.implementation.createHTMLDocument(""); const svg = fixture.createElementNS("http://www.w3.org/2000/svg", "svg"); const style = fixture.createElementNS("http://www.w3.org/2000/svg", "style"); style.textContent = ".chart{fill:url(chart.svg#paint)}"; const rect = fixture.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("style", "filter:url(filters.svg#blur)"); svg.append(style, rect); fixture.body.append(svg); const parse = rs .spyOn(DOMParser.prototype, "parseFromString") .mockReturnValueOnce(fixture); const result = await prepareHtmlPreview( '', url, ); parse.mockRestore(); expect( request.mock.calls.map(([input]) => typeof input === "string" ? input : input instanceof URL ? input.href : input.url, ), ).toEqual([ url.replace("report.html", "chart.svg"), url.replace("report.html", "filters.svg"), ]); expect(style.textContent).toContain("data:"); expect(rect.getAttribute("style")).toContain("data:"); expect(result.content).toContain("#paint"); expect(result.content).toContain("#blur"); }); test("authenticates images, scripts and nested CSS assets in the parent", async () => { request.mockImplementation(async (input) => { const pathname = new URL( typeof input === "string" ? input : input instanceof URL ? input.href : input.url, ).pathname; if (pathname.endsWith("main.css")) return new Response( '@import "nested.css" screen; .hero{background:url("chart%2520.png")}', ); if (pathname.endsWith("nested.css")) return new Response(".icon{background:url(../uploads/icon.png)}"); return new Response( pathname.endsWith("app.js") ? "window.loaded=true" : "image", ); }); const result = await prepareHtmlPreview( '', url, ); expect(result.failedResources).toBe(0); expect( request.mock.calls.map(([input]) => typeof input === "string" ? input : input instanceof URL ? input.href : input.url, ), ).toEqual([ url.replace("report.html", "main.css"), url.replace("report.html", "nested.css"), url.replace("outputs/report.html", "uploads/icon.png"), url.replace("report.html", "chart%2520.png"), url.replace("report.html", "app.js"), ]); expect(result.content).not.toContain('src="chart%2520.png"'); const document = new DOMParser().parseFromString(result.content, "text/html"); const css = atob( document.querySelector("link")!.getAttribute("href")!.split(",")[1]!, ); expect(css).toContain("data:text/css"); expect( request.mock.calls.every(([, init]) => init?.redirect === "error"), ).toBe(true); }); test("never fetches another thread, external origin, non-artifact route or encoded separators", async () => { const result = await prepareHtmlPreview( ``, url, ); expect(request).not.toHaveBeenCalled(); expect(result.content).toContain("https://evil.example/image"); }); test("bounds cycles and failed resources without losing the HTML", async () => { request.mockImplementation(async (input) => { if ( (typeof input === "string" ? input : input instanceof URL ? input.href : input.url ).endsWith("a.css") ) return new Response('@import "b.css";'); if ( (typeof input === "string" ? input : input instanceof URL ? input.href : input.url ).endsWith("b.css") ) return new Response('@import "a.css";'); return new Response("missing", { status: 404 }); }); const result = await prepareHtmlPreview( '

Report

', url, ); expect(request).toHaveBeenCalledTimes(3); expect(result.failedResources).toBe(2); expect(result.content).toContain("Report"); expect(result.content).not.toContain('src="missing.png"'); }); test("rewrites inline CSS and ignores commented URL text", async () => { request.mockResolvedValue(new Response("image")); const result = await prepareHtmlPreview( '
', url, ); expect(request).toHaveBeenCalledTimes(1); expect(result.content).toContain("/*url(no.png)*/"); expect(result.content).toContain("data:"); }); test("leaves public demos and previews without a resource URL on their existing path", async () => { const content = ''; for (const value of [ undefined, "https://app.example/demo/threads/chat/report.html", ]) { const result = await prepareHtmlPreview(content, value); expect(result.content).toContain(content); expect(request).not.toHaveBeenCalled(); } }); test("preserves external bases and escaped external CSS references", async () => { const result = await prepareHtmlPreview( '', url, ); expect(request).not.toHaveBeenCalled(); expect(result.content).toContain('src="image.png"'); expect(result.content).toContain("a\\)b.png"); }); test("supports responsive pictures, SVG fragments and CSS escaped filenames", async () => { request.mockImplementation(async () => new Response("image")); const result = await prepareHtmlPreview( '', url, ); expect( request.mock.calls.map(([input]) => typeof input === "string" ? input : input instanceof URL ? input.href : input.url, ), ).toEqual([ url.replace("report.html", "one.png"), url.replace("report.html", "two.png"), url.replace("report.html", "icons.svg"), url.replace("report.html", "chart%20image.png"), ]); expect(result.content).toContain("data:image/png;base64,AA 1x"); expect(result.content).toContain("#chart"); expect(result.failedResources).toBe(0); }); test("caps distinct resources and CSS import depth", async () => { request.mockImplementation(async () => new Response("image")); const result = await prepareHtmlPreview( Array.from({ length: 40 }, (_, i) => ``).join(""), url, ); expect(request).toHaveBeenCalledTimes(32); expect(result.failedResources).toBe(8); request.mockClear(); request.mockImplementation(async (input) => { const name = new URL( typeof input === "string" ? input : input instanceof URL ? input.href : input.url, ).pathname .split("/") .pop()!; return new Response(`@import "${Number.parseInt(name) + 1}.css";`); }); const nested = await prepareHtmlPreview( '', url, ); expect(request).toHaveBeenCalledTimes(9); expect(nested.failedResources).toBe(1); }); test("caps streamed resource bytes and the aggregate budget", async () => { request.mockImplementation( async () => new Response(new Uint8Array(4 * 1024 * 1024 + 1)), ); const oversized = await prepareHtmlPreview('', url); expect(oversized.failedResources).toBe(1); expect(oversized.content).toContain('src="data:,"'); request.mockClear(); request.mockImplementation( async () => new Response(new Uint8Array(4 * 1024 * 1024)), ); const aggregate = await prepareHtmlPreview( Array.from({ length: 5 }, (_, i) => ``).join(""), url, ); expect(aggregate.failedResources).toBe(1); expect(request).toHaveBeenCalledTimes(4); }); test("bounds repeated data URLs and preserves out-of-route CSS resolution", async () => { request.mockImplementation( async () => new Response(new Uint8Array(4 * 1024 * 1024)), ); const repeated = await prepareHtmlPreview( Array.from({ length: 8 }, () => '').join(""), url, ); expect(request).toHaveBeenCalledTimes(1); expect(repeated.failedResources).toBeGreaterThan(0); expect(repeated.content.length).toBeLessThan(24 * 1024 * 1024); request.mockClear(); request.mockResolvedValue( new Response(".x{background:url(/public/chart.png)}"), ); const css = await prepareHtmlPreview( '', url, ); const document = new DOMParser().parseFromString(css.content, "text/html"); const stylesheet = atob( document.querySelector("link")!.getAttribute("href")!.split(",")[1]!, ); expect(stylesheet).toContain("https://app.example/public/chart.png"); expect(request).toHaveBeenCalledTimes(1); }); test("quotes absolute CSS URLs that replace escaped unquoted references", async () => { request.mockResolvedValue( new Response(".chart{background:url(//cdn.example/chart\\(1\\).png)}"), ); const result = await prepareHtmlPreview( '', url, ); const document = new DOMParser().parseFromString(result.content, "text/html"); const stylesheet = atob( document.querySelector("link")!.getAttribute("href")!.split(",")[1]!, ); expect(stylesheet).toContain('url("https://cdn.example/chart(1).png")'); expect(result.failedResources).toBe(0); }); test("aborts late responses before exposing prepared HTML", async () => { const controller = new AbortController(); request.mockImplementation(async (input) => { if ( (typeof input === "string" ? input : input instanceof URL ? input.href : input.url ).endsWith("late.png") ) controller.abort(); return new Response("image"); }); await expect( prepareHtmlPreview( '', url, controller.signal, ), ).rejects.toThrow(); }); test("validates original integrity before rewriting resources", async () => { request.mockImplementation(async () => new Response("body")); const result = await prepareHtmlPreview( '', url, ); expect(request.mock.calls.map(([, init]) => init?.integrity)).toEqual([ "sha256-original", "sha256-script", ]); expect(result.content).not.toContain("sha256-original"); expect(result.content).not.toContain("sha256-script"); expect(result.content).toContain("sha256-external"); }); test("never reuses an unverified resource for a distinct integrity constraint", async () => { request.mockImplementation(async (_input, init) => { if (init?.integrity === "sha256-wrong") throw new TypeError("Integrity mismatch"); return new Response("image"); }); const result = await prepareHtmlPreview( '', url, ); expect(request.mock.calls.map(([, init]) => init?.integrity)).toEqual([ undefined, "sha256-valid", "sha256-wrong", ]); expect(result.failedResources).toBe(1); expect(result.content).toContain('src="data:,"'); }); test("returns a partial preview when the preparation deadline expires", async () => { rs.useFakeTimers(); try { request.mockImplementation( (_input, init) => new Promise((_resolve, reject) => { init?.signal?.addEventListener( "abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true }, ); }), ); const pending = prepareHtmlPreview( '

Report

', url, ); await rs.advanceTimersByTimeAsync(10_000); const result = await pending; expect(result.failedResources).toBe(2); expect(request).toHaveBeenCalledTimes(1); expect(result.content).toContain("Report"); } finally { rs.useRealTimers(); } });