fix(tooling): reconcile framework fixes in canonical WireJS checkout
This commit is contained in:
1 parent
a077515336
commit
65d63dfbe7
19 files changed
+312
-156
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Window } from "happy-dom";
|
||||
import { Window, type HTMLInputElement, type HTMLOptionElement, type HTMLElement } from "happy-dom";
|
||||
|
||||
test("MFA uses the nested Select and restores continuation after hydration without reviving expired challenges", async () => {
|
||||
const window = new Window({ url: "https://sso.example.test/mfa" });
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { documentDiagnostics, virtualTypeScriptDocument } from "../src/index.ts";
|
||||
|
||||
test("editor diagnostics accept translated PageHeader attributes without hiding real errors", () => {
|
||||
const document = {
|
||||
uri: "file:///contact.wrn",
|
||||
text: `page Contact {
|
||||
view {
|
||||
<div data-component="PageHeader" eyebrow="{t:contact.eyebrow}" title="{t:contact.title}" description="{t:contact.description}"></div>
|
||||
<p title="{t:contact.title} {missingValue}"></p>
|
||||
}
|
||||
}`,
|
||||
};
|
||||
const diagnostics = documentDiagnostics(document).filter(
|
||||
(item) => item.source === "wrnexus-types",
|
||||
);
|
||||
expect(diagnostics).toHaveLength(1);
|
||||
expect(diagnostics[0]?.message).toContain("missingValue");
|
||||
expect(diagnostics[0]?.range.start.line).toBe(3);
|
||||
expect(virtualTypeScriptDocument(document).text).not.toContain("void (t:");
|
||||
expect(
|
||||
documentDiagnostics({ ...document, text: document.text.replace("{missingValue}", "") }).filter(
|
||||
(item) => item.source === "wrnexus-types",
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.8.18",
|
||||
"version": "0.8.19",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -768,9 +768,7 @@ function expandStructuredStateDeclarations(lines, unit) {
|
||||
}
|
||||
|
||||
function formatStructuredPropDeclaration(value, unit, depth, printWidth) {
|
||||
const match = /^([A-Za-z_$][\w$]*\??\s*(?::\s*[^=]+?)?\s*=\s*)([\[{][\s\S]*)$/.exec(
|
||||
value,
|
||||
);
|
||||
const match = /^([A-Za-z_$][\w$]*\??\s*(?::\s*[^=]+?)?\s*=\s*)([[{][\s\S]*)$/.exec(value);
|
||||
if (!match) return null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
Static type checking for `.wrn` declarations, props, state, outputs, functions, stores, and generated virtual TypeScript files.
|
||||
|
||||
The package is compiler tooling rather than a browser UI package, so its public kit consists of programmatic typecheck helpers and diagnostics.
|
||||
|
||||
View attribute `{t:key}` segments are translation markers, not TypeScript expressions. Ordinary expressions in the same attribute remain typechecked. Inline event bindings accept statement lists, with `event.currentTarget` typed as the bound HTML/SVG element. Nullable DOM queries still require normal guards.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/typecheck",
|
||||
"version": "0.8.16",
|
||||
"version": "0.8.17",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -377,6 +377,9 @@ export function virtualTypeScriptModule(
|
||||
// misspelled state/function name cannot silently become `undefined` at
|
||||
// runtime. Each/handler locals mirror the browser evaluator's bindings.
|
||||
let bindingIndex = 0;
|
||||
append(
|
||||
"type __WrnEventElement<Tag extends string> = Tag extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Tag] : Tag extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Tag] : HTMLElement;",
|
||||
);
|
||||
const reservedProps = new Set(
|
||||
ast.props.map((prop) => prop.name).filter((name) => !safeBindingName(name)),
|
||||
);
|
||||
@@ -386,7 +389,12 @@ export function virtualTypeScriptModule(
|
||||
for (const attr of node.attrs) {
|
||||
const expressions = attr.event
|
||||
? [attr.value]
|
||||
: extractInterpolations(attr.value).map((segment) => segment.expression);
|
||||
: extractInterpolations(attr.value)
|
||||
// Translation markers are resolved by i18n, not evaluated as JavaScript.
|
||||
// Filter individual segments so mixed translated/dynamic attributes
|
||||
// still typecheck their ordinary expressions.
|
||||
.filter((segment) => !/^t:[^{}]+$/.test(segment.expression))
|
||||
.map((segment) => segment.expression);
|
||||
for (const expression of expressions) {
|
||||
if (!expression.trim()) continue;
|
||||
const declarations = [
|
||||
@@ -394,12 +402,12 @@ export function virtualTypeScriptModule(
|
||||
...(attr.event
|
||||
? [
|
||||
"declare const payload: any;",
|
||||
"declare const event: Event & { target: HTMLElement };",
|
||||
`declare const event: Event & { target: HTMLElement; currentTarget: __WrnEventElement<${JSON.stringify(node.tag)}> };`,
|
||||
]
|
||||
: []),
|
||||
].join(" ");
|
||||
append(
|
||||
`namespace __wrn_view_${bindingIndex++} { ${declarations} void (${safeViewExpression(expression, reservedProps)}); }`,
|
||||
`namespace __wrn_view_${bindingIndex++} { ${declarations} ${attr.event ? `void (() => { ${safeViewExpression(expression, reservedProps)} });` : `void (${safeViewExpression(expression, reservedProps)});`} }`,
|
||||
attr.value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { checkWrnSource, virtualTypeScriptModule } from "../src/index.ts";
|
||||
|
||||
test("translation markers in native and component attributes are not TypeScript expressions", () => {
|
||||
const source = `page Contact {
|
||||
state count = 1
|
||||
view {
|
||||
<div data-component="PageHeader" eyebrow="{t:contact.eyebrow}" title="{t:contact.title}" description="{t:contact.description}"></div>
|
||||
<input aria-label="{t:contact.form.name}" placeholder='{t:contact.form.namePlaceholder}' />
|
||||
<div data-success-message="{t:contact.form.successMessage}" title="{t:contact.title} — {count}">{t:contact.title}</div>
|
||||
<span t:aria-label="contact.form.label"></span>
|
||||
}
|
||||
}`;
|
||||
expect(checkWrnSource(source)).toEqual([]);
|
||||
const virtual = virtualTypeScriptModule(source).code;
|
||||
expect(virtual).not.toContain("void (t:");
|
||||
expect(virtual).toContain("void (count)");
|
||||
});
|
||||
|
||||
test("mixed translation bindings retain genuine expression diagnostics", () => {
|
||||
const source = `page Contact {
|
||||
view {
|
||||
<div title="{t:contact.title} — {missingValue}"></div>
|
||||
<div title="{true ? missingBranch : 'fallback'}"></div>
|
||||
<div data-value="{{ t: missingProperty }}"></div>
|
||||
}
|
||||
}`;
|
||||
const diagnostics = checkWrnSource(source);
|
||||
for (const name of ["missingValue", "missingBranch", "missingProperty"]) {
|
||||
expect(
|
||||
diagnostics.some((item) => item.code === "WRN-TYPE-2304" && item.message.includes(name)),
|
||||
).toBe(true);
|
||||
}
|
||||
expect(diagnostics.some((item) => item.message.includes("name 't'"))).toBe(false);
|
||||
});
|
||||
|
||||
test("the translation exemption does not apply to event handlers", () => {
|
||||
const diagnostics = checkWrnSource(
|
||||
`page Contact { view { <button @click="t:contact.title"></button> } }`,
|
||||
);
|
||||
expect(
|
||||
diagnostics.some((item) => item.code === "WRN-TYPE-2304" && item.message.includes("contact")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("form event statement lists have the correct currentTarget type", () => {
|
||||
const source = `page Contact {
|
||||
view {
|
||||
<form @wrn:success="event.currentTarget.reset(); toast.success('Sent')"></form>
|
||||
<input @input="toast.info(event.currentTarget.value)" />
|
||||
<button @click="event.currentTarget.disabled = true; toast.info('Working')"></button>
|
||||
}
|
||||
}`;
|
||||
expect(checkWrnSource(source)).toEqual([]);
|
||||
const invalid = checkWrnSource(
|
||||
source.replace("event.currentTarget.disabled", "event.currentTarget.notARealProperty"),
|
||||
);
|
||||
expect(invalid.some((item) => item.code === "WRN-TYPE-2339")).toBe(true);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.8.61",
|
||||
"version": "0.8.62",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface MegaMenuColumn {
|
||||
actionHref?: string;
|
||||
}
|
||||
|
||||
export interface MegaMenuRailItem extends NavigationLink {}
|
||||
export type MegaMenuRailItem = NavigationLink;
|
||||
|
||||
export interface MegaMenuFeatured {
|
||||
eyebrow?: string;
|
||||
|
||||
Reference in new issue
Block a user