import { expect, test } from "bun:test"; import { diagnose, eraseFunctionTypes, parse } from "../src/index.ts"; const source = `import type { PublicUser } from "@/types/user.ts" import PublicLayout from "@/layouts/PublicLayout.wrn" component ProfileCard { props { user: PublicUser title?: string open: boolean = false size: "small" | "medium" | "large" = "medium" } state query: string = "" state { selected: PublicUser | null = null loading: boolean = false } client state { menuOpen: boolean = false } server state { sessionId: string | null = null } computed ready: boolean = selected !== null computed { label: string = title || user.name } outputs { confirm(payload: PublicUser) cancel() } functions { client async function confirmed(payload: PublicUser): Promise { const result = await server.confirmed(payload) output.confirm(result) } server async function confirmed(payload: PublicUser): Promise { return payload } shared function normalize(value: string): string { return value.trim() } } view { } }`; test("parses the WRNexusJS 0.6 language surface", () => { const ast = parse(source); expect(ast.structuredImports).toHaveLength(2); expect(ast.props.map((prop) => [prop.name, prop.required])).toEqual([ ["user", true], ["title", false], ["open", false], ["size", false], ]); expect(ast.states.map((state) => `${state.runtime}:${state.name}`)).toEqual([ "shared:query", "shared:selected", "shared:loading", "client:menuOpen", "server:sessionId", ]); expect(ast.outputs.map((output) => output.name)).toEqual(["confirm", "cancel"]); expect(ast.runtimeFunctions.map((fn) => `${fn.runtime}:${fn.name}`)).toEqual([ "client:confirmed", "server:confirmed", "shared:normalize", ]); }); test("supports global and page store roots", () => { const globalStore = parse(`global store UserStore { state { user: string | null = null } computed { authenticated: boolean = user !== null } persist { storage = "local" include = ["user"] version = 1 } lifecycle { serverInit {} clientInit {} hydrate {} dispose {} } functions { client function clear(): void { user = null } } }`); const pageStore = parse(`page store SearchStore { state { query: string = "" } }`); expect(globalStore.kind).toBe("global-store"); expect(globalStore.persist?.storage).toBe("local"); expect(globalStore.storeLifecycle).toEqual( expect.objectContaining({ serverInit: "", dispose: "" }), ); expect(pageStore.kind).toBe("page-store"); }); test("rejects duplicate implementations in the same runtime", () => { const diagnostics = diagnose(`component Invalid { functions { client function save(): void {} client function save(): void {} } view {
} }`); expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-FUNCTION-DUPLICATE")).toBe(true); }); test("readonly prop diagnostics ignore comparisons, strings, and shadowing parameters", () => { const diagnostics = diagnose(`component Valid { props { name: string = "field" mode: string = "exact" items: any[] = [] } functions { client function inspect(name, localItems) { if (mode === "exact") localItems = items.filter((item) => item.name === name) return document.querySelector("input[name='" + name + "']") } } view {
} }`); expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-PROP-READONLY")).toBe(false); }); test("readonly prop diagnostics still reject direct prop assignments", () => { const diagnostics = diagnose(`component Invalid { props { open: boolean = false } functions { client function mutate() { open = true } } view {
} }`); expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-PROP-READONLY")).toBe(true); }); test("browser type erasure removes typed catch bindings", () => { const source = `async function load(): Promise { try { await fetch("/api") } catch (error: any) { console.error(error) } }`; expect(eraseFunctionTypes(source)).toContain("catch (error)"); expect(eraseFunctionTypes(source)).not.toContain(": any"); }); test("diagnoses event syntax mistakenly used for loop directives", () => { const diagnostics = diagnose(`component Rows { view {
  • {row.name}
} }`); expect(diagnostics.filter((entry) => entry.code === "WRN-TEMPLATE-LOOP-DIRECTIVE")).toHaveLength( 2, ); });