121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { diagnose, 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<void> {
|
|
const result = await server.confirmed(payload)
|
|
output.confirm(result)
|
|
}
|
|
server async function confirmed(payload: PublicUser): Promise<PublicUser> {
|
|
return payload
|
|
}
|
|
shared function normalize(value: string): string {
|
|
return value.trim()
|
|
}
|
|
}
|
|
|
|
view { <button>{label}</button> }
|
|
}`;
|
|
|
|
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 { <div></div> }
|
|
}`);
|
|
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 { <div></div> }
|
|
}`);
|
|
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 { <div></div> }
|
|
}`);
|
|
expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-PROP-READONLY")).toBe(true);
|
|
});
|