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>
121 lines
4.3 KiB
TypeScript
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);
|
|
});
|