\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("');
});
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 { {label} }\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 { {label}: {count} }\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 {
Run
}
}`);
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 {
Toggle
}
}`);
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 {
}
}`);
expect(page).toContain('data-wrnexus-style-kind="page"');
expect(component).toContain('data-wrnexus-style-kind="component"');
expect(layout).toContain('data-wrnexus-style-kind="layout"');
expect(page).toContain("export const __wrnexusStyles");
expect(component).toContain("export const __wrnexusStyles");
expect(layout).toContain("export const __wrnexusStyles");
expect(page).toMatch(/data-wrnexus-style-id="wrn-page-[a-z0-9]+"/);
});
test("strict-mode reserved prop names compile through safe local references", () => {
const output = compileWireFile(`component Visibility {
props { private: boolean = false }
view { {#if private}Private{/if} }
}`);
expect(output).toContain("const __p_private: boolean");
expect(output).toContain("(__p_private) ?");
expect(output).not.toContain("const private:");
});
// data-show holds an expression the client re-evaluates, so it must survive
// compilation verbatim. Interpolating `{open || visible}` to the literal
// "false" froze every overlay that used the braced form -- Drawer, Dropdown,
// Popover, ContextMenu and Tooltip could never open.
test("data-show keeps its expression instead of being interpolated away", () => {
const source = `component Panel {
props {
open: boolean = false
}
state visible = false
view {
body
}
}
`;
const code = compileWireFile(source, "Panel.wrn");
expect(code).toContain(`data-show="open || visible"`);
expect(code).not.toContain(`data-show="false"`);
});
// Props and state are destructured into the same scope as the function body,
// so a local that shares one of their names used to emit a redeclaration and
// break the whole generated module with a parse error naming no source line.
test("a local variable may shadow a prop without breaking the module", () => {
const source = `component Sized {
props {
size: number = 10
}
state total = 0
functions {
client function recompute() {
var size = 4
var total = size * 2
return total
}
}
view {
{total}
}
}
`;
const code = compileWireFile(source, "Sized.wrn");
// The alias must be dropped, not emitted alongside the local declaration.
expect(code).not.toContain("const { size } = context.props;");
expect(code).toContain("var size = 4");
});
// A boolean attribute whose expression names a loop variable cannot be
// resolved on the server -- __wireBooleanAttr runs at render time, where the
// loop variable does not exist, and the generated module failed outright.
test("a boolean attribute bound to a loop variable binds on the client", () => {
const source = `component Picker {
state rows = []
functions {
shared function isOn(row) {
return row.on
}
}
view {