fix(ui): unify heading help, table sizing and navigation contracts
Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
This commit is contained in:
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env node
|
||||
// Compile the shared component contract once per invocation. Each invocation
|
||||
// owns its output, so standalone aliases and concurrent agents cannot erase it.
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const componentSuites = Object.freeze({
|
||||
"data-grid-actions": ["data-grid-actions", "data-grid-sizing"],
|
||||
"dialog-focus": ["dialog-focus"],
|
||||
"explorer-tree": ["explorer-tree"],
|
||||
"icon-button": ["icon-button"],
|
||||
"layout-primitives": ["layout-primitives"],
|
||||
"mail-components": ["mail-components"],
|
||||
"metric-card": ["metric-card"],
|
||||
"page-layout": ["page-layout"],
|
||||
"workspace-layout": ["workspace-layout"],
|
||||
"people-picker": ["people-picker"],
|
||||
"password-field": ["password-generator"],
|
||||
"resource-access": ["resource-access-explanation"],
|
||||
"action-blocker": ["action-blocker-hint"],
|
||||
"documentation-help": ["documentation-help-link"],
|
||||
"selection-list": ["selection-list"],
|
||||
"wysiwyg-editor": ["wysiwyg-editor-utils"],
|
||||
});
|
||||
|
||||
export function selectSuites(names) {
|
||||
const selected = [...new Set(names.filter((name) => name !== "--"))];
|
||||
if (!selected.length || (selected.length === 1 && selected[0] === "all")) return Object.keys(componentSuites);
|
||||
for (const name of selected) {
|
||||
if (!Object.hasOwn(componentSuites, name)) throw new Error(`Unknown component suite: ${name}`);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
function execute(argv, { cwd, signal }) {
|
||||
if (signal?.aborted) return Promise.reject(new Error("Component tests interrupted"));
|
||||
return new Promise((resolveCommand, reject) => {
|
||||
const child = spawn(argv[0], argv.slice(1), { cwd, stdio: "inherit", shell: false });
|
||||
let killTimer;
|
||||
const abort = () => {
|
||||
child.kill("SIGTERM");
|
||||
killTimer = setTimeout(() => child.kill("SIGKILL"), 5000);
|
||||
killTimer.unref();
|
||||
};
|
||||
signal?.addEventListener("abort", abort, { once: true });
|
||||
child.once("error", reject);
|
||||
child.once("close", (code, childSignal) => {
|
||||
signal?.removeEventListener("abort", abort);
|
||||
clearTimeout(killTimer);
|
||||
if (code === 0 && !signal?.aborted) resolveCommand();
|
||||
else reject(new Error(`Component command failed (${childSignal ?? code}): ${argv.slice(1).join(" ")}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function runComponentTests({
|
||||
names = [],
|
||||
webuiRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."),
|
||||
run = execute,
|
||||
compiler,
|
||||
signal,
|
||||
} = {}) {
|
||||
const selected = selectSuites(names);
|
||||
const require = createRequire(join(webuiRoot, "package.json"));
|
||||
const typescript = compiler ?? require.resolve("typescript/bin/tsc");
|
||||
const output = mkdtempSync(join(webuiRoot, ".component-test-build-"));
|
||||
try {
|
||||
writeFileSync(join(output, "package.json"), '{"type":"commonjs"}\n');
|
||||
await run([process.execPath, typescript, "-p", "tsconfig.component-tests.json", "--outDir", output], { cwd: webuiRoot, signal });
|
||||
// The SSR tests intentionally do not load browser CSS.
|
||||
mkdirSync(join(output, "src", "components"), { recursive: true });
|
||||
writeFileSync(join(output, "src", "components", "ProductAvailabilityState.css"), "module.exports = {};\n");
|
||||
for (const name of selected) {
|
||||
for (const test of componentSuites[name]) {
|
||||
await run([process.execPath, join(output, "tests", `${test}.test.js`)], { cwd: webuiRoot, signal });
|
||||
}
|
||||
if (name === "dialog-focus") {
|
||||
await run([process.execPath, join(webuiRoot, "scripts", "test-dialog-focus-structure.mjs")], { cwd: webuiRoot, signal });
|
||||
}
|
||||
}
|
||||
return { suites: selected, compiled: 1 };
|
||||
} finally {
|
||||
// Only remove this invocation's freshly-created directory, never the
|
||||
// legacy shared build or another invocation's artifacts.
|
||||
rmSync(output, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const controller = new AbortController();
|
||||
let interrupted;
|
||||
const stop = (signal) => { interrupted = signal; controller.abort(); };
|
||||
const onInterrupt = () => stop("SIGINT");
|
||||
const onTerminate = () => stop("SIGTERM");
|
||||
process.on("SIGINT", onInterrupt);
|
||||
process.on("SIGTERM", onTerminate);
|
||||
try {
|
||||
const result = await runComponentTests({ names: process.argv.slice(2), signal: controller.signal });
|
||||
process.stdout.write(`Component suites passed: ${result.suites.length}; compilations: ${result.compiled}.\n`);
|
||||
} catch (error) {
|
||||
process.stderr.write(`${error.message}\n`);
|
||||
process.exitCode = interrupted === "SIGINT" ? 130 : interrupted === "SIGTERM" ? 143 : 1;
|
||||
} finally {
|
||||
process.removeListener("SIGINT", onInterrupt);
|
||||
process.removeListener("SIGTERM", onTerminate);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user