Files
openclaw/test/vitest-ui-package-config.test.ts
Peter Steinberger 4fac990321 fix(test): run the shared Control UI lane on the cross-file cleanup runner (#126071)
* fix(test): run the shared Control UI lane on the cross-file cleanup runner

ui/vitest.config.ts drives CI's checks-ui job (pnpm --dir ui test). Its
unit project sets isolate:false but never wired
runner: nonIsolatedRunnerPath, so the per-file cleanup in
test/non-isolated-runner.ts — module-graph reset, repo-owned custom
element dropping, DOM body reset, timer and spy restoration — never ran
in the lane CI actually uses. Only the repo-root lane behind
scripts/run-vitest.mjs loaded it.

Files sharing a worker therefore kept the previous file's evaluated
modules, so whichever file imported a component first pinned it to the
real dependency and a later file's vi.mock factory never reached
production code, surfacing as "expected 0 to be 1" in whichever sibling
the size sequencer happened to pack alongside it. This is the class
PR #123512 diagnosed and fixed at the runner; the fix never reached this
lane, so the repo kept absorbing it one uiIsolatedTestFiles entry at a
time.

browser stays exempt (the runner imports node:fs and server modules that
cannot load in browser mode) and unit-node stays exempt (it carries the
Playwright-driven layout tests whose browser lives in module scope, which
per-file module resets churn). The config test asserted runner was
undefined for every project, pinning the broken wiring; it now asserts
the invariant and fails on the pre-fix config.

* fix(agents): stop passing an ignored resolver to instance-bound announce dispatch

check-prod-types is red on main: #126062 threaded resolveGatewayContext
into the announce dispatch call, but that call now goes through
dispatchGatewayLifecycleMethod, whose options type does not carry the
field.

The type checker is right that it does not belong there. That dispatcher
hands work to runtime.dispatchAgent, which resolves context from the
Gateway instance it is bound to and forwards a fixed option allowlist, so
a caller-supplied resolver was already being ignored. Dropping it is
behavior-preserving.

The delivery test asserted the resolver was forwarded, but production now
binds to the instance dispatcher while the test injects a mock, so that
assertion only proved the mock. It now asserts the resolver is
deliberately not forwarded.

Left for the owner of #126062: sendSubagentAnnounceDirectly and its
callers still accept and thread resolveGatewayContext, which is now
vestigial on this path. Deleting that chain or teaching the instance
runtime to honor the resolver is a design call on a just-landed change.
2026-08-18 17:42:00 -07:00

109 lines
3.9 KiB
TypeScript

// Vitest UI package config tests validate UI package test project settings.
import path from "node:path";
import { describe, expect, it } from "vitest";
import uiConfig from "../ui/vitest.config.ts";
import uiNodeConfig from "../ui/vitest.node.config.ts";
import { normalizeConfigPath } from "./helpers/vitest-config-paths.js";
type ExpectedTestConfig = {
browser?: { enabled?: boolean };
isolate?: boolean;
name?: string;
pool?: string;
projects?: unknown[];
runner?: string;
};
function requireTestConfig(config: unknown): ExpectedTestConfig {
if (!config || typeof config !== "object" || !("test" in config) || !config.test) {
throw new Error("expected ui package vitest test config");
}
return config.test as ExpectedTestConfig;
}
function requireAlias(config: unknown, specifier: string): { find: string; replacement: string } {
const aliases = (config as { resolve?: { alias?: unknown } }).resolve?.alias;
if (!Array.isArray(aliases)) {
throw new Error("expected ui package vitest aliases");
}
const alias = aliases.find((candidate): candidate is { find: string; replacement: string } =>
Boolean(
candidate &&
typeof candidate === "object" &&
"find" in candidate &&
candidate.find === specifier &&
"replacement" in candidate &&
typeof candidate.replacement === "string",
),
);
if (!alias) {
throw new Error(`missing ui package vitest alias ${specifier}`);
}
return alias;
}
describe("ui package vitest config", () => {
it("keeps the standalone ui package on thread workers without broad isolation", () => {
const testConfig = requireTestConfig(uiConfig);
expect(testConfig.pool).toBe("threads");
expect(testConfig.isolate).toBe(false);
expect(testConfig.projects).toHaveLength(4);
for (const project of testConfig.projects ?? []) {
const projectTestConfig = requireTestConfig(project);
expect(projectTestConfig.pool).toBe("threads");
expect(projectTestConfig.isolate).toBe(projectTestConfig.name === "unit-mock-registry");
}
});
// The invariant, not a snapshot: `unit` shares one module graph and jsdom
// window across files, so without the cleanup runner the first file to
// evaluate a component owns it for the whole worker and a later file's
// vi.mock never reaches production. Two projects are exempt for stated
// reasons: `browser` runs in browser mode, where the runner's node:fs and
// server-module imports cannot load, and `unit-node` carries the
// Playwright-driven layout tests whose browser lives in module scope, which
// per-file module resets churn.
it("runs the shared jsdom ui project on the cross-file cleanup runner", () => {
const projects = requireTestConfig(uiConfig).projects ?? [];
const wiring = projects.map((project) => {
const projectTestConfig = requireTestConfig(project);
return {
name: projectTestConfig.name,
runner: projectTestConfig.runner
? normalizeConfigPath(projectTestConfig.runner)
: undefined,
};
});
expect(wiring).toEqual([
{ name: "unit", runner: "test/non-isolated-runner.ts" },
{ name: "unit-mock-registry", runner: undefined },
{ name: "unit-node", runner: undefined },
{ name: "browser", runner: undefined },
]);
});
it("keeps the standalone ui node config on thread workers without isolation", () => {
const testConfig = requireTestConfig(uiNodeConfig);
expect(testConfig.pool).toBe("threads");
expect(testConfig.isolate).toBe(false);
expect(testConfig.runner).toBeUndefined();
});
it("aliases the scope-upgrade workspace subpath for clean browser test checkouts", () => {
expect(requireAlias(uiConfig, "@openclaw/gateway-client/scope-upgrade")).toEqual({
find: "@openclaw/gateway-client/scope-upgrade",
replacement: path.join(
process.cwd(),
"packages",
"gateway-client",
"src",
"scope-upgrade.ts",
),
});
});
});