complete framework remediation validation

This commit is contained in:
2026-08-09 14:39:22 +05:30
parent 905cbce0c1
commit 51286d0fa3
21 changed files with 898 additions and 1474 deletions
+20 -10
View File
@@ -174,7 +174,7 @@ export function browserModuleRequired(ast: PageAst): boolean {
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
}
function functionEntry(
function _functionEntry(
ast: PageAst,
fn: RuntimeFunctionDecl,
availableFunctions: string[],
@@ -242,7 +242,10 @@ function functionEntry(
.join(" ");
const peerAliases = !stateNames.length
? functionAliases
.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
.map(
(name) =>
`const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`,
)
.join("\n")
: `const __wrnexusFlush = () => { ${syncStateToContext} };
const __wrnexusRestore = () => { ${syncStateFromContext} };
@@ -271,9 +274,15 @@ function functionEntry(
const commitBinding = stateNames.length
? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") && !declaredLocals.has("setTimeout") && !functionAliases.includes("setTimeout") ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
${
!parameterNames.has("setTimeout") &&
!declaredLocals.has("setTimeout") &&
!functionAliases.includes("setTimeout")
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}`
}, delay);`
: ""
}`
: "";
const body = eraseFunctionTypes(fn.body);
const runtimeBindings = [
@@ -312,10 +321,7 @@ export function generateBrowserModule(ast: PageAst): string {
const sharedProps = ast.props
.map((entry) => entry.name)
.filter(
(name) =>
safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!sharedState.includes(name),
(name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name),
);
const callableAliases = functionNames.filter(
(name) =>
@@ -362,9 +368,13 @@ function __wrnexusCreateClientFunctions(context) {
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout ? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
${
!hasAuthoredSetTimeout
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);` : ""}
}, delay);`
: ""
}
const implementations = {
${implementations}
};
+1 -2
View File
@@ -752,8 +752,7 @@ function renderNestedComponentInvocation(
if (
!attr.value.includes("{") ||
(!exprRefsState(attr.value, ctx.stateNames) &&
!exprRefsState(attr.value, ctx.propNames))
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))
) {
return rendered;
}
+1 -3
View File
@@ -328,9 +328,7 @@ test("stateful component keeps both prop and state text reactive", async () => {
const out = render({ start: "10", label: "Score" });
expect(out).toContain('data-scope="start: 10, label: "Score", count: 10"');
expect(out).toContain('data-on-click="count++"');
expect(out).toContain(
'<span data-text="label">Score</span>: <span data-text="count">10</span>',
);
expect(out).toContain('<span data-text="label">Score</span>: <span data-text="count">10</span>');
expect(out).toContain('data-scope="');
+4 -1
View File
@@ -89,7 +89,10 @@ const CONTROLLER_LOADER = String.raw`
/** The reactive runtime served at `/__wrnexus/reactive.js` (plain browser JS). */
export function getReactiveRuntime(development = false): string {
let source = stripControllerSections(runtimeForMode(development));
source = source.replace(" function hydrateScopes(root) {", `${CONTROLLER_LOADER}\n function hydrateScopes(root) {`);
source = source.replace(
" function hydrateScopes(root) {",
`${CONTROLLER_LOADER}\n function hydrateScopes(root) {`,
);
source = source.replace(
" hydrateNavbarControllers(host);\n hydratePreferenceControllers(host);\n hydrateSelectControllers(host);\n hydratePinInputControllers(host);",
" loadComponentControllers(host);",
+44 -18
View File
@@ -60,7 +60,10 @@ test("split runtime hydrates a controller only from the controller asset", () =>
});
test("a page without controller markers does not request the controller asset", () => {
const win = mount(`<main data-scope="count: 1"><span>{count}</span></main>`, getReactiveRuntime(true));
const win = mount(
`<main data-scope="count: 1"><span>{count}</span></main>`,
getReactiveRuntime(true),
);
expect(win.document.querySelector('script[src$="controllers.js"]')).toBeNull();
});
@@ -703,20 +706,27 @@ test("a camelCase output reaches a parent binding despite attribute lowercasing"
test("development runtime warns once for an output with no parent binding", () => {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML =
`<div data-scope="" data-wrn-events="complete"><button data-on-click="output.complete({})">go</button></div>`;
win.document.body.innerHTML = `<div data-scope="" data-wrn-events="complete"><button data-on-click="output.complete({})">go</button></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (win as unknown as { NodeFilter: unknown }).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (win as unknown as { MutationObserver: unknown }).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (win as unknown as { CustomEvent: unknown }).CustomEvent;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args);
try {
(0, eval)(getReactiveRuntime(true));
(win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(win.document);
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
(win.document.querySelector("button") as unknown as HTMLElement).click();
(win.document.querySelector("button") as unknown as HTMLElement).click();
} finally {
@@ -730,15 +740,21 @@ test("development runtime warns when a component binding names a missing functio
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML =
`<div data-scope="">` +
`<div data-scope="" data-wrn-hydration="Child:x">` +
`<div data-wrn-events="save" data-wrn-out-save="missingSave(payload)"></div>` +
`</div></div>`;
`<div data-scope="" data-wrn-hydration="Child:x">` +
`<div data-wrn-events="save" data-wrn-out-save="missingSave(payload)"></div>` +
`</div></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (win as unknown as { NodeFilter: unknown }).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (win as unknown as { MutationObserver: unknown }).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (win as unknown as { CustomEvent: unknown }).CustomEvent;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
const originalError = console.error;
@@ -746,7 +762,9 @@ test("development runtime warns when a component binding names a missing functio
console.error = () => {};
try {
(0, eval)(getReactiveRuntime(true));
(win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(win.document);
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
const target = win.document.querySelector("[data-wrn-events]") as unknown as {
__wrnexusOutputHandlers?: Record<string, Set<(payload: unknown) => unknown>>;
};
@@ -770,15 +788,23 @@ test("development runtime warns for referenced theme tokens absent from rendered
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (win as unknown as { NodeFilter: unknown }).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (win as unknown as { MutationObserver: unknown }).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (win as unknown as { CustomEvent: unknown }).CustomEvent;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args);
try {
(0, eval)(getReactiveRuntime(true));
(win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(win.document);
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
} finally {
console.warn = originalWarn;
}
@@ -31,7 +31,8 @@ test("a component nested in another component's slot keeps its boundary", async
return { render: (props: Record<string, string>) => `<b class="badge">${props.label}</b>` };
}
return {
default: () => '<div data-component="Card"><div data-component="Badge" label="x"></div></div>',
default: () =>
'<div data-component="Card"><div data-component="Badge" label="x"></div></div>',
};
},
getMiddleware: async () => [],
+1 -3
View File
@@ -1,7 +1,5 @@
{
"sourceDocuments": [
"Screenshot-directed WRNexus UI catalog reset"
],
"sourceDocuments": ["Screenshot-directed WRNexus UI catalog reset"],
"components": [
{
"name": "Accordion",
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -121,7 +121,7 @@ component DataTable {
state query = ""
state sortKey = ""
state sortDirection = "asc"
state page = 1
state paginationPage = 1
state perPage = pageSize
state selectedKeys = []
state remoteRows = []
@@ -273,7 +273,7 @@ component DataTable {
// page number past the end, and a table showing nothing with rows
// available is worse than one that quietly lands on the last page.
shared function currentPage() {
return Math.min(Math.max(1, Number(page) || 1), pageCount())
return Math.min(Math.max(1, Number(paginationPage) || 1), pageCount())
}
shared function visibleRows() {
@@ -576,7 +576,7 @@ component DataTable {
sortKey = key
sortDirection = "asc"
}
page = 1
paginationPage = 1
output.sort({ key: sortKey, direction: sortDirection })
reload()
output.change(viewState())
@@ -584,7 +584,7 @@ component DataTable {
client function updateQuery(sourceEvent) {
query = sourceEvent.target.value
page = 1
paginationPage = 1
output.search({ query: query })
reload()
output.change(viewState())
@@ -592,7 +592,7 @@ component DataTable {
client function goToPage(next) {
var target = Math.min(Math.max(1, next), pageCount())
page = target
paginationPage = target
output.pageChange({ page: target, pageSize: Number(perPage) || 10 })
reload()
output.change(viewState())
@@ -602,7 +602,7 @@ component DataTable {
// takes the Dropdown select detail directly.
client function pickPageSize(detail) {
perPage = Number(detail && detail.value) || 10
page = 1
paginationPage = 1
output.pageChange({ page: 1, pageSize: perPage })
reload()
output.change(viewState())
@@ -613,7 +613,7 @@ component DataTable {
// clicks.
client function clearSearch() {
query = ""
page = 1
paginationPage = 1
output.search({ query: "" })
reload()
}
+21 -5
View File
@@ -3227,7 +3227,12 @@ test("list selects items without hrefs as well as navigation items", async () =>
test("chart renders accessible SVG bars and emits point and legend outputs", async () => {
const source = readFileSync(uiComponentPath("Chart"), "utf8");
const dom = mountHtml(
await renderComponent(source, { items: [{ label: "Alpha", value: 12 }, { label: "Beta", value: 6 }] }),
await renderComponent(source, {
items: [
{ label: "Alpha", value: 12 },
{ label: "Beta", value: 6 },
],
}),
);
const root = dom.querySelector('[data-ui-component="Chart"]') as HTMLElement;
const events: string[] = [];
@@ -3251,7 +3256,8 @@ test("tree view expands branches, selects nodes, and emits public outputs", asyn
);
const root = dom.querySelector('[data-ui-component="TreeView"]') as HTMLElement;
const events: string[] = [];
for (const name of ["toggle", "expand", "select"]) root.addEventListener(name, () => events.push(name));
for (const name of ["toggle", "expand", "select"])
root.addEventListener(name, () => events.push(name));
(root.querySelector(".wire-tree-view__toggle") as HTMLButtonElement).click();
expect(root.querySelector(".wire-tree-view__group")?.getAttribute("data-show")).toBe("true");
(root.querySelector('[aria-level="2"] .wire-tree-view__node') as HTMLButtonElement).click();
@@ -3259,7 +3265,12 @@ test("tree view expands branches, selects nodes, and emits public outputs", asyn
});
test("confetti completes deferred bursts and clipboard reports successful copies", async () => {
const confetti = mountHtml(await renderComponent(readFileSync(uiComponentPath("Confetti"), "utf8"), { duration: 5, count: 4 }));
const confetti = mountHtml(
await renderComponent(readFileSync(uiComponentPath("Confetti"), "utf8"), {
duration: 5,
count: 4,
}),
);
const confettiRoot = confetti.querySelector('[data-ui-component="Confetti"]') as HTMLElement;
const confettiEvents: string[] = [];
confettiRoot.addEventListener("start", () => confettiEvents.push("start"));
@@ -3268,14 +3279,19 @@ test("confetti completes deferred bursts and clipboard reports successful copies
await new Promise((resolve) => setTimeout(resolve, 15));
expect(confettiEvents).toEqual(["start", "complete"]);
const clipboard = mountHtml(await renderComponent(readFileSync(uiComponentPath("Clipboard"), "utf8"), { value: "npm add wrnexus" }));
const clipboard = mountHtml(
await renderComponent(readFileSync(uiComponentPath("Clipboard"), "utf8"), {
value: "npm add wrnexus",
}),
);
Object.defineProperty((clipboard.window as { navigator: object }).navigator, "clipboard", {
configurable: true,
value: { writeText: () => Promise.resolve() },
});
const clipboardRoot = clipboard.querySelector('[data-ui-component="Clipboard"]') as HTMLElement;
const clipboardEvents: string[] = [];
for (const name of ["copy", "success"]) clipboardRoot.addEventListener(name, () => clipboardEvents.push(name));
for (const name of ["copy", "success"])
clipboardRoot.addEventListener(name, () => clipboardEvents.push(name));
(clipboardRoot.querySelector("button") as HTMLButtonElement).click();
await new Promise((resolve) => setTimeout(resolve, 0));
expect(clipboardEvents).toEqual(["copy", "success"]);