release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 12m19s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-04 12:19:09 +05:30
parent 4cebacadfe
commit 72e4d3eceb
108 changed files with 2276 additions and 532 deletions
@@ -149,3 +149,63 @@ test("extracts v0.6 optional props, declared union options, and outputs", () =>
],
);
});
test("accepts unresolved dynamic expressions for typed component props", () => {
const featureCard = parseComponentMetadata(`component FeatureCard {
props {
external: boolean = false
disabled: boolean = false
showArrow: boolean = true
}
view { <article></article> }
}`);
const source = `component FeatureGrid {
props { items: unknown[] = [] }
view {
{#each items as item}
<FeatureCard
external='{item.external || false}'
disabled='{item.disabled || false}'
showArrow='{item.showArrow !== false}'
/>
{/each}
}
}`;
assert.equal(attributeValueType("{item.external || false}"), "unknown");
assert.equal(attributeValueType("{item.showArrow !== false}"), "boolean");
assert.deepEqual(validateComponentTags(source, new Map([["FeatureCard", featureCard]])), []);
});
test("continues to reject invalid literal boolean props", () => {
const target = parseComponentMetadata(`component Toggle {
props { enabled: boolean = false }
view { <button></button> }
}`);
const diagnostics = validateComponentTags(
`<Toggle enabled="definitely" />`,
new Map([["Toggle", target]]),
);
assert.deepEqual(
diagnostics.map(({ code }) => code),
["wrn-component-prop-type"],
);
});
test("accepts unresolved dynamic expressions for declared string options", () => {
const target = parseComponentMetadata(`component FeatureCard {
props { target: "_blank" | "_self" = "_self" }
view { <article></article> }
}`);
const dynamic = `<FeatureCard target='{item.target || ""}' />`;
assert.deepEqual(validateComponentTags(dynamic, new Map([["FeatureCard", target]])), []);
const invalidLiteral = validateComponentTags(
`<FeatureCard target="popup" />`,
new Map([["FeatureCard", target]]),
);
assert.deepEqual(
invalidLiteral.map(({ code }) => code),
["wrn-component-prop-option"],
);
});
+33
View File
@@ -201,3 +201,36 @@ page Test {
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});
test("does not interpret TypeScript generic types in outputs as HTML tags", () => {
const source = `component AuthForm {
outputs {
change(payload: { values?: Array<string | number | boolean | null | object>; sourceEvent?: Event })
}
view { <form><strong>Sign in</strong></form> }
}`;
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});
test("allows JavaScript-looking documentation text inside pre and code", () => {
const source = `page Docs {
view {
<pre><code><span class="code-muted">// One project, one language</span>
<span class="code-key">page</span> Dashboard {
&lt;button&gt;Ship&lt;/button&gt;
}</code></pre>
}
}`;
assert.deepEqual(validateHtmlTags(mockDocument(source), source), []);
});
test("still reports genuinely mismatched view tags", () => {
const source = `page Broken {
view { <main><strong>Broken</main> }
}`;
const diagnostics = validateHtmlTags(mockDocument(source), source);
assert.equal(diagnostics.length, 1);
assert.equal(diagnostics[0].code, "wrn-mismatched-html-tag");
});
+45
View File
@@ -255,3 +255,48 @@ test("formats v0.6 grouped state and outputs blocks", () => {
/\n {2}outputs \{\n {4}confirm\(payload: ConfirmPayload\)\n {4}cancel\(\)\n {2}\}/,
);
});
test("preserves preformatted WRN code examples", () => {
const source = `page Docs {
view {
<pre><code><span class="code-muted">// One project, one language</span>
<span class="code-key">page</span> ProductDashboard {
<span class="code-key">state</span> count: number = 0
&lt;button @click='count++'&gt;Ship {count}&lt;/button&gt;
}</code></pre>
}
}
`;
const options = { insertSpaces: true, tabSize: 2, printWidth: 100 };
const formatted = formatWrn(source, options);
assert.match(
formatted,
/<pre><code><span class="code-muted">\/\/ One project, one language<\/span>\n<span class="code-key">page<\/span>/,
);
assert.equal(formatWrn(formatted, options), formatted);
});
test("keeps attribute-free opening tags intact when content is long", () => {
const source = `page Docs {
view {
<p>This documentation sentence is deliberately long enough to exceed the formatter print width without splitting the p opening delimiter.</p>
}
}
`;
const options = { insertSpaces: true, tabSize: 2, printWidth: 70 };
const formatted = formatWrn(source, options);
assert.match(formatted, /^ {4}<p>$/m);
assert.doesNotMatch(formatted, /^ {4}<p\n {4}>$/m);
assert.equal(formatWrn(formatted, options), formatted);
});
test("preserves balanced compact sibling markup without indentation drift", () => {
const source = `page Compact {
view {
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
}
}\n`;
const formatted = formatWrn(source, { tabSize: 2, printWidth: 80 });
assert.match(formatted, /<div class="strip"><span>Page<\/span><b>→<\/b><span>API<\/span><\/div>/);
assert.equal(formatWrn(formatted, { tabSize: 2, printWidth: 80 }), formatted);
});
+141
View File
@@ -0,0 +1,141 @@
"use strict";
const assert = require("node:assert");
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const { test } = require("node:test");
const root = path.resolve(__dirname, "../../..");
const server = path.join(root, "editors", "vscode", "src", "language-server.cjs");
function rpc(value) {
const body = Buffer.from(JSON.stringify(value));
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`), body]);
}
function parseResponses(output) {
const responses = [];
let position = 0;
while (position < output.length) {
const headerEnd = output.indexOf(Buffer.from("\r\n\r\n"), position);
if (headerEnd < 0) break;
const header = output.subarray(position, headerEnd).toString();
const length = Number(/Content-Length:\s*(\d+)/i.exec(header)?.[1] || 0);
const start = headerEnd + 4;
const end = start + length;
if (!length || end > output.length) break;
responses.push(JSON.parse(output.subarray(start, end).toString()));
position = end;
}
return responses;
}
function run(messages) {
const result = spawnSync(process.execPath, [server], {
cwd: root,
input: Buffer.concat(messages.map(rpc)),
timeout: 10_000,
});
assert.equal(result.status, 0, (result.stderr || Buffer.alloc(0)).toString());
return parseResponses(result.stdout || Buffer.alloc(0));
}
test("language server starts under Node and keeps official typed components clean", () => {
const files = [
path.join(root, "packages", "ui", "components", "AuthForm.wrn"),
path.join(root, "packages", "ui", "components", "FeatureGrid.wrn"),
];
const messages = [
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
{ jsonrpc: "2.0", method: "initialized", params: {} },
];
files.forEach((file, index) => {
messages.push({
jsonrpc: "2.0",
method: "textDocument/didOpen",
params: {
textDocument: {
uri: `file://${file}`,
languageId: "wrn",
version: index + 1,
text: fs.readFileSync(file, "utf8"),
},
},
});
});
messages.push({ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} });
messages.push({ jsonrpc: "2.0", method: "exit", params: {} });
const responses = run(messages);
assert.equal(responses.find((item) => item.id === 1)?.result?.serverInfo?.version, "0.8.3");
const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
assert.equal(diagnostics.length, 2);
assert.deepEqual(
diagnostics.map((item) => item.params.diagnostics),
[[], []],
);
});
test("language server formatter preserves code samples and compact sibling markup", () => {
const source = `page Docs {
view {
<pre><code><span class="muted">// One project, one language</span></code></pre>
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
}
}\n`;
const uri = "file:///tmp/wrnexus-docs.wrn";
const responses = run([
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
{ jsonrpc: "2.0", method: "initialized", params: {} },
{
jsonrpc: "2.0",
method: "textDocument/didOpen",
params: { textDocument: { uri, languageId: "wrn", version: 1, text: source } },
},
{
jsonrpc: "2.0",
id: 2,
method: "textDocument/formatting",
params: { textDocument: { uri }, options: { tabSize: 2, insertSpaces: true } },
},
{ jsonrpc: "2.0", id: 3, method: "shutdown", params: {} },
{ jsonrpc: "2.0", method: "exit", params: {} },
]);
const formatted = responses.find((item) => item.id === 2)?.result?.[0]?.newText;
assert.ok(formatted);
assert.match(formatted, /\/\/ One project, one language/);
assert.match(formatted, /<span>Page<\/span><b>→<\/b><span>API<\/span>/);
});
test("language server runs TypeScript diagnostics with workspace standard libraries", () => {
const uri = "file:///tmp/wrnexus-invalid-state.wrn";
const source = `component InvalidState {
outputs { save(payload: { id: string }) }
functions {
client function submit(value: string): void {
output.save({ id: 123 })
}
}
view { <button @click="submit('demo')"></button> }
}
`;
const responses = run([
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
{ jsonrpc: "2.0", method: "initialized", params: {} },
{
jsonrpc: "2.0",
method: "textDocument/didOpen",
params: { textDocument: { uri, languageId: "wrn", version: 1, text: source } },
},
{ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} },
{ jsonrpc: "2.0", method: "exit", params: {} },
]);
const diagnostics = responses.find((item) => item.method === "textDocument/publishDiagnostics")
?.params?.diagnostics;
assert.ok(Array.isArray(diagnostics));
assert.ok(
diagnostics.some((diagnostic) => diagnostic.code === "WRN-TYPE-2322"),
JSON.stringify(diagnostics, null, 2),
);
});