diff --git a/scripts/run-vitest.mjs b/scripts/run-vitest.mjs index 46eb11805671..4346d62acf8b 100644 --- a/scripts/run-vitest.mjs +++ b/scripts/run-vitest.mjs @@ -1,6 +1,7 @@ import { spawn } from "node:child_process"; import { createRequire } from "node:module"; import path from "node:path"; +import { isUnitUiTestTarget } from "../test/vitest/vitest.ui-paths.mjs"; import { resolveLocalVitestEnv } from "./lib/vitest-local-scheduling.mjs"; import { spawnPnpmRunner } from "./pnpm-runner.mjs"; import { @@ -13,6 +14,7 @@ const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]); const ANSI_CSI_PREFIX = `${String.fromCharCode(27)}[`; const ANSI_CSI_SUFFIX_RE = /^[0-?]*[ -/]*[@-~]/u; const SUPPRESSED_VITEST_STDERR_PATTERNS = ["[PLUGIN_TIMINGS]"]; +const UNIT_UI_VITEST_CONFIG = "test/vitest/vitest.unit-ui.config.ts"; const require = createRequire(import.meta.url); function isTruthyEnvValue(value) { @@ -92,6 +94,31 @@ export function resolveDirectNodeVitestArgs(pnpmArgs) { return pnpmArgs[0] === "exec" && pnpmArgs[1] === "node" ? pnpmArgs.slice(2) : null; } +function hasExplicitVitestConfigArg(argv) { + return argv.some((arg) => arg === "--config" || arg === "-c" || arg.startsWith("--config=")); +} + +function toRepoRelativeArg(arg, cwd) { + const normalized = path.isAbsolute(arg) ? path.relative(cwd, arg) : arg; + return normalized.replaceAll(path.sep, "/").replace(/^\.\//u, ""); +} + +export function resolveImplicitVitestArgs(argv, cwd = process.cwd()) { + if (hasExplicitVitestConfigArg(argv)) { + return argv; + } + const testTargets = argv + .filter((arg) => !arg.startsWith("-") && arg.endsWith(".test.ts")) + .map((arg) => toRepoRelativeArg(arg, cwd)); + if (testTargets.length === 0 || !testTargets.every(isUnitUiTestTarget)) { + return argv; + } + if (argv[0] === "run") { + return ["run", "--config", UNIT_UI_VITEST_CONFIG, ...argv.slice(1)]; + } + return ["--config", UNIT_UI_VITEST_CONFIG, ...argv]; +} + function spawnVitestProcess({ pnpmArgs, spawnParams }) { const directNodeArgs = resolveDirectNodeVitestArgs(pnpmArgs); if (directNodeArgs) { @@ -271,11 +298,18 @@ function main(argv = process.argv.slice(2), env = process.env) { process.exit(1); } + const vitestArgs = resolveImplicitVitestArgs(argv); const { child, teardown } = spawnWatchedVitestProcess({ - pnpmArgs: ["exec", "node", ...resolveVitestNodeArgs(env), resolveVitestCliEntry(), ...argv], + pnpmArgs: [ + "exec", + "node", + ...resolveVitestNodeArgs(env), + resolveVitestCliEntry(), + ...vitestArgs, + ], spawnParams: resolveVitestSpawnParams(env), env, - label: argv.join(" "), + label: vitestArgs.join(" "), }); child.on("exit", (code, signal) => { diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index e8c6206b5de3..b140146369eb 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -34,6 +34,7 @@ import { resolvePluginSdkLightIncludePattern, } from "../test/vitest/vitest.plugin-sdk-paths.mjs"; import { fullSuiteVitestShards } from "../test/vitest/vitest.test-shards.mjs"; +import { isUnitUiTestTarget } from "../test/vitest/vitest.ui-paths.mjs"; import { resolveUnitFastTestIncludePattern } from "../test/vitest/vitest.unit-fast-paths.mjs"; import { isBoundaryTestFile, @@ -1126,23 +1127,6 @@ function isVitestConfigTargetForKind(kind, targetArg, cwd) { return resolveVitestConfigTargetKind(toRepoRelativeTarget(targetArg, cwd)) === kind; } -function isUnitUiTestTarget(relative) { - if (!relative.endsWith(".test.ts")) { - return false; - } - return ( - relative === "ui/src/ui/app-chat.test.ts" || - relative.startsWith("ui/src/ui/chat/") || - relative === "ui/src/ui/views/agents-utils.test.ts" || - relative === "ui/src/ui/views/channels.test.ts" || - relative === "ui/src/ui/views/chat.test.ts" || - relative === "ui/src/ui/views/dreaming.test.ts" || - relative === "ui/src/ui/views/usage-render-details.test.ts" || - relative === "ui/src/ui/controllers/agents.test.ts" || - relative === "ui/src/ui/controllers/chat.test.ts" - ); -} - function isControlUiE2eTarget(relative) { return ( relative === "ui/src/test-helpers/control-ui-e2e.ts" || diff --git a/test/scripts/run-vitest.test.ts b/test/scripts/run-vitest.test.ts index faf3e0b08fed..d53c363e862a 100644 --- a/test/scripts/run-vitest.test.ts +++ b/test/scripts/run-vitest.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { installVitestNoOutputWatchdog, resolveDirectNodeVitestArgs, + resolveImplicitVitestArgs, resolveVitestNodeArgs, resolveVitestNoOutputTimeoutMs, resolveVitestSpawnParams, @@ -26,6 +27,40 @@ describe("scripts/run-vitest", () => { expect(resolveDirectNodeVitestArgs(["exec", "vitest", "run"])).toBeNull(); }); + it("routes explicit unit ui tests through the narrow unit ui config", () => { + expect( + resolveImplicitVitestArgs([ + "ui/src/ui/controllers/chat.test.ts", + "-t", + "keeps optimistic user attachment previews", + ]), + ).toEqual([ + "--config", + "test/vitest/vitest.unit-ui.config.ts", + "ui/src/ui/controllers/chat.test.ts", + "-t", + "keeps optimistic user attachment previews", + ]); + }); + + it("does not override explicit vitest configs", () => { + const argv = [ + "--config", + "test/vitest/vitest.ui.config.ts", + "ui/src/ui/controllers/chat.test.ts", + ]; + expect(resolveImplicitVitestArgs(argv)).toBe(argv); + }); + + it("keeps the run subcommand first when routing unit ui tests", () => { + expect(resolveImplicitVitestArgs(["run", "ui/src/ui/controllers/chat.test.ts"])).toEqual([ + "run", + "--config", + "test/vitest/vitest.unit-ui.config.ts", + "ui/src/ui/controllers/chat.test.ts", + ]); + }); + it("allows opting back into Maglev explicitly", () => { expect( resolveVitestNodeArgs({ diff --git a/test/vitest/vitest.ui-paths.mjs b/test/vitest/vitest.ui-paths.mjs new file mode 100644 index 000000000000..7c2576c107c5 --- /dev/null +++ b/test/vitest/vitest.ui-paths.mjs @@ -0,0 +1,28 @@ +export const unitUiIncludePatterns = [ + "ui/src/ui/app-chat.test.ts", + "ui/src/ui/chat/**/*.test.ts", + "ui/src/ui/views/agents-utils.test.ts", + "ui/src/ui/views/channels.test.ts", + "ui/src/ui/views/chat.test.ts", + "ui/src/ui/views/dreaming.test.ts", + "ui/src/ui/views/usage-render-details.test.ts", + "ui/src/ui/controllers/agents.test.ts", + "ui/src/ui/controllers/chat.test.ts", +]; + +export function isUnitUiTestTarget(relative) { + if (!relative.endsWith(".test.ts")) { + return false; + } + return ( + relative === "ui/src/ui/app-chat.test.ts" || + relative.startsWith("ui/src/ui/chat/") || + relative === "ui/src/ui/views/agents-utils.test.ts" || + relative === "ui/src/ui/views/channels.test.ts" || + relative === "ui/src/ui/views/chat.test.ts" || + relative === "ui/src/ui/views/dreaming.test.ts" || + relative === "ui/src/ui/views/usage-render-details.test.ts" || + relative === "ui/src/ui/controllers/agents.test.ts" || + relative === "ui/src/ui/controllers/chat.test.ts" + ); +} diff --git a/test/vitest/vitest.ui.config.ts b/test/vitest/vitest.ui.config.ts index a5d0fdeb2740..e10dcb031fdc 100644 --- a/test/vitest/vitest.ui.config.ts +++ b/test/vitest/vitest.ui.config.ts @@ -1,17 +1,6 @@ import { createScopedVitestConfig } from "./vitest.scoped-config.ts"; import { jsdomOptimizedDeps } from "./vitest.shared.config.ts"; - -export const unitUiIncludePatterns = [ - "ui/src/ui/app-chat.test.ts", - "ui/src/ui/chat/**/*.test.ts", - "ui/src/ui/views/agents-utils.test.ts", - "ui/src/ui/views/channels.test.ts", - "ui/src/ui/views/chat.test.ts", - "ui/src/ui/views/dreaming.test.ts", - "ui/src/ui/views/usage-render-details.test.ts", - "ui/src/ui/controllers/agents.test.ts", - "ui/src/ui/controllers/chat.test.ts", -]; +import { unitUiIncludePatterns } from "./vitest.ui-paths.mjs"; export function createUiVitestConfig( env?: Record, diff --git a/test/vitest/vitest.unit-ui.config.ts b/test/vitest/vitest.unit-ui.config.ts index a06998e1fbff..61af2c16c018 100644 --- a/test/vitest/vitest.unit-ui.config.ts +++ b/test/vitest/vitest.unit-ui.config.ts @@ -1,4 +1,5 @@ -import { createUiVitestConfig, unitUiIncludePatterns } from "./vitest.ui.config.ts"; +import { unitUiIncludePatterns } from "./vitest.ui-paths.mjs"; +import { createUiVitestConfig } from "./vitest.ui.config.ts"; export default createUiVitestConfig(process.env, { includePatterns: unitUiIncludePatterns,