91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { renderDocument, renderDocumentStream } from "../src/index.ts";
|
|
|
|
test("escapes metadata and script URLs while preserving trusted rendered body", () => {
|
|
const html = renderDocument({
|
|
meta: { title: '</title><script>alert("x")</script>', description: '" onload="x' },
|
|
body: "<main>trusted</main>",
|
|
scripts: ['"><script>alert(1)</script>'],
|
|
});
|
|
expect(html).not.toContain('</title><script>alert("x")</script>');
|
|
expect(html).toContain("</title>");
|
|
expect(html).toContain("<main>trusted</main>");
|
|
expect(html).not.toContain("<script>alert(1)</script>");
|
|
});
|
|
|
|
test("deduplicates runtime scripts and module preloads", () => {
|
|
const html = renderDocument({
|
|
meta: { title: "Page" },
|
|
body: "",
|
|
scripts: ["/app.js", "/app.js"],
|
|
});
|
|
expect(html.match(/rel="modulepreload"/g)).toHaveLength(1);
|
|
expect(html.match(/src="\/app\.js"/g)).toHaveLength(1);
|
|
});
|
|
|
|
test("always emits a document language and preserves an explicit language", () => {
|
|
const fallback = renderDocument({ meta: {}, body: "" });
|
|
expect(fallback).toContain('<html lang="en">');
|
|
|
|
const explicit = renderDocument({
|
|
meta: {},
|
|
body: "",
|
|
htmlAttrs: ' data-theme="dark" lang="fr"',
|
|
});
|
|
expect(explicit).toContain('<html data-theme="dark" lang="fr">');
|
|
expect(explicit).not.toContain('lang="en"');
|
|
});
|
|
|
|
test("merges framework head and scripts into an application document template", () => {
|
|
const html = renderDocument({
|
|
meta: { title: "Document layout" },
|
|
body: "",
|
|
htmlAttrs: ' data-theme="dark"',
|
|
scripts: ["/page.js"],
|
|
documentTemplate:
|
|
'<html data-ui-font="system"><head><meta name="custom" content="yes" /></head><body><div id="app">Page</div></body></html>',
|
|
});
|
|
expect(html).toStartWith("<!doctype html>");
|
|
expect(html).toContain('<html data-ui-font="system" data-theme="dark" lang="en">');
|
|
expect(html).toContain("<title>Document layout</title>");
|
|
expect(html).toContain('<meta name="custom" content="yes" />');
|
|
expect(html).toContain('<script type="module" src="/page.js"></script>');
|
|
expect(html.match(/<html/g)).toHaveLength(1);
|
|
expect(html.match(/<body/g)).toHaveLength(1);
|
|
});
|
|
|
|
test("streams async body chunks inside the document shell", async () => {
|
|
async function* body() {
|
|
yield "<h1>Shell</h1>";
|
|
yield "<p>Later</p>";
|
|
}
|
|
const html = await new Response(
|
|
renderDocumentStream({ meta: { title: "Stream" }, body: body() }),
|
|
).text();
|
|
expect(html).toContain('<div id="app"><h1>Shell</h1><p>Later</p></div>');
|
|
});
|
|
|
|
test("renders rich script metadata and deduplicates by source", () => {
|
|
const html = renderDocument({
|
|
meta: { title: "Runtime" },
|
|
body: '<main data-wrnexus-runtime="captcha"></main>',
|
|
scripts: [
|
|
{
|
|
src: "/__wrnexus/assets/captcha.abc.js",
|
|
type: "classic",
|
|
defer: true,
|
|
integrity: "sha256-test",
|
|
crossOrigin: "anonymous",
|
|
attributes: { "data-wrnexus-runtime-src": "captcha" },
|
|
},
|
|
{ src: "/__wrnexus/assets/captcha.abc.js", type: "classic" },
|
|
],
|
|
});
|
|
expect(html.match(/captcha\.abc\.js/g)).toHaveLength(1);
|
|
expect(html).toContain("defer");
|
|
expect(html).toContain('integrity="sha256-test"');
|
|
expect(html).toContain('crossorigin="anonymous"');
|
|
expect(html).toContain('data-wrnexus-runtime-src="captcha"');
|
|
expect(html).not.toContain('rel="modulepreload" href="/__wrnexus/assets/captcha.abc.js"');
|
|
});
|