Files
WRNexusJS/packages/compiler/test/v060-targets.test.ts
T
2026-08-01 01:09:58 +05:30

148 lines
5.7 KiB
TypeScript

import { expect, test } from "bun:test";
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<void> {
const saved = await server.confirm(value)
output.confirm(saved)
}
server async function confirm(value: string): Promise<string> { return value }
shared function normalize(value: string): string { return value.trim() }
}
view { <button>{title}</button> }
}`);
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 { <div></div> }
}`),
);
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 { <button>{class}</button> }
}`),
);
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<void> { await server.save("ok") }
server async function save(value: string): Promise<string> { return value }
server function internalSecret(): string { return "secret" }
}
view { <button @click='saveClient()'>Save</button> }
}`),
);
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<void> { user = await server.loadCurrentUser() }
server async function loadCurrentUser(): Promise<string | null> { 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 { <button @click='run()'>{value}</button> }
}`),
);
expect(targets.browser).toContain("context.functions");
const executable = new Function(
`${targets.browser.replace(/^export\s+/gm, "")}\nreturn { bindClientScope };`,
)() as { bindClientScope: (context: Record<string, unknown>) => Record<string, () => void> };
const state = { value: 0 };
const functions = executable.bindClientScope({
state,
props: {},
output: {},
server: {},
refs: {},
});
functions.run?.();
expect(state.value).toBe(1);
});