import { expect, test } from "bun:test"; import { gzipSync } from "bun"; import { generateTargets, parse } from "../src/index.ts"; const ast = parse(`component ConfirmDialog { props { title: string open: boolean = false } state { loading: boolean = false } server state { internalId: string = "secret" } outputs { confirm(payload: string) close() } functions { client async function confirm(value: string): Promise { const saved = await server.confirm(value) output.confirm(saved) } server async function confirm(value: string): Promise { return value } shared function normalize(value: string): string { return value.trim() } } view { } }`); test("emits separate browser and server function targets", () => { const targets = generateTargets(ast); expect(targets.browser).toContain('"confirm": async function'); expect(targets.browser).toContain('"normalize": function'); expect(targets.server).toContain("async function confirm"); expect(targets.rpc).toEqual([ expect.objectContaining({ component: "ConfirmDialog", function: "confirm" }), ]); expect(targets.declarations).toContain("interface ConfirmDialogOutputs"); expect(targets.declarations).toContain("interface ConfirmDialogServerCalls"); }); test("component contracts retain declared union options", () => { const targets = generateTargets( parse(`component SizeBox { props { size: "small" | "medium" | "large" = "medium" } view {
} }`), ); expect(targets.contract.props[0]?.options).toEqual(["small", "medium", "large"]); }); test("generates a standalone browser module for imported stores", () => { const targets = generateTargets( parse(`page store SearchStore { state { query: string = "" } client state { focused: boolean = false } server state { secret: string = "hidden" } computed { empty: boolean = query.length === 0 } functions { client function setQuery(value: string): void { query = value } } persist { storage = "session" include = ["query"] version = 1 } }`), ); expect(targets.browser).toContain("__wrnexusStoreRegistry"); expect(targets.browser).toContain('name: "SearchStore"'); expect(targets.browser).toContain('"query"'); expect(targets.browser).not.toContain('"secret": ("hidden")'); expect(targets.declarations).toContain("interface SearchStoreInstance"); }); test("browser codegen avoids reserved prop bindings and parameter collisions", () => { const targets = generateTargets( parse(`component ReservedBindings { props { class: string = "" output: string = "" } state { value: string = "" } outputs { change(payload: { value: string }) } functions { client function update(output: string): void { value = output } client function notify(): void { output.change({ value: value }) } } view { } }`), ); expect(targets.browser).not.toContain("const { class }"); expect(targets.browser).not.toContain("const output = context.output;\n const output"); expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow(); }); test("RPC manifests expose only server functions referenced through server.name", () => { const targets = generateTargets( parse(`component SecureActions { functions { client async function saveClient(): Promise { await server.save("ok") } server async function save(value: string): Promise { return value } server function internalSecret(): string { return "secret" } } view { } }`), ); expect(targets.rpc.map((entry) => entry.function)).toEqual(["save"]); expect(targets.server).toContain("internalSecret"); }); test("generated browser stores bind typed RPC, persistence validation, and HMR", () => { const targets = generateTargets( parse(`global store UserStore { state { user: string | null = null count: number = 0 } functions { client async function refresh(): Promise { user = await server.loadCurrentUser() } server async function loadCurrentUser(): Promise { return "Ajay" } server function internalOnly(): string { return "secret" } } persist { storage = "local" include = ["count"] version = 2 migrations { function migrate(value, fromVersion, toVersion) { return value } } validate { function validate(value) { return value && typeof value === "object" ? value : null } } } }`), ); expect(targets.browser).toContain("const server = context.server"); expect(targets.browser).toContain("currentDefinition.persist.migrate"); expect(targets.browser).toContain("currentDefinition.persist.validate"); expect(targets.browser).toContain("__wrnexusApplyStoreHotUpdate"); expect(targets.rpc.map((entry) => entry.function)).toEqual(["loadCurrentUser"]); }); test("browser codegen binds peer client functions through the scoped function table", () => { const targets = generateTargets( parse(`component PeerCalls { state { value: number = 0 } functions { client function increment(): void { value += 1 } client function run(): void { increment() } } view { } }`), ); expect(targets.browser).toContain('implementations["increment"]'); const executable = new Function( `${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`, )() as { bindClientScope: (context: Record) => Record void> }; const state = { value: 0 }; const functions = executable.bindClientScope({ state, props: {}, output: {}, server: {}, refs: {}, }); functions.run?.(); expect(state.value).toBe(1); }); test("deferred timer state writes commit after the client function returns", async () => { const targets = generateTargets( parse(`component DeferredWrite { state { value: number = 0 } functions { client function later(): void { setTimeout(() => { value = 2 }, 0) } } view { } }`), ); const executable = new Function( `${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`, )() as { bindClientScope: (context: Record) => Record void> }; let rendered = "0"; const state = new Proxy( { value: 0 }, { set(target, property, value) { Reflect.set(target, property, value); rendered = String(value); return true; }, }, ); const functions = executable.bindClientScope({ state, props: {}, output: {}, server: {}, refs: {}, }); functions.later?.(); expect(state.value).toBe(0); await new Promise((resolve) => setTimeout(resolve, 10)); expect(state.value).toBe(2); expect(rendered).toBe("2"); }); test("the deferred commit helper does not shadow an authored commit function", () => { const targets = generateTargets( parse(`component AuthoredCommit { state { value: number = 0 } functions { client function commit(): void { value = 1 } client function save(): void { commit() } } view { } }`), ); expect(() => new Function(targets.browser.replace(/^export\s+/gm, ""))).not.toThrow(); expect(targets.browser).not.toContain("const commit = __wrnexusCommit"); }); test("generated client modules stay below decoded-size and duplication ratchets", () => { const states = Array.from({ length: 12 }, (_, index) => `s${index}: number = ${index}`).join(" "); const functions = Array.from( { length: 20 }, (_, index) => `client function f${index}(): void { s${index % 12} += 1 ${Array.from( { length: 20 }, (_unused, peer) => (peer === index ? "" : `if (false) f${peer}()`), ).join(" ")} }`, ).join("\n"); const browser = generateTargets( parse(`component ClientSizeStress { state { ${states} } functions { ${functions} } view { } }`), ).browser; const decoded = new TextEncoder().encode(browser).length; const compressed = gzipSync(new TextEncoder().encode(browser)).length; expect(decoded).toBeLessThanOrEqual(13_000); expect(decoded / compressed).toBeLessThanOrEqual(10); });