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"; 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); } 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="${__wireAttr(title)}"'); }); 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"); });