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); });