import { test, expect } from "bun:test"; import { writeFileSync, mkdirSync, mkdtempSync, rmSync } 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("top-level imports are preserved and available to SSR view expressions", async () => { const source = `import { posix } from "node:path"; page Home { view { } }`; const ast = parse(source); expect(ast.imports).toEqual(['import { posix } from "node:path";']); const mod = await compileAndImport(source); const render = mod.default as (ctx: unknown) => string; expect(String(await render({}))).toContain('signInHref="/sso/sign-in"'); }); 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("component event declarations compile to public root metadata", async () => { const source = `component EventButton { props { label = "Save" @event complete = function } view { } }`; const ast = parse(source); const output = generate(ast); const mod = await compileAndImport(source); const render = mod.render as (props: Record) => string; expect(ast.events).toEqual([{ name: "complete" }]); expect(output).toContain('data-wrn-events="complete"'); expect(render({})).toContain('data-wrn-events="complete"'); }); test("typed props, required props, state, custom types, and function parameters compile", () => { const source = `component TypedPicker { types { interface DateOption { value: string; label: string } type ChangeHandler = (value: string) => void } props { value: string = "" options: DateOption[] = [] required: boolean = false onChange: ChangeHandler } state open: boolean = false functions { function choose(option: DateOption, index: number): void { open = false onChange(option.value) } } view { } }`; const ast = parse(source); expect(ast.props.map(({ name, valueType, required }) => ({ name, valueType, required }))).toEqual( [ { name: "value", valueType: "string", required: false }, { name: "options", valueType: "DateOption[]", required: false }, { name: "required", valueType: "boolean", required: false }, { name: "onChange", valueType: "ChangeHandler", required: true }, ], ); expect(ast.states[0]).toMatchObject({ name: "open", valueType: "boolean", expr: "false" }); const output = generate(ast); expect(output).toContain("interface DateOption"); expect(output).toContain("export interface TypedPickerProps"); expect(output).toContain('"onChange": ChangeHandler;'); expect(output).toContain("let open: boolean = (false)"); expect(output).toContain("function choose(option: DateOption, index: number): void"); const behavior = extractCompiledBehavior(output); expect(behavior.functions).toContain("function choose(option, index)"); expect(behavior.functions).not.toContain("option: DateOption"); }); test("typed declarations reject obvious initializer mismatches", () => { expect(() => parse(`component Invalid { props { required: boolean = "yes" } view {
} }`), ).toThrow("Prop 'required' is declared as boolean, but its initializer is string"); expect(() => parse(`page Invalid { state count: number = false\n view {

} }`)).toThrow( "State 'count' is declared as number, but its initializer is boolean", ); }); test("typed props enforce required values and runtime-compatible input", async () => { const component = await compileAndImport(`component TypedInput { props { label: string count: number = 0 enabled: boolean = false } view { {label}:{count}:{enabled} } }`); const render = component.render as (props?: Record) => string; expect(() => render()).toThrow("TypedInput requires prop 'label' (string)"); expect(render({ label: "Total", count: "4", enabled: "true" })).toContain( "Total:4:true", ); expect(() => render({ label: "Total", count: "many" })).toThrow("Expected a finite number prop"); expect(() => render({ label: "Total", enabled: "sometimes" })).toThrow("Expected a boolean prop"); }); 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("dynamic HTML boolean attributes are omitted when false", async () => { const mod = await compileAndImport(`component BooleanAttributes { props { disabled = false checked = false required = false loading = false } view { } }`); const render = mod.render as (props?: Record) => string; const enabled = render(); expect(enabled).not.toContain(" disabled"); expect(enabled).not.toContain(" checked"); expect(enabled).not.toContain(" required"); const disabled = render({ disabled: "true", checked: "true", required: "true" }); expect(disabled).toContain(" } }`); const render = mod.render as (props?: Record) => string; const html = render({ label: "Save", variant: "primary", formaction: "/submit-form", formenctype: "application/x-www-form-urlencoded", formmethod: "post", popovertarget: "myPopover", "aria-describedby": "save-help", "data-testid": "save", onclick: "alert(1)", style: "display:none", "data-wrn-bind-0": "unsafe", }); expect(html).toContain('formaction="/submit-form"'); expect(html).toContain('formenctype="application/x-www-form-urlencoded"'); expect(html).toContain('formmethod="post"'); expect(html).toContain('popovertarget="myPopover"'); expect(html).toContain('aria-describedby="save-help"'); expect(html).toContain('data-testid="save"'); expect(html).not.toContain("variant="); expect(html).not.toContain("onclick="); expect(html).not.toContain("style="); expect(html).not.toContain("data-wrn-bind"); }); test("an explicit attrs spread overrides automatic root forwarding", async () => { const mod = await compileAndImport(`component WrappedControl { props { label = "Control" class = "" } view { } }`); const render = mod.render as (props?: Record) => string; const html = render({ label: "Save", class: "wrapper", formaction: "/save" }); expect(html).toContain(''); expect(html).not.toContain('Save'); }); 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'); expect(out).toContain('data-scope="'); expect(out).toContain("start: 10"); expect(out).toContain("label: "Score""); expect(out).toContain("count: 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("request-dependent page state attributes resolve during server rendering", async () => { const mod = await compileAndImport(`page Playground { state label = ctx.url.searchParams.get("label") ?? "Default" state loading = ctx.url.searchParams.get("loading") ?? "false" view {
} }`); const render = mod.default as (ctx: { url: URL }) => Promise; const html = await render({ url: new URL("https://example.test/playground?label=Visible&loading=false"), }); expect(html).toContain('label="Visible"'); expect(html).toContain('loading="false"'); expect(html).not.toContain('label=""'); }); 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("class directives accept single-quoted brace expressions", () => { const output = generate( parse(` component MarketingSectionHeader { props { align = "start" } view {
    Header
    } } `), ); expect(output).toContain('${(align === "center") ? " mx-auto" : ""}'); expect(output).toContain('${(align === "center") ? " text-center" : ""}'); expect(output).not.toContain('({align === "center"})'); expect(output).toContain(""align === \\\\"center\\\\"""); }); 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)"); }); test("data-for conditional classes do not evaluate loop variables during SSR", () => { const output = generate( parse(` component Accordion { props { items = [] } state openIndexes = [] functions { function isOpen(index) { return openIndexes.includes(index) } } view {
    {item.title}
    } } `), ); expect(output).toContain('data-for="item, index in items"'); expect(output).toContain("data-wrn-class-0"); expect(output).not.toContain('${(isOpen(index)) ? " open" : ""}'); }); test("data-for parser supports optional stable key expressions", async () => { const { parseForExpr } = await import("../src/codegen.ts"); expect(parseForExpr("item, index in items key item.id")).toEqual({ item: "item", index: "index", list: "items", key: "item.id", }); expect(parseForExpr("item in items")).toEqual({ item: "item", index: undefined, list: "items", key: undefined, }); }); test("server-rendered each locals work in reactive handlers", async () => { const firstLocals = Buffer.from( JSON.stringify({ item: { question: "First", }, index: 0, }), "utf8", ).toString("base64"); const secondLocals = Buffer.from( JSON.stringify({ item: { question: "Second", }, index: 1, }), "utf8", ).toString("base64"); const dom = mountHtml(`
    `); const buttons = dom.querySelectorAll("button") as HTMLButtonElement[]; buttons[1]!.click(); await Promise.resolve(); const articles = dom.querySelectorAll("article"); expect(articles[0]!.querySelector("span")?.classList.contains("open")).toBe(false); expect(articles[1]!.querySelector("span")?.classList.contains("open")).toBe(true); }, 20_000); test("WRN 0.3 metadata, computed values, loaders, and actions compile additively", () => { const source = `page Dashboard { runtime = "universal" hydrate = "idle" state count = 2 computed { doubled = count * 2 } effect { console.log(doubled) } security { auth = "required" } load server { return { count: 2 } } load client { return { refreshed: true } } action save(input) { return input } view { } }`; const output = compileWireFile(source); expect(output).toContain('export const __wrnexusRuntime = "universal"'); expect(output).toContain('export const __wrnexusHydrate = "idle"'); expect(output).toContain("export async function __wrnexusLoad"); expect(output).toContain("export async function __wrnexusClientLoad"); expect(output).toContain("export async function save(input)"); expect(output).toContain("export const __wrnexusActions"); expect(output).toContain('data-wrn-hydrate="idle"'); }); test("page state is available inside server-rendered if blocks", () => { const source = ` page Pricing { state billingCycle = "monthly" view { {#if billingCycle === "annual"}

    Annual

    {:else}

    Monthly

    {/if} } } `; const ast = parse(source); const code = generate(ast); expect(code).toContain("const { billingCycle } = __state;"); expect(code).toContain('billingCycle === "annual"'); }); test("page state SSR condition renders without ReferenceError", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-page-state-")); const generatedFile = join(root, "pricing.generated.ts"); try { const source = ` page Pricing { state billingCycle = "monthly" view { {#if billingCycle === "annual"}

    Annual

    {:else}

    Monthly

    {/if} } } `; const code = generate(parse(source)); expect(code).toContain("const { billingCycle } = __state;"); expect(code).toContain('billingCycle === "annual"'); writeFileSync(generatedFile, code, "utf8"); const moduleUrl = pathToFileURL(generatedFile).href + `?test=${Date.now()}-${Math.random()}`; const generatedModule = await import(moduleUrl); const html = await generatedModule.default({}); expect(html).toContain("

    Monthly

    "); expect(html).not.toContain("

    Annual

    "); } finally { rmSync(root, { recursive: true, force: true, }); } }); test("page state is declared for server-rendered control blocks", () => { const source = ` page Pricing { state billingCycle = "monthly" view { {#if billingCycle === "annual"}

    Annual

    {:else}

    Monthly

    {/if} } } `; const code = generate(parse(source)); expect(code).toContain("const { billingCycle } = __state;"); expect(code).toContain('billingCycle === "annual"'); });