Files
WRNexusJS/packages/dev-server/test/structured-component-props.test.ts
T

200 lines
6.9 KiB
TypeScript

import { expect, test } from "bun:test";
import { createProductionHandlers } from "../src/prod.ts";
test("component resolution preserves JSON user/permission props and drops unresolved bindings", async () => {
let received: Record<string, string> = {};
const handlers = createProductionHandlers(
{
pages: [
{
raw: "/",
mod: {
default: () =>
'<div data-component="account-shell" identity="{&quot;displayName&quot;:&quot;Alice&quot;}" permissions="{&quot;read&quot;:true,&quot;update&quot;:false}" preferences="{&quot;theme&quot;:&quot;dark&quot;}" pending="{account.pending}" value="{{order}}"><p>Account content</p></div>',
},
},
],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "AccountShell",
mod: {
render: (props: Record<string, string>) => {
received = props;
const identity = JSON.parse(props.identity ?? "{}");
const permissions = JSON.parse(props.permissions ?? "{}");
return permissions.read
? "<main>" + identity.displayName + "<slot /></main>"
: "<main>Denied</main>";
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
expect(response?.status).toBe(200);
const html = await response!.text();
expect(html).toContain("Alice");
expect(html).toContain("Account content");
expect(JSON.parse(received.permissions!)).toEqual({ read: true, update: false });
expect(JSON.parse(received.preferences!)).toEqual({ theme: "dark" });
expect(received.pending).toBeUndefined();
expect(received.value).toBe("{{order}}");
});
test("recursive inert components reuse request-local control prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="Tree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "Tree",
mod: {
render: () => {
renders++;
const control = encodeControlTemplate([
{ cond: "open", body: '<div data-component="Tree"></div>' },
]);
return `<div data-scope="open: false"><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
for (let index = 0; index < 2; index++) {
const before = renders;
const response = await handlers.fetch(new Request("http://localhost/"), {
upgrade: () => false,
});
expect(response?.status).toBe(200);
const html = await response!.text();
expect(renders - before).toBeLessThanOrEqual(3);
expect(html.length).toBeLessThan(10000);
expect(html).toContain("wrn-controls-");
}
});
test("recursive mixed prop bindings do not grow inert prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [
{ raw: "/", mod: { default: () => '<div data-component="Branch" prefix="live"></div>' } },
],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "Branch",
mod: {
render: (props: Record<string, string>) => {
renders++;
const prefix = props.prefix || "";
const binding = "[&quot;prefix&quot;,&quot;{prefix}-child&quot;]";
const body = `<div data-component="Branch" prefix="${prefix}-child" data-wrn-prop-bind-0="${binding}"></div>`;
const control = encodeControlTemplate([{ cond: "open", body }]);
return `<div data-scope="open: false, prefix: '${prefix}'"><p>${prefix}</p><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
const html = await response!.text();
expect(renders).toBeLessThanOrEqual(3);
expect(html.length).toBeLessThan(10000);
expect(html).toContain("<p>live</p>");
});
test("loop-local bridge metadata does not multiply recursive prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="LoopTree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "LoopTree",
mod: {
render: () => {
renders++;
const locals = Buffer.from(JSON.stringify({ row: renders })).toString("base64");
const control = encodeControlTemplate([
{
cond: "open",
body: `<div data-component="LoopTree" data-wrn-loop-locals="${locals}"></div>`,
},
]);
return `<div data-scope="open: false"><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
expect(response?.status).toBe(200);
expect(renders).toBeLessThanOrEqual(3);
expect((await response!.text()).length).toBeLessThan(10000);
});
test("inline compiler controls in recursive prototypes are hoisted once", async () => {
let renders = 0;
const encoded = Buffer.from(
JSON.stringify([
{
cond: "open",
body: '<div data-component="InlineTree"></div><div data-component="InlineTree"></div>',
},
]),
).toString("base64");
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="InlineTree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "InlineTree",
mod: {
render: () => {
renders++;
return `<div data-scope="open: false"><template data-wrn-if="${encoded}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
expect(response?.status).toBe(200);
expect(renders).toBeLessThanOrEqual(3);
expect((await response!.text()).length).toBeLessThan(10000);
});