refactor: migrate legacy wire namespace to wrn
This commit is contained in:
@@ -69,7 +69,7 @@ bun add @wrnexus/compiler
|
||||
|
||||
All exports come from the package root (`@wrnexus/compiler`).
|
||||
|
||||
### `compileWireFile(source: string): string`
|
||||
### `compileWrnFile(source: string): string`
|
||||
|
||||
Compile `.wrn` source to a TypeScript module string. Throws `ParseError` on invalid input. The output is prefixed with a `// compiled from .wrn` comment.
|
||||
|
||||
@@ -115,10 +115,10 @@ class Lexer {
|
||||
|
||||
### Errors
|
||||
|
||||
| Class | Thrown by | Meaning |
|
||||
| ------------ | ------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| `ParseError` | `parse`, `compile`, `compileWireFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. |
|
||||
| `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. |
|
||||
| Class | Thrown by | Meaning |
|
||||
| ------------ | ------------------------------------------------ | --------------------------------------------------------------- |
|
||||
| `ParseError` | `parse`, `compile`, `compileWrnFile`, `generate` | Invalid `.wrn` grammar or (rewrapped) lex failure. |
|
||||
| `LexError` | `Lexer` | Unexpected character / unterminated string / unbalanced braces. |
|
||||
|
||||
### AST types
|
||||
|
||||
@@ -143,9 +143,9 @@ Exported type-only symbols describing the parsed tree:
|
||||
Compile a page:
|
||||
|
||||
```ts
|
||||
import { compileWireFile } from "@wrnexus/compiler";
|
||||
import { compileWrnFile } from "@wrnexus/compiler";
|
||||
|
||||
const ts = compileWireFile(`
|
||||
const ts = compileWrnFile(`
|
||||
page Home {
|
||||
state count = 0
|
||||
seo { title = "Home" description = "Welcome" }
|
||||
@@ -214,4 +214,4 @@ A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` bo
|
||||
## Requirements / Notes
|
||||
|
||||
- Pure TypeScript with no runtime dependencies; runs under **Bun** as part of the WrNexus toolchain (Node is not supported).
|
||||
- Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wire*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader.
|
||||
- Generated modules target WrNexus runtime primitives (`data-scope`, `data-text`, `data-on-*`, `data-for`, `data-component`, `__wrnexus*`/`__wrn*` helpers) — consume the output within a WrNexus app, e.g. via `@wrnexus/core`'s dev loader.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.8.8",
|
||||
"version": "0.8.9",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -508,7 +508,7 @@ function compileIfExpr(node: IfNode): string {
|
||||
|
||||
/**
|
||||
* Collect every server-control expression in a view (recursively): `{#each}` list
|
||||
* expressions and `{#if}` conditions. Used to wire up raw SSR data consts.
|
||||
* expressions and `{#if}` conditions. Used to wrn up raw SSR data consts.
|
||||
*/
|
||||
function collectControlExprs(nodes: ViewNode[], out: string[] = []): string[] {
|
||||
for (const node of nodes) {
|
||||
@@ -727,7 +727,7 @@ function renderNestedComponentInvocation(
|
||||
.map((attr) => {
|
||||
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(attr.name);
|
||||
if (spread) {
|
||||
return `\${__wireSpreadAttrs(${ctx.resolveExpr(spread[1]!)})}`;
|
||||
return `\${__wrnSpreadAttrs(${ctx.resolveExpr(spread[1]!)})}`;
|
||||
}
|
||||
|
||||
if (attr.event) {
|
||||
@@ -745,7 +745,7 @@ function renderNestedComponentInvocation(
|
||||
const wholeExpression = wholeAttributeExpression(attr.value);
|
||||
|
||||
const compiledValue = wholeExpression
|
||||
? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
|
||||
? `\${__wrnProp(${ctx.resolveExpr(wholeExpression)})}`
|
||||
: compileAttrValue(attr.value, ctx);
|
||||
|
||||
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||
@@ -778,7 +778,7 @@ function renderNestedComponentInvocation(
|
||||
|
||||
return (
|
||||
`<div data-component="${attrEscape(node.tag)}"` +
|
||||
`${ctx.forwardRestAttrs ? "${__wireSpreadAttrs(__attrs)}" : ""}` +
|
||||
`${ctx.forwardRestAttrs ? "${__wrnSpreadAttrs(__attrs)}" : ""}` +
|
||||
`${attrs}>${inner}</div>`
|
||||
);
|
||||
}
|
||||
@@ -1910,7 +1910,7 @@ function viewHasRestAttributeSpread(nodes: ViewNode[]): boolean {
|
||||
/**
|
||||
* Compile a text node. Interpolations that reference state stay as client
|
||||
* mustaches (`{expr}`, hydrated by the reactive runtime); interpolations of
|
||||
* props/constants are baked server-side (`${__wireHtml(expr)}`), so static
|
||||
* props/constants are baked server-side (`${__wrnHtml(expr)}`), so static
|
||||
* components render correct HTML with zero JavaScript.
|
||||
*/
|
||||
function compileText(raw: string, ctx: CompCtx): string {
|
||||
@@ -1929,17 +1929,17 @@ function compileText(raw: string, ctx: CompCtx): string {
|
||||
// list renderer fills it per item; it has no server-side value.
|
||||
out += escLit(`{${expr}}`);
|
||||
} else if (expr === "content") {
|
||||
out += `\${__wireRaw(${ctx.resolveExpr(expr)})}`;
|
||||
out += `\${__wrnRaw(${ctx.resolveExpr(expr)})}`;
|
||||
} else if (exprRefsComponentReactiveValue(expr, ctx)) {
|
||||
// State interpolation: bake the initial value AND keep it reactive via a
|
||||
// data-text span, so no-JS clients see the real value and hydration
|
||||
// updates it in place. `count` → `<span data-text="count">0</span>`.
|
||||
out +=
|
||||
escLit(`<span data-text="${attrEscape(expr)}">`) +
|
||||
`\${__wireHtml(${ctx.resolveExpr(expr)})}` +
|
||||
`\${__wrnHtml(${ctx.resolveExpr(expr)})}` +
|
||||
escLit(`</span>`);
|
||||
} else {
|
||||
out += `\${__wireHtml(${ctx.resolveExpr(expr)})}`;
|
||||
out += `\${__wrnHtml(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
@@ -1959,7 +1959,7 @@ function compileAttrValue(raw: string, ctx: CompCtx): string {
|
||||
if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
|
||||
out += escLit(`{${expr}}`); // hydrated per-item by the list renderer
|
||||
} else {
|
||||
out += `\${__wireAttr(${ctx.resolveExpr(expr)})}`;
|
||||
out += `\${__wrnAttr(${ctx.resolveExpr(expr)})}`;
|
||||
}
|
||||
last = m.index + m[0].length;
|
||||
}
|
||||
@@ -2152,7 +2152,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
.map((a) => {
|
||||
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(a.name);
|
||||
if (spread) {
|
||||
return `\${__wireSpreadAttrs(${elementContext.resolveExpr(spread[1]!)})}`;
|
||||
return `\${__wrnSpreadAttrs(${elementContext.resolveExpr(spread[1]!)})}`;
|
||||
}
|
||||
|
||||
if (a.event) {
|
||||
@@ -2181,7 +2181,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
: "";
|
||||
/*
|
||||
* A boolean attribute whose expression names a loop variable cannot
|
||||
* be resolved on the server: __wireBooleanAttr runs at render time,
|
||||
* be resolved on the server: __wrnBooleanAttr runs at render time,
|
||||
* where `row` or `item` simply does not exist, and the emitted
|
||||
* module blew up. Leave the attribute off the server output and let
|
||||
* the client bind set it -- the runtime toggles boolean attributes
|
||||
@@ -2193,7 +2193,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
attrEscape(JSON.stringify([a.name, a.value])),
|
||||
)}"`;
|
||||
}
|
||||
return `\${__wireBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker}`;
|
||||
return `\${__wrnBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker}`;
|
||||
}
|
||||
|
||||
if (a.value === "false") return "";
|
||||
@@ -2219,7 +2219,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
const compiledValue =
|
||||
isExplicitComponentMount && wholeExpression
|
||||
? `\${__wireProp(${elementContext.resolveExpr(wholeExpression)})}`
|
||||
? `\${__wrnProp(${elementContext.resolveExpr(wholeExpression)})}`
|
||||
: compileAttrValue(a.value, elementContext);
|
||||
const rendered = ` ${a.name}="${compiledValue}"`;
|
||||
|
||||
@@ -2297,7 +2297,7 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||
|
||||
const allAttrs =
|
||||
`${loopLocalsAttribute}` +
|
||||
`${ctx.forwardRestAttrs ? "${__wireSpreadAttrs(__attrs)}" : ""}` +
|
||||
`${ctx.forwardRestAttrs ? "${__wrnSpreadAttrs(__attrs)}" : ""}` +
|
||||
`${
|
||||
ctx.eventNames?.length ? ` data-wrn-events="${attrEscape(ctx.eventNames.join(","))}"` : ""
|
||||
}` +
|
||||
@@ -2584,7 +2584,7 @@ function __restProps(
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: unknown): string {
|
||||
function __wrnHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -2596,7 +2596,7 @@ function __wireHtml(v: unknown): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: unknown): string {
|
||||
function __wrnAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -2610,7 +2610,7 @@ function __wireAttr(v: unknown): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
function __wrnBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -2621,7 +2621,7 @@ function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
function __wrnSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
|
||||
@@ -2646,27 +2646,27 @@ function __wireSpreadAttrs(value: unknown): string {
|
||||
}
|
||||
|
||||
if (booleanAttributes.has(lowerName)) {
|
||||
attributes.push(__wireBooleanAttr(name, raw));
|
||||
attributes.push(__wrnBooleanAttr(name, raw));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (raw === false || raw === null || raw === undefined) continue;
|
||||
attributes.push(" " + name + '="' + __wireAttr(raw) + '"');
|
||||
attributes.push(" " + name + '="' + __wrnAttr(raw) + '"');
|
||||
}
|
||||
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: unknown): string {
|
||||
function __wrnProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
: String(v == null ? "" : v);
|
||||
|
||||
return __wireAttr(value);
|
||||
return __wrnAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: unknown): string {
|
||||
function __wrnRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}`);
|
||||
|
||||
@@ -2747,7 +2747,7 @@ function __wireRaw(v: unknown): string {
|
||||
return out.join("\n\n") + "\n";
|
||||
}
|
||||
|
||||
function __wireRaw(v: unknown): string {
|
||||
function __wrnRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}
|
||||
|
||||
@@ -2756,23 +2756,23 @@ function wholeAttributeExpression(value: string): string | null {
|
||||
|
||||
return match?.[1]?.trim() || null;
|
||||
}
|
||||
function __wireHtml(v: unknown): string {
|
||||
function __wrnHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
|
||||
c === "&" ? "&" : c === "<" ? "<" : ">",
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: unknown): string {
|
||||
function __wrnAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
||||
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
||||
);
|
||||
}
|
||||
|
||||
function __wireProp(v: unknown): string {
|
||||
function __wrnProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
|
||||
|
||||
return __wireAttr(value);
|
||||
return __wrnAttr(value);
|
||||
}
|
||||
function renderPageComponentAttr(attr: Attr, dynamicExpressions: string[]): string {
|
||||
if (attr.event) {
|
||||
|
||||
@@ -83,7 +83,7 @@ export interface CompileResult {
|
||||
}
|
||||
|
||||
/** Compile `.wrn` source into an Expo Router React Native screen. */
|
||||
export function compileNativeWireFile(source: string): string {
|
||||
export function compileNativeWrnFile(source: string): string {
|
||||
const ast = parse(source);
|
||||
assertValidAst(ast);
|
||||
return generateNative(ast);
|
||||
@@ -93,7 +93,7 @@ export function compileNativeWireFile(source: string): string {
|
||||
* Compile `.wrn` source into TypeScript source. Errors include a stable code,
|
||||
* source location, code frame, and actionable hint whenever available.
|
||||
*/
|
||||
export function compileWireFile(source: string, filePath = "<inline .wrn>"): string {
|
||||
export function compileWrnFile(source: string, filePath = "<inline .wrn>"): string {
|
||||
try {
|
||||
const ast = parse(source);
|
||||
assertValidAst(ast, { file: filePath, accessibility: true });
|
||||
|
||||
@@ -204,7 +204,7 @@ function __diagnostic(code, message, details) {
|
||||
}
|
||||
function __csrfToken() {
|
||||
if (typeof document === "undefined") return undefined;
|
||||
const match = /(?:^|;\\s*)wire-csrf=([^;]+)/.exec(document.cookie || "");
|
||||
const match = /(?:^|;\\s*)wrn-csrf=([^;]+)/.exec(document.cookie || "");
|
||||
return match ? decodeURIComponent(match[1]) : undefined;
|
||||
}
|
||||
async function __callServerFunction(storeName, functionName, args, options) {
|
||||
|
||||
@@ -121,7 +121,7 @@ function __restProps(
|
||||
);
|
||||
}
|
||||
|
||||
function __wireHtml(v: unknown): string {
|
||||
function __wrnHtml(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>]/g,
|
||||
(c) =>
|
||||
@@ -133,7 +133,7 @@ function __wireHtml(v: unknown): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireAttr(v: unknown): string {
|
||||
function __wrnAttr(v: unknown): string {
|
||||
return String(v == null ? "" : v).replace(
|
||||
/[&<>"]/g,
|
||||
(c) =>
|
||||
@@ -147,7 +147,7 @@ function __wireAttr(v: unknown): string {
|
||||
);
|
||||
}
|
||||
|
||||
function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
function __wrnBooleanAttr(name: string, value: unknown): string {
|
||||
return value === true ||
|
||||
value === "true" ||
|
||||
value === "" ||
|
||||
@@ -158,7 +158,7 @@ function __wireBooleanAttr(name: string, value: unknown): string {
|
||||
: "";
|
||||
}
|
||||
|
||||
function __wireSpreadAttrs(value: unknown): string {
|
||||
function __wrnSpreadAttrs(value: unknown): string {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
|
||||
|
||||
const booleanAttributes = new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);
|
||||
@@ -183,27 +183,27 @@ function __wireSpreadAttrs(value: unknown): string {
|
||||
}
|
||||
|
||||
if (booleanAttributes.has(lowerName)) {
|
||||
attributes.push(__wireBooleanAttr(name, raw));
|
||||
attributes.push(__wrnBooleanAttr(name, raw));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (raw === false || raw === null || raw === undefined) continue;
|
||||
attributes.push(" " + name + '="' + __wireAttr(raw) + '"');
|
||||
attributes.push(" " + name + '="' + __wrnAttr(raw) + '"');
|
||||
}
|
||||
|
||||
return attributes.join("");
|
||||
}
|
||||
|
||||
function __wireProp(v: unknown): string {
|
||||
function __wrnProp(v: unknown): string {
|
||||
const value =
|
||||
v !== null && typeof v === "object"
|
||||
? JSON.stringify(v)
|
||||
: String(v == null ? "" : v);
|
||||
|
||||
return __wireAttr(value);
|
||||
return __wrnAttr(value);
|
||||
}
|
||||
|
||||
function __wireRaw(v: unknown): string {
|
||||
function __wrnRaw(v: unknown): string {
|
||||
return String(v == null ? "" : v);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ export function render(props: CounterProps = {} as CounterProps): string {
|
||||
const __scope = __wrnexusScopeDecl(__scopeState);
|
||||
const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");
|
||||
return \`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}" data-wrn-behavior="eyJmdW5jdGlvbnMiOiJmdW5jdGlvbiBpbmNyZW1lbnQoKXtcbiAgICAgIGNvdW50ID0gY291bnQgKyAxXG4gICAgICBvdXRwdXQuY2hhbmdlKGNvdW50KVxuICAgIH0iLCJvdXRwdXRzIjpbeyJuYW1lIjoiY2hhbmdlIiwicGF5bG9hZCI6eyJuYW1lIjoidmFsdWUiLCJ2YWx1ZVR5cGUiOiJudW1iZXIiLCJvcHRpb25hbCI6ZmFsc2V9fV0sImNvbXB1dGVkIjpbXSwiZWZmZWN0cyI6W10sImxpZmVjeWNsZSI6e30sIndhdGNoZXMiOltdfQ==" data-wrn-hydration="Counter:1skggk6" data-wrn-hydrate="load" data-wrn-runtime="universal" data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__">
|
||||
<div data-component="Button"\${__wireSpreadAttrs(__attrs)} on:click="\${__wireProp(increment)}"><span data-text="label">\${__wireHtml(label)}</span>: <span data-text="count">\${__wireHtml(count)}</span></div>
|
||||
<div data-component="Button"\${__wrnSpreadAttrs(__attrs)} on:click="\${__wrnProp(increment)}"><span data-text="label">\${__wrnHtml(label)}</span>: <span data-text="count">\${__wrnHtml(count)}</span></div>
|
||||
</div>\`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { generate, parse } from "../src/index.ts";
|
||||
import { compileWireFile } from "../src/index.ts";
|
||||
import { compileWrnFile } from "../src/index.ts";
|
||||
import { mountHtml } from "@wrnexus/test";
|
||||
|
||||
test("explicit static rendering disables hydration metadata", () => {
|
||||
const output = compileWireFile(`page StaticPage {
|
||||
const output = compileWrnFile(`page StaticPage {
|
||||
render = "static"
|
||||
state count = 0
|
||||
view { <button @click="count++">{count}</button> }
|
||||
@@ -19,7 +19,7 @@ test("explicit static rendering disables hydration metadata", () => {
|
||||
});
|
||||
|
||||
test("named data loads compile as parallel typed data entries", () => {
|
||||
const output = compileWireFile(`page Users {
|
||||
const output = compileWrnFile(`page Users {
|
||||
load users { return ["Ada"] }
|
||||
load server teams { return ["Core"] }
|
||||
view { <p>Users</p> }
|
||||
@@ -31,10 +31,10 @@ test("named data loads compile as parallel typed data entries", () => {
|
||||
let seq = 0;
|
||||
/** Compile a `.wrn` source and import the resulting module. */
|
||||
async function compileAndImport(src: string): Promise<Record<string, unknown>> {
|
||||
const dir = join(tmpdir(), "wire-compiler-test");
|
||||
const dir = join(tmpdir(), "wrn-compiler-test");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const file = join(dir, `m${seq++}.ts`);
|
||||
writeFileSync(file, compileWireFile(src));
|
||||
writeFileSync(file, compileWrnFile(src));
|
||||
return import(pathToFileURL(file).href);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ test("component event declarations compile to public root metadata", async () =>
|
||||
});
|
||||
|
||||
test("explicit nested component mounts serialize structured expression props", () => {
|
||||
const output = compileWireFile(`component Shell {
|
||||
const output = compileWrnFile(`component Shell {
|
||||
state brand = { "label": "WRNexus" }
|
||||
state items = [{ "label": "Home", "href": "/" }]
|
||||
view {
|
||||
@@ -129,8 +129,8 @@ test("explicit nested component mounts serialize structured expression props", (
|
||||
}`);
|
||||
|
||||
expect(output).toContain('data-component="Navbar"');
|
||||
expect(output).toContain('brand="${__wireProp(brand)}"');
|
||||
expect(output).toContain('items="${__wireProp(items)}"');
|
||||
expect(output).toContain('brand="${__wrnProp(brand)}"');
|
||||
expect(output).toContain('items="${__wrnProp(items)}"');
|
||||
});
|
||||
|
||||
test("typed props, required props, state, custom types, and function parameters compile", () => {
|
||||
@@ -212,7 +212,7 @@ test("HTML view: void elements, boolean attrs, comments, lone <", () => {
|
||||
const ast = parse(
|
||||
`component T {\n view {\n <input type="text" disabled>\n <br/>\n <!-- comment -->\n <p>a < b</p>\n }\n}`,
|
||||
);
|
||||
const html = compileWireFile(
|
||||
const html = compileWrnFile(
|
||||
`component T {\n view {\n <input type="text" disabled>\n <br/>\n <!-- comment -->\n <p>a < b</p>\n }\n}`,
|
||||
);
|
||||
expect(html).toContain("<input");
|
||||
@@ -310,11 +310,11 @@ test("an explicit attrs spread overrides automatic root forwarding", async () =>
|
||||
|
||||
test("prop-driven component renders SSR content and exposes reactive prop signals", async () => {
|
||||
const mod = await compileAndImport(
|
||||
`component Button {\n props {\n label = "Button"\n variant = "default"\n class = ""\n }\n view { <button class="wire-btn wire-btn--{variant} {class}">{label}</button> }\n}`,
|
||||
`component Button {\n props {\n label = "Button"\n variant = "default"\n class = ""\n }\n view { <button class="wrn-btn wrn-btn--{variant} {class}">{label}</button> }\n}`,
|
||||
);
|
||||
const render = mod.render as (p: Record<string, string>) => string;
|
||||
const out = render({ label: "Save <b>", variant: "primary", class: "mt-2" });
|
||||
expect(out).toContain('class="wire-btn wire-btn--primary mt-2"');
|
||||
expect(out).toContain('class="wrn-btn wrn-btn--primary mt-2"');
|
||||
expect(out).toContain("Save <b>"); // html-escaped
|
||||
expect(out).toContain("data-scope");
|
||||
expect(out).toContain('data-text="label"');
|
||||
@@ -353,7 +353,7 @@ test("prop type coercion follows the default value's type", async () => {
|
||||
});
|
||||
|
||||
test("platform events compile through the native runtime bridge", () => {
|
||||
const out = compileWireFile(`page Platform {
|
||||
const out = compileWrnFile(`page Platform {
|
||||
state count = 0
|
||||
view {
|
||||
<button @browser-click="count++" @mobile-click="count = count + 2">Run</button>
|
||||
@@ -364,20 +364,20 @@ test("platform events compile through the native runtime bridge", () => {
|
||||
});
|
||||
|
||||
test("{t:key} compiles to a data-t marker (both pages and components)", () => {
|
||||
const page = compileWireFile(`page P {\n view { <h1>{t:home.title}</h1> }\n}`);
|
||||
const page = compileWrnFile(`page P {\n view { <h1>{t:home.title}</h1> }\n}`);
|
||||
expect(page).toContain('<span data-t="home.title"></span>');
|
||||
const comp = compileWireFile(`component C {\n view { <h1>{t:x}</h1> }\n}`);
|
||||
const comp = compileWrnFile(`component C {\n view { <h1>{t:x}</h1> }\n}`);
|
||||
expect(comp).toContain('<span data-t="x"></span>');
|
||||
});
|
||||
|
||||
test("{t:} does NOT wrap a page in a data-scope (regression)", () => {
|
||||
// Only state / events force a reactive scope, not i18n markers.
|
||||
const page = compileWireFile(`page P {\n view { <h1>{t:a}</h1> <p>{t:b}</p> }\n}`);
|
||||
const page = compileWrnFile(`page P {\n view { <h1>{t:a}</h1> <p>{t:b}</p> }\n}`);
|
||||
expect(page).not.toContain("data-scope");
|
||||
});
|
||||
|
||||
test("page state text bakes its initial value into a reactive data-text span", () => {
|
||||
const page = compileWireFile(
|
||||
const page = compileWrnFile(
|
||||
`page Reactive {\n state count = 3\n view { <p>Count is {count}, doubled {count * 2}</p> }\n}`,
|
||||
);
|
||||
expect(page).toContain('<span data-text="count">3</span>'); // no-JS sees "3"
|
||||
@@ -386,7 +386,7 @@ test("page state text bakes its initial value into a reactive data-text span", (
|
||||
});
|
||||
|
||||
test("page state attributes bake their initial value and retain a reactive binding", () => {
|
||||
const page = compileWireFile(`page Password {
|
||||
const page = compileWrnFile(`page Password {
|
||||
state show = false
|
||||
view {
|
||||
<input type="{show ? 'text' : 'password'}" aria-label="{show ? 'Hide' : 'Show'}">
|
||||
@@ -418,18 +418,18 @@ test("request-dependent page state attributes resolve during server rendering",
|
||||
});
|
||||
|
||||
test("data-for: loop-variable mustaches stay literal (not baked server-side)", () => {
|
||||
const comp = compileWireFile(
|
||||
const comp = compileWrnFile(
|
||||
`component TodoList {\n state todos = []\n view { <ul><li data-for="t in todos">{t.text}</li></ul> }\n}`,
|
||||
);
|
||||
// The <li> template keeps `{t.text}` for the client list renderer, and the
|
||||
// loop variable is never baked (which would be a server-side ReferenceError).
|
||||
expect(comp).toContain('data-for="t in todos"');
|
||||
expect(comp).toContain("{t.text}");
|
||||
expect(comp).not.toContain("__wireHtml(t.text)");
|
||||
expect(comp).not.toContain("__wrnHtml(t.text)");
|
||||
});
|
||||
|
||||
test("named slots pass through to the component output", () => {
|
||||
const out = compileWireFile(
|
||||
const out = compileWrnFile(
|
||||
`component Card {\n view { <div><slot name="header"></slot><slot></slot></div> }\n}`,
|
||||
);
|
||||
expect(out).toContain('<slot name="header">');
|
||||
@@ -497,9 +497,9 @@ component ParentCard {
|
||||
|
||||
expect(output).toContain('data-component="ChildCard"');
|
||||
|
||||
expect(output).toContain('title="${__wireProp(title)}"');
|
||||
expect(output).toContain('title="${__wrnProp(title)}"');
|
||||
|
||||
expect(output).toContain("function __wireProp");
|
||||
expect(output).toContain("function __wrnProp");
|
||||
});
|
||||
|
||||
test("native array and object props serialize through component mounts", async () => {
|
||||
@@ -561,7 +561,7 @@ layout AppLayout {
|
||||
|
||||
expect(output).toContain('export const __wrnexusLayout = "AppLayout"');
|
||||
|
||||
expect(output).toContain("${__wireRaw(content)}");
|
||||
expect(output).toContain("${__wrnRaw(content)}");
|
||||
|
||||
expect(output).toContain("export function render");
|
||||
});
|
||||
@@ -1298,7 +1298,7 @@ test("WRN 0.3 metadata, computed values, loaders, and actions compile additively
|
||||
action save(input) { return input }
|
||||
view { <button @click="count++">{doubled}</button> }
|
||||
}`;
|
||||
const output = compileWireFile(source);
|
||||
const output = compileWrnFile(source);
|
||||
|
||||
expect(output).toContain('export const __wrnexusRuntime = "universal"');
|
||||
expect(output).toContain('export const __wrnexusHydrate = "idle"');
|
||||
@@ -1393,15 +1393,15 @@ page Pricing {
|
||||
});
|
||||
|
||||
test("style blocks compile with stable page/component/layout metadata", () => {
|
||||
const page = compileWireFile(`page StyledPage {
|
||||
const page = compileWrnFile(`page StyledPage {
|
||||
style { .shared { color: red; } }
|
||||
view { <main class="shared">Page</main> }
|
||||
}`);
|
||||
const component = compileWireFile(`component StyledCard {
|
||||
const component = compileWrnFile(`component StyledCard {
|
||||
style { .shared { color: blue; } }
|
||||
view { <article class="shared">Card</article> }
|
||||
}`);
|
||||
const layout = compileWireFile(`layout StyledLayout {
|
||||
const layout = compileWrnFile(`layout StyledLayout {
|
||||
style { .shared { color: green; } }
|
||||
view { <div class="shared"><slot></slot></div> }
|
||||
}`);
|
||||
@@ -1416,7 +1416,7 @@ test("style blocks compile with stable page/component/layout metadata", () => {
|
||||
});
|
||||
|
||||
test("strict-mode reserved prop names compile through safe local references", () => {
|
||||
const output = compileWireFile(`component Visibility {
|
||||
const output = compileWrnFile(`component Visibility {
|
||||
props { private: boolean = false }
|
||||
view { {#if private}<span>Private</span>{/if} }
|
||||
}`);
|
||||
@@ -1441,7 +1441,7 @@ test("data-show keeps its expression instead of being interpolated away", () =>
|
||||
}
|
||||
}
|
||||
`;
|
||||
const code = compileWireFile(source, "Panel.wrn");
|
||||
const code = compileWrnFile(source, "Panel.wrn");
|
||||
expect(code).toContain(`data-show="open || visible"`);
|
||||
expect(code).not.toContain(`data-show="false"`);
|
||||
});
|
||||
@@ -1467,14 +1467,14 @@ test("a local variable may shadow a prop without breaking the module", () => {
|
||||
}
|
||||
}
|
||||
`;
|
||||
const code = compileWireFile(source, "Sized.wrn");
|
||||
const code = compileWrnFile(source, "Sized.wrn");
|
||||
// The alias must be dropped, not emitted alongside the local declaration.
|
||||
expect(code).not.toContain("const { size } = context.props;");
|
||||
expect(code).toContain("var size = 4");
|
||||
});
|
||||
|
||||
// A boolean attribute whose expression names a loop variable cannot be
|
||||
// resolved on the server -- __wireBooleanAttr runs at render time, where the
|
||||
// resolved on the server -- __wrnBooleanAttr runs at render time, where the
|
||||
// loop variable does not exist, and the generated module failed outright.
|
||||
test("a boolean attribute bound to a loop variable binds on the client", () => {
|
||||
const source = `component Picker {
|
||||
@@ -1493,14 +1493,14 @@ test("a boolean attribute bound to a loop variable binds on the client", () => {
|
||||
}
|
||||
}
|
||||
`;
|
||||
const code = compileWireFile(source, "Picker.wrn");
|
||||
expect(code).not.toContain('__wireBooleanAttr("checked"');
|
||||
const code = compileWrnFile(source, "Picker.wrn");
|
||||
expect(code).not.toContain('__wrnBooleanAttr("checked"');
|
||||
expect(code).toContain("data-wrn-bind-");
|
||||
});
|
||||
|
||||
test("block comments inside props report the authoring restriction", () => {
|
||||
expect(() =>
|
||||
compileWireFile(`component Example {
|
||||
compileWrnFile(`component Example {
|
||||
props {
|
||||
/* use a line comment */
|
||||
label: string = "Example"
|
||||
@@ -1512,7 +1512,7 @@ test("block comments inside props report the authoring restriction", () => {
|
||||
|
||||
test("state page reports its collision with the page keyword", () => {
|
||||
expect(() =>
|
||||
compileWireFile(`page Example {
|
||||
compileWrnFile(`page Example {
|
||||
state page = 1
|
||||
view { <span>{page}</span> }
|
||||
}`),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { NativeCompileError, compileNativeWireFile } from "../src/index.ts";
|
||||
import { NativeCompileError, compileNativeWrnFile } from "../src/index.ts";
|
||||
|
||||
test("compiles portable wrn markup to React Native components", () => {
|
||||
const code = compileNativeWireFile(`page Home {
|
||||
const code = compileNativeWrnFile(`page Home {
|
||||
state count = 0
|
||||
view {
|
||||
<main class="screen">
|
||||
@@ -22,13 +22,13 @@ test("compiles portable wrn markup to React Native components", () => {
|
||||
});
|
||||
|
||||
test("rejects browser-only elements with an actionable error", () => {
|
||||
expect(() => compileNativeWireFile("page Data { view { <table></table> } }")).toThrow(
|
||||
expect(() => compileNativeWrnFile("page Data { view { <table></table> } }")).toThrow(
|
||||
NativeCompileError,
|
||||
);
|
||||
});
|
||||
|
||||
test("compiles loops to native JSX", () => {
|
||||
const code = compileNativeWireFile(
|
||||
const code = compileNativeWrnFile(
|
||||
"page List { view { <ul>{#each items as item, i}<li>{item.name}</li>{:empty}<li>Empty</li>{/each}</ul> } }",
|
||||
);
|
||||
expect(code).toContain("(items).map((item, i)");
|
||||
@@ -36,7 +36,7 @@ test("compiles loops to native JSX", () => {
|
||||
});
|
||||
|
||||
test("selects mobile-only markup and events for native output", () => {
|
||||
const code = compileNativeWireFile(`page Platforms { view {
|
||||
const code = compileNativeWrnFile(`page Platforms { view {
|
||||
<button data-native-only="mobile" @mobile-click="save()" @browser-click="copy()">Save</button>
|
||||
<p data-native-only="browser">Browser help</p>
|
||||
} }`);
|
||||
@@ -47,14 +47,14 @@ test("selects mobile-only markup and events for native output", () => {
|
||||
|
||||
test("rejects declarative Capacitor actions instead of silently dropping them in Expo", () => {
|
||||
expect(() =>
|
||||
compileNativeWireFile(
|
||||
compileNativeWrnFile(
|
||||
`page Share { view { <button data-native-mobile="share">Share</button> } }`,
|
||||
),
|
||||
).toThrow("currently targets browser/Capacitor pages");
|
||||
});
|
||||
|
||||
test("does not emit native visibility directives as React Native props", () => {
|
||||
const code = compileNativeWireFile(
|
||||
const code = compileNativeWrnFile(
|
||||
`page Support { view { <p data-native-requires="camera">Camera</p> } }`,
|
||||
);
|
||||
expect(code).not.toContain("data-native-requires");
|
||||
|
||||
Reference in New Issue
Block a user