166 lines
5.9 KiB
TypeScript
166 lines
5.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { createProductionHandlers, type ProdManifest } from "../src/prod.ts";
|
|
|
|
const manifest: ProdManifest = {
|
|
pages: [
|
|
{
|
|
raw: "/",
|
|
mod: { default: () => "<main>Production artifact</main>", meta: { title: "Prod" } },
|
|
},
|
|
{
|
|
raw: "/partial",
|
|
staticShell:
|
|
'<main>Build shell<template data-wrn-dynamic-placeholder="wrn-region-0"></template></main>',
|
|
mod: {
|
|
default: () =>
|
|
'<main>Request shell<wrn-dynamic-region data-wrn-dynamic="true"><strong>User 42</strong></wrn-dynamic-region></main>',
|
|
meta: { title: "Partial" },
|
|
__wrnexusRender: "partial-static",
|
|
},
|
|
},
|
|
{
|
|
raw: "/async",
|
|
mod: {
|
|
default: () => "<main>Async page</main>",
|
|
meta: { title: "Async" },
|
|
__wrnexusClientLoad: async () => ({ users: [{ id: 1, name: "Ada" }] }),
|
|
},
|
|
},
|
|
],
|
|
api: [],
|
|
realtime: [],
|
|
middleware: [],
|
|
components: [],
|
|
layouts: [],
|
|
};
|
|
|
|
const server = { upgrade: () => false } as never;
|
|
|
|
test("supervised production runtime injects reconnecting DOM-morph support", async () => {
|
|
const handlers = createProductionHandlers(manifest, { developmentRuntime: true });
|
|
const response = await handlers.fetch(new Request("http://localhost/"), server);
|
|
expect(response).toBeInstanceOf(Response);
|
|
expect(await (response as Response).text()).toContain("/__wrnexus/hmr");
|
|
});
|
|
|
|
test("production streams request regions into the build-time static shell", async () => {
|
|
const handlers = createProductionHandlers(manifest, {});
|
|
const response = (await handlers.fetch(
|
|
new Request("http://localhost/partial"),
|
|
server,
|
|
)) as Response;
|
|
const html = await response.text();
|
|
expect(response.headers.get("x-wrnexus-static-shell")).toBe("build");
|
|
expect(html).toContain("Build shell");
|
|
expect(html).not.toContain("Request shell");
|
|
expect(html).toContain("User 42");
|
|
});
|
|
|
|
test("normal production output remains free of development HMR", async () => {
|
|
const handlers = createProductionHandlers(manifest, {});
|
|
const response = await handlers.fetch(new Request("http://localhost/"), server);
|
|
const html = await (response as Response).text();
|
|
expect(html).not.toContain("/__wrnexus/hmr");
|
|
expect(html).toContain('data-wrn-route-params="{}"');
|
|
});
|
|
|
|
test("production client-load endpoint returns only the requested named result", async () => {
|
|
const handlers = createProductionHandlers(manifest, {});
|
|
const response = (await handlers.fetch(
|
|
new Request("http://localhost/__wrnexus/client-load?route=%2Fasync&name=users"),
|
|
server,
|
|
)) as Response;
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("cache-control")).toBe("private, no-store");
|
|
expect(await response.json()).toEqual({ data: [{ id: 1, name: "Ada" }] });
|
|
});
|
|
|
|
test("production prefers fast gzip for dynamic HTML and cached Brotli for immutable assets", async () => {
|
|
const largeManifest: ProdManifest = {
|
|
...manifest,
|
|
pages: [
|
|
{
|
|
raw: "/large",
|
|
mod: { default: () => `<main>${"fast production ".repeat(500)}</main>` },
|
|
},
|
|
],
|
|
};
|
|
const directory = mkdtempSync(join(tmpdir(), "wrnexus-prod-assets-"));
|
|
const assetPath = join(directory, "app.js");
|
|
writeFileSync(assetPath, "const production = true;\n".repeat(500));
|
|
const handlers = createProductionHandlers(largeManifest, {
|
|
pluginAssets: {
|
|
"/assets/app.js": {
|
|
path: assetPath,
|
|
contentType: "text/javascript; charset=utf-8",
|
|
},
|
|
},
|
|
});
|
|
const headers = { "accept-encoding": "br, gzip" };
|
|
const page = (await handlers.fetch(
|
|
new Request("http://localhost/large", { headers }),
|
|
server,
|
|
)) as Response;
|
|
expect(page.headers.get("content-encoding")).toBe("gzip");
|
|
|
|
const firstAsset = (await handlers.fetch(
|
|
new Request("http://localhost/assets/app.js", { headers }),
|
|
server,
|
|
)) as Response;
|
|
const secondAsset = (await handlers.fetch(
|
|
new Request("http://localhost/assets/app.js", { headers }),
|
|
server,
|
|
)) as Response;
|
|
expect(firstAsset.headers.get("content-encoding")).toBe("br");
|
|
expect(await secondAsset.arrayBuffer()).toEqual(await firstAsset.arrayBuffer());
|
|
|
|
const head = (await handlers.fetch(
|
|
new Request("http://localhost/assets/app.js", { method: "HEAD", headers }),
|
|
server,
|
|
)) as Response;
|
|
expect(head.headers.get("content-encoding")).toBeNull();
|
|
});
|
|
|
|
test("client-load preserves the active route query string", async () => {
|
|
const queryManifest: ProdManifest = {
|
|
...manifest,
|
|
pages: [
|
|
...manifest.pages,
|
|
{
|
|
raw: "/query",
|
|
mod: {
|
|
default: () => "<main>Query page</main>",
|
|
__wrnexusClientLoad: async (ctx: { url: URL }) => ({
|
|
identity: ctx.url.searchParams.get("as"),
|
|
}),
|
|
},
|
|
},
|
|
],
|
|
};
|
|
const handlers = createProductionHandlers(queryManifest, {});
|
|
const response = (await handlers.fetch(
|
|
new Request(
|
|
"http://localhost/__wrnexus/client-load?route=%2Fquery&search=%3Fas%3Drohan&name=identity",
|
|
),
|
|
server,
|
|
)) as Response;
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual({ data: "rohan" });
|
|
});
|
|
|
|
test("production serves generated WRN browser modules from the client directory", async () => {
|
|
const directory = mkdtempSync(join(tmpdir(), "wrnexus-client-modules-"));
|
|
writeFileSync(join(directory, "settings-abc123.mjs"), "export const ready = true;\n");
|
|
const handlers = createProductionHandlers(manifest, { clientModulesDir: directory });
|
|
const response = (await handlers.fetch(
|
|
new Request("http://localhost/__wrnexus/client/settings-abc123.mjs"),
|
|
server,
|
|
)) as Response;
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("content-type")).toContain("text/javascript");
|
|
expect(await response.text()).toContain("ready = true");
|
|
});
|