release: WRNexusJS 0.3.5

This commit is contained in:
2026-07-24 12:46:44 +05:30
parent 44ba847210
commit c81dedff17
2114 changed files with 65790 additions and 150559 deletions
+119
View File
@@ -80,6 +80,24 @@ test("parses a component with props and state", () => {
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 { <button>{label}</button> }
}`;
const ast = parse(source);
const output = generate(ast);
const mod = await compileAndImport(source);
const render = mod.render as (props: Record<string, unknown>) => 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 {
@@ -169,6 +187,89 @@ test("HTML view: void elements, boolean attrs, comments, lone <", () => {
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 {
<button disabled="{disabled || loading}">Save</button>
<input checked="{checked}" required="{required}" />
}
}`);
const render = mod.render as (props?: Record<string, unknown>) => 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("<button disabled>");
expect(disabled).toContain("<input checked required>");
});
test("components safely forward undeclared HTML attributes to their root", async () => {
const mod = await compileAndImport(`component ForwardingButton {
props {
label = "Button"
variant = "default"
disabled = false
}
view {
<button disabled="{disabled}">{label}</button>
}
}`);
const render = mod.render as (props?: Record<string, unknown>) => 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 {
<span class="{class}">
<button {...attrs}>{label}</button>
</span>
}
}`);
const render = mod.render as (props?: Record<string, unknown>) => string;
const html = render({ label: "Save", class: "wrapper", formaction: "/save" });
expect(html).toContain('<span class="wrapper">');
expect(html).not.toContain('<span formaction="/save"');
expect(html).toContain('<button formaction="/save">Save</button>');
});
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 { <button class="wire-btn wire-btn--{variant} {class}">{label}</button> }\n}`,
@@ -260,6 +361,24 @@ test("page state attributes bake their initial value and retain a reactive bindi
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 {
<div data-component="Button" label="{label}" loading="{loading}"></div>
}
}`);
const render = mod.default as (ctx: { url: URL }) => Promise<string>;
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 { <ul><li data-for="t in todos">{t.text}</li></ul> }\n}`,