feat: release design system engine, studio and component contracts

This commit is contained in:
Clintchiz committed 2026-10-19 13:09:03 +05:30
1 parent ff1065fb27
commit 3943b946d6
693 files changed
+175546 -3894

No files matched your search

@@ -180,6 +180,7 @@ function __wrnSpreadAttrs(value: unknown): string {
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
lowerName !== "data-wrn-loop-locals" &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
+15
View File
@@ -1161,6 +1161,21 @@ test("page state passed to a component retains a parent-owned prop binding", ()
expect(output).toContain("data-wrn-prop-bind-");
expect(output).toContain("{count}");
});
test("nested component helper results retain reactive bindings", () => {
const output = generate(
parse(`component Signature {
props { value = {} }
functions {
shared function currentName() { return value.name || '' }
shared function currentConsent() { return !!value.consent }
}
view { <Input value="{currentName()}" /><Switch checked="{currentConsent()}" /> }
}`),
);
expect(output).toContain("[&quot;value&quot;,&quot;{currentName()}&quot;]");
expect(output).toContain("[&quot;checked&quot;,&quot;{currentConsent()}&quot;]");
});
test("nested component props retain parent-owned reactive bindings", () => {
const output = generate(
parse(`component Parent {
@@ -0,0 +1,42 @@
import { expect, test } from "bun:test";
import { compile } from "../src/index.ts";
test("component conditional templates retain enclosing loop locals for reactive reevaluation", async () => {
const source = `component FormFields {
props { pages = [{id:'page',fields:[{id:'name',hidden:false}]}] }
state answers = {}
view { {#each pages as page}<section>{#each page.fields as field}{#if !field.hidden}<Input name="{field.id}" @input="answers[field.id]=payload.value" />{/if}{/each}</section>{/each} }
}`;
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile(source, "FormFields.wrn").code,
);
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
const html = mod.render({});
const markers = [...html.matchAll(/<template([^>]+)>/g)].map((match) => match[1]!);
const conditional = markers.find((marker) => marker.includes("data-wrn-if="))!;
const locals = /data-wrn-loop-locals="([^"]+)"/.exec(conditional);
expect(conditional?.trim().startsWith("data-wrn-if=")).toBe(true);
expect(locals).not.toBeNull();
const decoded = JSON.parse(Buffer.from(locals![1]!, "base64").toString());
expect(decoded.field.id).toBe("name");
expect(decoded.page.id).toBe("page");
const nestedEach = markers.filter((marker) => marker.includes("data-wrn-each="))[1]!;
const eachLocals = /data-wrn-loop-locals="([^"]+)"/.exec(nestedEach);
expect(eachLocals).not.toBeNull();
expect(JSON.parse(Buffer.from(eachLocals![1]!, "base64").toString()).page.id).toBe("page");
const child = /<div data-component="Input"([^>]+)>/.exec(html)!;
const childLocals = /data-wrn-loop-locals="([^"]+)"/.exec(child[1]!);
expect(childLocals).not.toBeNull();
expect(JSON.parse(Buffer.from(childLocals![1]!, "base64").toString()).field.id).toBe("name");
const childCode = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile("component Child { view { <input /> } }", "Child.wrn").code,
);
const childModule = await import(
"data:text/javascript;base64," + Buffer.from(childCode).toString("base64")
);
const renderedChild = childModule.render({
"data-wrn-loop-locals": childLocals![1]!,
"data-wrn-if": "forbidden",
});
expect(renderedChild).toContain('data-wrn-loop-locals="' + childLocals![1] + '"');
expect(renderedChild).not.toContain('data-wrn-if="forbidden"');
});
@@ -7,7 +7,9 @@ test("deep conditional components render each branch once with bounded document
let view = "<p>Leaf</p>";
for (let i = 0; i < 12; i++) view = `{#if open}${view}{:else}<i>Closed</i>{/if}`;
const source = `component Nested {\n state open = true\n view { ${view} }\n }`;
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(compile(source, "Nested.wrn").code);
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile(source, "Nested.wrn").code,
);
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
runWithRequestContext({} as never, () => {
const body = mod.render({});
@@ -15,6 +17,6 @@ test("deep conditional components render each branch once with bounded document
expect(body).toContain("<p>Leaf</p>");
expect(document.length).toBeLessThan(30000);
const registry = JSON.parse(document.match(/data-wrn-controls[^>]*>(.*?)<\/script>/s)![1]!);
expect(registry).toHaveLength(12);
expect(registry.filter(Array.isArray)).toHaveLength(12);
});
});
@@ -0,0 +1,34 @@
import { expect, test } from "bun:test";
import { parse } from "@wrnexus/syntax";
import { generateBrowserModule } from "../src/client-codegen.ts";
const source = `component FunctionDefaults {
props { timezone = "UTC" }
state selected = ""
functions {
client function choose(section = "connections", hour = 9) { selected = section; return hour }
shared function label(prefix = "event", suffix = prefix + " details", zone = timezone) { return [prefix, suffix, zone] }
}
view { <button @click="choose()">Choose</button> }
}`;
test("browser functions retain defaults, prior-argument references and prop references", () => {
const module = generateBrowserModule(parse(source));
const functions = new Function(
module.replace(/\bexport\s+(?=const|function)/g, "") + ";return __wrnexusClientFunctions;",
)();
const context = {
state: { selected: "" },
props: { timezone: "Asia/Kolkata" },
batch: (apply: () => void) => apply(),
};
expect(functions.choose(context)).toBe(9);
expect(context.state.selected).toBe("connections");
expect(functions.label(context)).toEqual(["event", "event details", "Asia/Kolkata"]);
expect(functions.label(context, "meeting")).toEqual([
"meeting",
"meeting details",
"Asia/Kolkata",
]);
expect(functions.choose(context, undefined, 0)).toBe(0);
expect(functions.label(context, null, "explicit", "UTC")).toEqual([null, "explicit", "UTC"]);
});
@@ -0,0 +1,49 @@
import { expect, test } from "bun:test";
import { compile } from "../src/index.ts";
test("inactive conditional branches do not dereference null state during server rendering", async () => {
const source = `component AddressReview {
state review = null
view { <h3>Address review</h3>{#if review}<p>{review.address}</p>{#if review.suppression.active}<button>Reactivate</button>{/if}{/if} }
}`;
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile(source, "AddressReview.wrn").code,
);
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
expect(() => mod.render({})).not.toThrow();
expect(mod.render({})).toContain("<h3>Address review</h3>");
});
test("selected branches render state while inactive nested branches remain browser templates", async () => {
const source = `component AddressReview {
props { review = { address: 'review@example.test', suppression: null } }
view { {#if review}<p>{review.address}</p>{#if review.suppression}<b>{review.suppression.reason}</b>{:else}<i>No block</i>{/if}{/if} }
}`;
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile(source, "ReviewedAddress.wrn").code,
);
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
const html = mod.render({});
expect(html).toContain('data-text="review.address">review@example.test</span>');
expect(html).toContain("<i>No block</i>");
expect(html).not.toContain("<b>null</b>");
});
test("inactive branch child components retain parent prop bindings", async () => {
const source = `component AddressReview {
props { messages = { evidence: 'Evidence', reactivate: 'Reactivate' } }
state review = null
view { {#if review}<Input label="{messages.evidence}" /><Button label="{messages.reactivate}" />{/if} }
}`;
const code = new Bun.Transpiler({ loader: "ts" }).transformSync(
compile(source, "LocalizedReview.wrn").code,
);
const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64"));
const html = mod.render({});
const match = /data-wrn-if="([A-Za-z0-9+/=]+)"/.exec(html)!;
const body = JSON.parse(Buffer.from(match[1]!, "base64").toString())[0].body;
expect(body).toContain("data-wrn-prop-bind-0=");
expect(body).not.toContain("data-wrn-bind-0=");
expect(body).toContain("messages.evidence");
expect(body).toContain("messages.reactivate");
});
+222
View File
@@ -225,3 +225,225 @@ test("generated client modules stay below decoded-size and duplication ratchets"
expect(decoded).toBeLessThanOrEqual(13_000);
expect(decoded / compressed).toBeLessThanOrEqual(10);
});
test("async completion commits sibling state atomically before reactive readers run", async () => {
const targets = generateTargets(
parse(`component AtomicCompletion {
state { busy: boolean = true; submitted: boolean = false }
functions { shared function visible(){return !submitted} client async function finish(){await Promise.resolve();busy=false;submitted=true} }
view { <p>{visible()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
let batching = false,
reads = 0;
const values = { busy: true, submitted: false };
const state = new Proxy(values, {
set(target, key, value) {
const previous = Reflect.get(target, key);
Reflect.set(target, key, value);
if (!batching && previous !== value) {
reads++;
functions.visible();
}
return true;
},
});
const functions = executable.bindClientScope({
state,
props: {},
output: {},
server: {},
refs: {},
batch(callback: () => void) {
batching = true;
try {
callback();
} finally {
batching = false;
}
reads++;
},
});
await functions.finish();
expect(values.busy).toBe(false);
expect(values.submitted).toBe(true);
expect(reads).toBeGreaterThan(0);
});
test("display helpers subscribe only to their transitive inputs and never write unchanged state", () => {
const targets = generateTargets(
parse(`component FocusedHelpers {
state { events: string[] = []; title: string = ""; selected: string = "" }
props { timezone: string = "UTC"; unrelated: string = "" }
functions {
shared function zone(){return timezone}
shared function matches(item){return item===selected}
shared function visible(){return events.filter(matches).map(item=>item+zone())}
client function changeTitle(value){title=value}
}
view { <p>{visible()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
const reads: string[] = [],
writes: string[] = [];
const values = { events: ["chosen"], title: "draft", selected: "chosen" };
const functions = executable.bindClientScope({
state: new Proxy(values, {
get(target, key) {
reads.push(String(key));
return Reflect.get(target, key);
},
set(target, key, value) {
writes.push(String(key));
return Reflect.set(target, key, value);
},
}),
props: { timezone: "UTC", unrelated: "" },
output: {},
server: {},
refs: {},
});
reads.length = 0;
writes.length = 0;
expect(functions.visible()).toEqual(["chosenUTC"]);
expect([...new Set(reads)].sort()).toEqual(["events", "selected"]);
expect(writes).toEqual([]);
functions.changeTitle("new draft");
expect(writes).toEqual(["title"]);
expect(values.title).toBe("new draft");
reads.length = 0;
writes.length = 0;
expect(functions.visible()).toEqual(["chosenUTC"]);
expect(reads).not.toContain("title");
expect(writes).toEqual([]);
});
test("helper defaults, template expressions and returned callbacks retain state dependencies", () => {
const targets = generateTargets(
parse(`component IndirectReads {
state { selected: string = "A"; title: string = "draft"; irrelevant: number = 0 }
functions { shared function value(input=selected){return input} shared function label(){return () => \`\${title}:\${value()}\`} }
view { <p>{label()()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
const reads: string[] = [];
const values = { selected: "A", title: "draft", irrelevant: 0 };
const functions = executable.bindClientScope({
state: new Proxy(values, {
get(target, key) {
reads.push(String(key));
return Reflect.get(target, key);
},
}),
props: {},
output: {},
server: {},
refs: {},
});
reads.length = 0;
expect(functions.label()()).toBe("draft:A");
expect([...new Set(reads)].sort()).toEqual(["selected", "title"]);
values.title = "updated";
expect(functions.label()()).toBe("updated:A");
});
test("a focused helper cannot overwrite unrelated state changed outside the bound closure", async () => {
const targets = generateTargets(
parse(`component PreserveExternalState {
state { count: number = 0; title: string = "draft" }
functions { shared function countLabel(){return String(count)} client async function increment(){await Promise.resolve();count+=1} }
view { <p>{countLabel()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
const state = { count: 0, title: "draft" };
const functions = executable.bindClientScope({
state,
props: {},
output: {},
server: {},
refs: {},
});
state.title = "external update";
expect(functions.countLabel()).toBe("0");
expect(state.title).toBe("external update");
await functions.increment();
expect(state.count).toBe(1);
expect(state.title).toBe("external update");
});
test("permission strings and method names do not create false helper dependencies", () => {
const targets = generateTargets(
parse(`component PermissionHelpers {
state { title: string = "draft" }
props { roles: string[] = [] }
functions { shared function canCreate(){return roles.includes('calendar.create')} client function create(){return title} }
view { <p>{canCreate()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
const reads: string[] = [];
const functions = executable.bindClientScope({
state: new Proxy(
{ title: "draft" },
{
get(target, key) {
reads.push(String(key));
return Reflect.get(target, key);
},
},
),
props: { roles: ["calendar.create"] },
output: {},
server: {},
refs: {},
});
reads.length = 0;
expect(functions.canCreate()).toBe(true);
expect(reads).toEqual([]);
});
test("async functions publish loading and navigation state before yielding", async () => {
const targets = generateTargets(
parse(`component ImmediateAsyncState {
state { busy: boolean = false; selected: string = "old" }
functions { shared function label(){return selected} client async function load(value){busy=true;selected=value;await server.wait();busy=false} }
view { <p>{label()}</p> }
}`),
);
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn {bindClientScope};`,
)();
let release: any;
const gate = new Promise<void>((resolve) => (release = resolve));
const state = { busy: false, selected: "old" };
const functions = executable.bindClientScope({
state,
props: {},
output: {},
server: { wait: () => gate },
refs: {},
});
const pending = functions.load("new");
expect(state.busy).toBe(true);
expect(state.selected).toBe("new");
expect(functions.label()).toBe("new");
expect(state.busy).toBe(true);
release();
await pending;
expect(state.busy).toBe(false);
expect(state.selected).toBe("new");
});