import { test, expect } from "bun:test"; import { writeFileSync, mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { generate, parse } from "../src/index.ts"; import { compileWireFile } from "../src/index.ts"; import { mountHtml } from "@wrnexus/test"; let seq = 0; /** Compile a `.wrn` source and import the resulting module. */ async function compileAndImport(src: string): Promise> { const dir = join(tmpdir(), "wire-compiler-test"); mkdirSync(dir, { recursive: true }); const file = join(dir, `m${seq++}.ts`); writeFileSync(file, compileWireFile(src)); return import(pathToFileURL(file).href); } type CompiledBehavior = { functions: string; lifecycle: { mount?: string; update?: string; unmount?: string; }; watches: Array<{ state: string; body: string; }>; }; function extractCompiledBehavior(output: string): CompiledBehavior { const marker = 'data-wrn-behavior="'; const start = output.indexOf(marker); expect(start).toBeGreaterThanOrEqual(0); const valueStart = start + marker.length; const valueEnd = output.indexOf('"', valueStart); expect(valueEnd).toBeGreaterThan(valueStart); const encoded = output.slice(valueStart, valueEnd); expect(encoded).toMatch(/^[A-Za-z0-9+/]+=*$/); return JSON.parse(Buffer.from(encoded, "base64").toString("utf8")) as CompiledBehavior; } test("parses a page with a layout member", () => { const ast = parse(`page Home {\n layout = "public"\n view {

Hi

}\n}`); expect(ast.kind).toBe("page"); expect(ast.name).toBe("Home"); expect(ast.layout).toBe("public"); }); test("parses a component with props and state", () => { const ast = parse( `component C {\n props {\n n = 0\n }\n state count = n\n view { {count} }\n}`, ); expect(ast.kind).toBe("component"); expect(ast.props.map((p) => p.name)).toEqual(["n"]); expect(ast.states.map((s) => s.name)).toEqual(["count"]); }); test("HTML view: void elements, boolean attrs, comments, lone <", () => { const ast = parse( `component T {\n view {\n \n
\n \n

a < b

\n }\n}`, ); const html = compileWireFile( `component T {\n view {\n \n
\n \n

a < b

\n }\n}`, ); expect(html).toContain(""); expect(html).not.toContain("comment"); expect(html).toContain("a < b"); void ast; }); test("stateless component bakes props into server HTML (zero JS)", async () => { const mod = await compileAndImport( `component Button {\n props {\n label = "Button"\n variant = "default"\n class = ""\n }\n view { }\n}`, ); const render = mod.render as (p: Record) => string; const out = render({ label: "Save ", variant: "primary", class: "mt-2" }); expect(out).toContain('class="wire-btn wire-btn--primary mt-2"'); expect(out).toContain("Save <b>"); // html-escaped expect(out).not.toContain("data-scope"); // no reactivity → no scope }); test("stateful component: state text baked into a reactive data-text span, prop text baked", async () => { const mod = await compileAndImport( `component Counter {\n props {\n start = 0\n label = "Count"\n }\n state count = start\n view { }\n}`, ); const render = mod.render as (p: Record) => string; const out = render({ start: "10", label: "Score" }); expect(out).toContain("data-scope=\"start: 10, label: 'Score', count: 10\""); expect(out).toContain('data-on-click="count++"'); // label baked as static text; count baked as its initial value AND kept live. expect(out).toContain('Score: 10'); }); test("prop type coercion follows the default value's type", async () => { const mod = await compileAndImport( `component X {\n props {\n n = 0\n s = "x"\n b = false\n }\n view { {n}{s}{b} }\n}`, ); // needsScope=false → seeds nothing; verify via a stateful variant instead: const mod2 = await compileAndImport( `component Y {\n props {\n n = 0\n }\n state v = n\n view { {v} }\n}`, ); const out = (mod2.render as (p: Record) => string)({ n: "42" }); expect(out).toContain("v: 42"); // "42" coerced to number 42 (not '42') void mod; }); test("platform events compile through the native runtime bridge", () => { const out = compileWireFile(`page Platform { state count = 0 view { } }`); expect(out).toContain('data-on-wrnexus-browser-click="count++"'); expect(out).toContain('data-on-wrnexus-mobile-click="count = count + 2"'); }); test("{t:key} compiles to a data-t marker (both pages and components)", () => { const page = compileWireFile(`page P {\n view {

{t:home.title}

}\n}`); expect(page).toContain(''); const comp = compileWireFile(`component C {\n view {

{t:x}

}\n}`); expect(comp).toContain(''); }); test("{t:} does NOT wrap a page in a data-scope (regression)", () => { // Only state / events force a reactive scope, not i18n markers. const page = compileWireFile(`page P {\n view {

{t:a}

{t:b}

}\n}`); expect(page).not.toContain("data-scope"); }); test("page state text bakes its initial value into a reactive data-text span", () => { const page = compileWireFile( `page Reactive {\n state count = 3\n view {

Count is {count}, doubled {count * 2}

}\n}`, ); expect(page).toContain('3'); // no-JS sees "3" expect(page).toContain('6'); // expression evaluated expect(page).toContain("data-scope"); // state still forces a reactive scope }); test("page state attributes bake their initial value and retain a reactive binding", () => { const page = compileWireFile(`page Password { state show = false view { } }`); expect(page).toContain('type="password"'); expect(page).toContain('aria-label="Show"'); expect(page.match(/data-wrn-bind-/g)).toHaveLength(2); expect(page).toContain("show ? 'text' : 'password'"); }); test("data-for: loop-variable mustaches stay literal (not baked server-side)", () => { const comp = compileWireFile( `component TodoList {\n state todos = []\n view {
  • {t.text}
}\n}`, ); // The
  • template keeps `{t.text}` for the client list renderer, and the // loop variable is never baked (which would be a server-side ReferenceError). expect(comp).toContain('data-for="t in todos"'); expect(comp).toContain("{t.text}"); expect(comp).not.toContain("__wireHtml(t.text)"); }); test("named slots pass through to the component output", () => { const out = compileWireFile( `component Card {\n view {
    }\n}`, ); expect(out).toContain(''); expect(out).toContain(""); }); test("capitalized self-closing tags compile to component mounts", () => { const source = ` page ComponentPage { view { } } `; const output = generate(parse(source)); expect(output).toContain('data-component="UserCard"'); expect(output).toContain('name="Ajay"'); expect(output).toContain('role="Admin"'); expect(output).not.toContain(" { const source = ` page SlotPage { view {

    Hello

    Component content

    } } `; const output = generate(parse(source)); expect(output).toContain('data-component="Card"'); expect(output).toContain("

    Hello

    "); expect(output).toContain("

    Component content

    "); }); test("component tags work inside reusable components", () => { const source = ` component ParentCard { props { title = "Hello" } view { } } `; const output = generate(parse(source)); expect(output).toContain('data-component="ChildCard"'); expect(output).toContain('title="${__wireProp(title)}"'); expect(output).toContain("function __wireProp"); }); test("parses a layout block", () => { const ast = parse(` layout AppLayout { view {
    {content}
    } } `); expect(ast.kind).toBe("layout"); expect(ast.name).toBe("AppLayout"); }); test("layouts compile as reusable render modules", () => { const output = generate( parse(` layout AppLayout { view {
    {content}
    } } `), ); expect(output).toContain('export const __wrnexusLayout = "AppLayout"'); expect(output).toContain("${__wireRaw(content)}"); expect(output).toContain("export function render"); }); test("parses component lifecycle hooks", () => { const ast = parse(` component BackToTop { state visible = false lifecycle { mount { visible = true } update { console.log(visible) } unmount { visible = false } } view { } } `); expect(ast.lifecycle.mount).toContain("visible = true"); expect(ast.lifecycle.update).toContain("console.log(visible)"); expect(ast.lifecycle.unmount).toContain("visible = false"); }); test("parses state watchers", () => { const ast = parse(` component Menu { state open = false watch open { console.log("Open changed", open) } view { } } `); expect(ast.watches).toEqual([ { state: "open", body: expect.stringContaining('console.log("Open changed", open)'), }, ]); }); test("rejects watching an undeclared state", () => { expect(() => parse(` component Broken { watch missing { console.log(missing) } view {
    Broken
    } } `), ).toThrow("Cannot watch undeclared state 'missing'"); }); test("rejects unknown lifecycle hooks", () => { expect(() => parse(` component Broken { lifecycle { created { console.log("invalid") } } view {
    Broken
    } } `), ).toThrow("Unknown lifecycle hook 'created'"); }); test("component functions compile into browser behavior metadata", () => { const output = generate( parse(` component Counter { state count = 0 functions { function increment() { count += 1 } } view { } } `), ); expect(output).toContain("data-wrn-behavior"); expect(output).toContain("function increment()"); expect(output).toContain("export const __wrnexusBehavior"); }); test("component lifecycle hooks compile into behavior metadata", () => { const output = generate( parse(` component LifecycleTest { state ready = false lifecycle { mount { ready = true } update { console.log(ready) } unmount { ready = false } } view {
    {ready}
    } } `), ); const behavior = extractCompiledBehavior(output); expect(behavior.lifecycle.mount).toContain("ready = true"); expect(behavior.lifecycle.update).toContain("console.log(ready)"); expect(behavior.lifecycle.unmount).toContain("ready = false"); }); test("component watchers compile into behavior metadata", () => { const output = generate( parse(` component WatchTest { state visible = false watch visible { console.log("changed", visible) } view {
    {visible}
    } } `), ); const behavior = extractCompiledBehavior(output); expect(behavior.watches).toHaveLength(1); expect(behavior.watches[0]?.state).toBe("visible"); expect(behavior.watches[0]?.body).toContain('console.log("changed", visible)'); }); test("behavior-only components still receive a reactive scope", () => { const output = generate( parse(` component BehaviorOnly { functions { function sayHello() { console.log("hello") } } lifecycle { mount { sayHello() } } view {
    Hello
    } } `), ); expect(output).toContain("data-scope"); expect(output).toContain("data-wrn-behavior"); }); test("component behavior metadata safely encodes multiline code", () => { const output = generate( parse(` component BackToTop { state visible = false functions { function updateVisibility() { visible = window.scrollY > 500 } function scrollToTop() { window.scrollTo({ top: 0, behavior: "smooth" }) } } lifecycle { mount { updateVisibility() } } watch visible { console.log( value, previous ) } view { } } `), ); expect(output).toContain("data-wrn-behavior="); expect(output).not.toContain('data-wrn-behavior="{'); expect(output).not.toContain('data-wrn-behavior="%7B'); }); test("component watchers compile into behavior metadata", () => { const output = generate( parse(` component WatchTest { state visible = false watch visible { console.log("changed", visible) } view {
    {visible}
    } } `), ); const match = /data-wrn-behavior="([^"]+)"/.exec(output); expect(match).not.toBeNull(); const behavior = extractBehavior(output); expect(behavior.watches).toEqual([ { state: "visible", body: expect.stringContaining('console.log("changed", visible)'), }, ]); }); test("component lifecycle hooks compile into behavior metadata", () => { const output = generate( parse(` component LifecycleTest { state ready = false lifecycle { mount { ready = true } update { console.log(ready) } unmount { ready = false } } view {
    {ready}
    } } `), ); const match = /data-wrn-behavior="([^"]+)"/.exec(output); expect(match).not.toBeNull(); const behavior = extractBehavior(output); expect(behavior.lifecycle.mount).toContain("ready = true"); expect(behavior.lifecycle.update).toContain("console.log(ready)"); expect(behavior.lifecycle.unmount).toContain("ready = false"); }); function extractBehavior(output: string): { functions: string; lifecycle: { mount?: string; update?: string; unmount?: string; }; watches: Array<{ state: string; body: string; }>; } { const match = /data-wrn-behavior="([^"]+)"/.exec(output); expect(match).not.toBeNull(); return JSON.parse(Buffer.from(match![1], "base64").toString("utf8")); } test("component interpolated class attributes remain reactive", () => { const output = generate( parse(` component CookieToggle { state personalizationCookies = false view { Cookie } } `), ); const match = output.match(/data-wrn-bind-class="([^"]+)"/); expect(match).not.toBeNull(); const encodedBinding = match?.[1] ?? ""; const decodedBinding = encodedBinding .replaceAll(""", '"') .replaceAll("'", "'") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">"); const binding = JSON.parse(decodedBinding) as [string, string]; expect(binding[0]).toBe("class"); expect(binding[1]).toBe("base {personalizationCookies ? 'enabled' : 'disabled'}"); }); test("parses class directives containing Tailwind arbitrary values", () => { const ast = parse(` component PageHeader { state centered = false view {
    Header
    } } `); expect(ast).toBeDefined(); }); test("compiles Tailwind arbitrary values in class directives", () => { const output = generate( parse(` component PageHeader { state centered = false view {
    Header
    } } `), ); expect(output).toContain("lg:grid-cols-[minmax(0,1fr)_auto]"); expect(output).toContain("dark:bg-white/10"); }); test("components support each blocks", () => { const output = generate( parse(` component PageHeader { props { highlights = [] } view {
    {#each highlights as highlight} {highlight.title} {/each}
    } } `), ); expect(output).toContain("highlights"); expect(output).toContain("highlight.title"); expect(output).not.toContain("supported in pages, not components"); }); test("components support if blocks", () => { const output = generate( parse(` component Banner { props { visible = true } view { {#if visible}
    Visible
    {:else}
    Hidden
    {/if} } } `), ); expect(output).toContain("visible"); expect(output).toContain("Visible"); expect(output).toContain("Hidden"); }); test("component array props support each blocks", () => { const output = generate( parse(` component HighlightList { props { highlights = [] } view { {#each highlights as highlight}

    {highlight.title}

    {/each} } } `), ); expect(output).toContain("Array.isArray(def)"); expect(output).toContain("JSON.parse(v)"); expect(output).toContain("highlights"); }); test("page component props evaluate whole expressions", () => { const output = generate( parse(` page Home { view { } } `), ); expect(output).toContain("__wrnexusPropAttr(true)"); expect(output).toContain("__wrnexusPropAttr(["); }); test("component functions are available during server rendering", () => { const output = generate( parse(` component FAQAccordion { props { allowMultiple = false items = [] } state openIndexes = [] functions { function isOpen(index) { return openIndexes.includes(index) } function toggleItem(index) { if (isOpen(index)) { openIndexes = openIndexes.filter( (itemIndex) => itemIndex !== index ) } else if (allowMultiple) { openIndexes = [ ...openIndexes, index ] } else { openIndexes = [index] } } } view { {#each items as item, index} {/each} } } `), ); expect(output).toContain("function isOpen(index)"); expect(output).toContain("function toggleItem(index)"); expect(output).toContain("let openIndexes ="); expect(output).toContain("isOpen(index)"); });