Files
ClintchizandClaude Opus 5 20783699ac test(compiler): prove page loop locals render, not just emit
The existing tests assert the marker is emitted. This one executes the
generated module and asserts the rendered HTML carries each item's real,
decodable values -- generated text that reads correctly can still render
wrong, and what matters is what the runtime finds in the DOM at click time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 07:38:40 +05:30

121 lines
4.3 KiB
TypeScript

import { expect, test } from "bun:test";
import { generate, parse } from "../src/index.ts";
// A `{#each}` inside a COMPONENT emitted `data-wrn-loop-locals`, so a handler
// could reference the loop variable. The same view inside a PAGE emitted the
// handler text verbatim with no locals marker, so the loop variable was not
// defined at click time and the handler threw ReferenceError -- with a green
// build and green tests, because nothing renders the page in a browser during
// a build. The runtime resolves locals with closest("[data-wrn-loop-locals]"),
// so emitting the marker is all that is required.
const view = `
{#each packs as pack}
<button @click="buyPack(pack.code)">{pack.code}</button>
{/each}
`;
const body = `
state packs = [{ code: "small" }]
functions { client async function buyPack(c) { console.log(c) } }
view {${view}}
`;
test("a page {#each} exposes loop locals to an event handler", () => {
const code = generate(parse(`page P {${body}}`));
expect(code).toContain("data-wrn-loop-locals");
// The item and the implicit index both have to travel, since a handler may
// reference either.
expect(code).toContain('"pack": pack');
});
test("a component {#each} still exposes loop locals to an event handler", () => {
const code = generate(parse(`component C {${body}}`));
expect(code).toContain("data-wrn-loop-locals");
expect(code).toContain('"pack": pack');
});
test("a named loop index is exposed to a page event handler", () => {
const code = generate(
parse(`page P {
state packs = [{ code: "small" }]
functions { client async function pick(c) { console.log(c) } }
view {
{#each packs as pack, i}
<button @click="pick(i)">{pack.code}</button>
{/each}
}
}`),
);
expect(code).toContain('"i": i');
});
// Only elements that actually bind an event need the marker; adding it to every
// element in a loop body would bloat the HTML for no benefit.
test("a page loop body element with no handler gets no locals marker", () => {
const code = generate(
parse(`page P {
state packs = [{ code: "small" }]
view {
{#each packs as pack}
<span>{pack.code}</span>
{/each}
}
}`),
);
expect(code).not.toContain("data-wrn-loop-locals");
});
// Emitting the marker without defining its encoder would turn a ReferenceError
// on the loop variable into a ReferenceError on the encoder -- at render time
// rather than click time, so strictly worse.
test("a page that emits loop locals also defines the encoder", () => {
const code = generate(parse(`page P {${body}}`));
expect(code).toContain("data-wrn-loop-locals");
expect(code).toContain("function __wrnexusEncodeLoopLocals");
});
test("a page with no loop-local markers does not define the encoder", () => {
const code = generate(
parse(`page P {
state packs = [{ code: "small" }]
view { <span>{packs.length}</span> }
}`),
);
expect(code).not.toContain("__wrnexusEncodeLoopLocals");
});
// The tests above assert the marker is EMITTED. This one executes the generated
// module and asserts it RENDERS with the loop's real values -- generated text
// that reads correctly can still render wrong, and the whole point of the
// marker is what the runtime finds in the DOM at click time.
test("a page loop handler's locals render as real, decodable values", async () => {
const code = generate(
parse(`page Packs {
state packs = [{ code: "small" }, { code: "large" }]
functions { client async function pick(c) { console.log(c) } }
view {
{#each packs as pack}
<button @click="pick(pack.code)">{pack.code}</button>
{/each}
}
}`),
).replace(/^import \{ buildApiRequest[^\n]*\n/m, "");
const js = new Bun.Transpiler({ loader: "ts" }).transformSync(code);
const mod = await import("data:text/javascript;base64," + Buffer.from(js).toString("base64"));
const html: string = await mod.default({
req: new Request("http://x/"),
cookies: {},
session: {},
});
const markers = [...html.matchAll(/data-wrn-loop-locals="([^"]+)"/g)].map((m) => m[1]!);
expect(markers.length).toBe(2);
const decoded = markers.map((m) => JSON.parse(Buffer.from(m, "base64").toString("utf8")));
expect(decoded[0].pack).toEqual({ code: "small" });
expect(decoded[1].pack).toEqual({ code: "large" });
expect(decoded[0].__wi).toBe(0);
expect(decoded[1].__wi).toBe(1);
});