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}
{/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}
{/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}
{pack.code}
{/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 { {packs.length} }
}`),
);
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}
{/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);
});