fix(test): route explicit helper targets narrowly

This commit is contained in:
Vincent Koc
2026-05-30 07:34:04 +02:00
parent d65b3a68aa
commit b6f3fe7938
3 changed files with 118 additions and 2 deletions

View File

@@ -785,6 +785,14 @@ function isTestFileTarget(arg) {
return /\.(?:test|spec)\.[cm]?[jt]sx?$/u.test(arg);
}
function isTestSupportFileTarget(arg) {
if (/(?:^|\/)(?:test-helpers|test-support)(?:\/|$)/u.test(arg)) {
return true;
}
const basename = path.posix.basename(arg).replace(/\.[cm]?[jt]sx?$/u, "");
return /(?:^|[._-])test-(?:helpers|support)(?:[._-]|$)/u.test(basename);
}
function isLikelyFileTarget(arg) {
return /(?:^|\/)[^/]+\.[A-Za-z0-9]+$/u.test(arg);
}
@@ -863,6 +871,28 @@ function includePatternMatchesAnyFile(pattern, files) {
return files.some((file) => file === pattern || path.matchesGlob(file, pattern));
}
function resolveExplicitTestSupportTargets(targetArg, cwd) {
const relative = toRepoRelativeTarget(targetArg, cwd);
const kind = classifyTarget(targetArg, cwd);
if (shouldUseWholeConfigTarget(kind, targetArg, cwd)) {
return null;
}
if (!isExistingFileTarget(targetArg, cwd) || !isTestSupportFileTarget(relative)) {
return null;
}
const mappedTargets = resolveToolingTestTargets(relative) ?? SOURCE_TEST_TARGETS.get(relative);
return [...new Set(mappedTargets ?? resolveAffectedTestsFromImportGraph(relative, cwd))].toSorted(
(left, right) => left.localeCompare(right),
);
}
function expandExplicitTestSupportTargets(targetArgs, cwd) {
return targetArgs.flatMap((targetArg) => {
const targets = resolveExplicitTestSupportTargets(targetArg, cwd);
return targets && targets.length > 0 ? targets : [targetArg];
});
}
export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
const { targetArgs } = parseTestProjectsArgs(args, cwd);
if (targetArgs.length === 0) {
@@ -907,6 +937,17 @@ export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
continue;
}
const explicitSupportTargets = resolveExplicitTestSupportTargets(targetArg, cwd);
if (explicitSupportTargets) {
if (explicitSupportTargets.length === 0) {
unmatched.push({
target: targetArg,
reason: "target-matched-no-test-files",
});
}
continue;
}
const includePattern = toScopedIncludePattern(targetArg, cwd);
if (!includePatternMatchesAnyFile(includePattern, getCandidateFiles())) {
unmatched.push({
@@ -1778,7 +1819,8 @@ export function buildVitestRunPlans(
const { forwardedArgs, targetArgs, watchMode } = parseTestProjectsArgs(args, cwd);
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, cwd, listChangedPaths, options) : null;
const activeTargetArgs = changedTargetArgs ?? targetArgs;
const requestedTargetArgs = changedTargetArgs ?? targetArgs;
const activeTargetArgs = expandExplicitTestSupportTargets(requestedTargetArgs, cwd);
const activeForwardedArgs =
changedTargetArgs !== null ? stripChangedArgs(forwardedArgs) : forwardedArgs;
if (changedTargetArgs !== null && activeTargetArgs.length === 0) {
@@ -1824,7 +1866,7 @@ export function buildVitestRunPlans(
);
}
const nonTargetArgs = activeForwardedArgs.filter((arg) => !activeTargetArgs.includes(arg));
const nonTargetArgs = activeForwardedArgs.filter((arg) => !requestedTargetArgs.includes(arg));
const orderedKinds = [
"unitFast",
"unitFastFakeTimers",

View File

@@ -1077,6 +1077,43 @@ describe("test-projects args", () => {
}
});
it("routes explicit test-support helper files to affected tests", () => {
expect(
findUnmatchedExplicitTestTargets(["src/commands/onboard-non-interactive.test-helpers.ts"]),
).toEqual([]);
expect(buildVitestRunPlans(["src/commands/onboard-non-interactive.test-helpers.ts"])).toEqual([
{
config: "test/vitest/vitest.commands.config.ts",
forwardedArgs: [],
includePatterns: ["src/commands/onboard-non-interactive.gateway.test.ts"],
watchMode: false,
},
]);
});
it("rejects explicit test-support helper files with no importing tests", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-test-targets-"));
try {
fs.mkdirSync(path.join(tempDir, "src", "lonely"), { recursive: true });
fs.writeFileSync(
path.join(tempDir, "src", "lonely", "runtime.test-helpers.ts"),
"export {};\n",
);
expect(
findUnmatchedExplicitTestTargets(["src/lonely/runtime.test-helpers.ts"], tempDir),
).toEqual([
{
target: "src/lonely/runtime.test-helpers.ts",
reason: "target-matched-no-test-files",
},
]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("accepts explicit Vitest config targets routed as whole config runs", () => {
expect(
findUnmatchedExplicitTestTargets(["test/vitest/vitest.contracts-channel-surface.config.ts"]),

View File

@@ -425,6 +425,43 @@ describe("scripts/test-projects changed-target routing", () => {
).toEqual([]);
});
it("routes explicit test-support helper files to affected tests", () => {
expect(
findUnmatchedExplicitTestTargets(["src/commands/onboard-non-interactive.test-helpers.ts"]),
).toEqual([]);
expect(buildVitestRunPlans(["src/commands/onboard-non-interactive.test-helpers.ts"])).toEqual([
{
config: "test/vitest/vitest.commands.config.ts",
forwardedArgs: [],
includePatterns: ["src/commands/onboard-non-interactive.gateway.test.ts"],
watchMode: false,
},
]);
});
it("rejects explicit test-support helper files with no importing tests", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-test-targets-"));
try {
fs.mkdirSync(path.join(tempDir, "src", "lonely"), { recursive: true });
fs.writeFileSync(
path.join(tempDir, "src", "lonely", "runtime.test-helpers.ts"),
"export {};\n",
);
expect(
findUnmatchedExplicitTestTargets(["src/lonely/runtime.test-helpers.ts"], tempDir),
).toEqual([
{
target: "src/lonely/runtime.test-helpers.ts",
reason: "target-matched-no-test-files",
},
]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("routes contract roots to separate contract shards", () => {
const plans = buildVitestRunPlans([
"src/channels/plugins/contracts/channel-catalog.contract.test.ts",