Files
2026-07-24 12:46:44 +05:30

109 lines
3.1 KiB
TypeScript

import { expect, test } from "bun:test";
import { diagnose, formatDiagnostic, parse } from "../src/index.ts";
test("parses WRN 0.3 execution, data, security, and reactivity blocks", () => {
const ast = parse(`page Dashboard {
runtime = "universal"
hydrate = "visible"
state count = 1
computed {
doubled = count * 2
}
effect {
console.log(doubled)
}
security {
auth = "required"
csrf = "true"
}
load server {
return { count: 1 }
}
load client {
return { refreshed: true }
}
action save(input) {
return input
}
view { <button @click="count++">{doubled}</button> }
}`);
expect(ast.runtime).toBe("universal");
expect(ast.hydrate).toBe("visible");
expect(ast.computed).toEqual([{ name: "doubled", expr: "count * 2" }]);
expect(ast.effects).toHaveLength(1);
expect(ast.security).toEqual({ auth: "required", csrf: "true" });
expect(ast.loads.map((load) => load.mode)).toEqual(["server", "client"]);
expect(ast.actions).toEqual([expect.objectContaining({ name: "save", args: ["input"] })]);
});
test("diagnoses server-only interactive roots and accessibility issues", () => {
const diagnostics = diagnose(
`component AvatarButton {
runtime = "server"
state open = false
view {
<button @click="open = true"><img src="/avatar.png"></button>
}
}`,
{ file: "AvatarButton.wrn", accessibility: true },
);
expect(diagnostics.map((diagnostic) => diagnostic.code)).toContain(
"WRN-RUNTIME-SERVER-INTERACTIVE",
);
expect(diagnostics.map((diagnostic) => diagnostic.code)).toContain("WRN-A11Y-001");
});
test("formats parser diagnostics with stable codes and source locations", () => {
const source = `page Broken { runtime = "worker"\n view { <main></main> } }`;
const [diagnostic] = diagnose(source, { file: "Broken.wrn" });
expect(diagnostic?.code).toBe("WRN-RUNTIME-TARGET");
expect(formatDiagnostic(source, diagnostic!)).toContain("Broken.wrn");
});
test("parses keyed each blocks without changing legacy loop syntax", () => {
const keyed = parse(`component Rows {
props { rows = [] }
view {
{#each rows as row, index key row.id}
<p>{index}: {row.name}</p>
{/each}
}
}`);
const loop = keyed.view.find((node) => node.type === "each");
expect(loop).toEqual(
expect.objectContaining({
type: "each",
list: "rows",
item: "row",
index: "index",
key: "row.id",
}),
);
const legacy = parse(`component Rows {
props { rows = [] }
view { {#each rows as row}<p>{row.name}</p>{/each} }
}`);
expect(legacy.view.find((node) => node.type === "each")).toEqual(
expect.objectContaining({ type: "each", key: undefined }),
);
});
test("parses public component event declarations inside props", () => {
const ast = parse(`component SearchBox {
props {
value = ""
@event search = function
@event clear = function
}
view { <input value="{value}" /> }
}`);
expect(ast.props.map((prop) => prop.name)).toEqual(["value"]);
expect(ast.events).toEqual([{ name: "search" }, { name: "clear" }]);
});