Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion e2e/filter/changed.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -208,6 +208,40 @@ describe('changed test filtering', () => {
`);
});

it('should run all tests when a force rerun trigger changes', async () => {
const { fixturesTargetPath } = await prepareChangedFixture(
'changed-force-rerun',
);

await initGitFixture(fixturesTargetPath);

await writeFile(
join(fixturesTargetPath, 'package.json'),
`${JSON.stringify({ name: 'changed-force-rerun', version: '1.0.1' })}\n`,
);

const { cli, expectExecSuccess } = await runRstestCli({
command: 'rstest',
args: ['run', '--changed'],
options: {
nodeOptions: {
cwd: fixturesTargetPath,
},
},
});

await expectExecSuccess();

const logs = collectRunTestFileLogs(cli.stdout);

expect(logs).toMatchInlineSnapshot(`
[
" ✓ index.test.ts (1)",
" ✓ other.test.ts (1)",
]
`);
});

it('should pass when changed finds no files', async () => {
const { fixturesTargetPath } = await prepareChangedFixture('changed-empty');

Expand Down
1 change: 1 addition & 0 deletions packages/adapter-rsbuild/src/toRstestConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export function toRstestConfig({
const rstestConfig = {
root: finalBuildConfig.root,
name: environmentName,
forceRerunTriggers: configPath ? [normalize(configPath)] : undefined,
plugins: [
...(finalBuildConfig.plugins || []),
// remove some plugins that are not needed or not compatible in test environment
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-rsbuild/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default defineConfig({
cacheDigest: ['file-digest'],
buildDependencies: [join(__dirname, 'cache-extra.ts'), testConfigPath],
});
expect(config.forceRerunTriggers).toEqual([testConfigPath]);
expect(config.testEnvironment).toBe('happy-dom');
});

Expand Down
3 changes: 3 additions & 0 deletions packages/adapter-rsbuild/tests/toRstestConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ describe('toRstestConfig', () => {
normalize('/repo/configs/rsbuild.config.ts'),
],
});
expect(config.forceRerunTriggers).toEqual([
normalize('/repo/configs/rsbuild.config.ts'),
]);
});

it('should resolve relative build dependencies from root when configPath is not provided', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-rslib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirname, isAbsolute, resolve } from 'node:path';
import { dirname, isAbsolute, normalize, resolve } from 'node:path';
import { loadConfig, type RslibConfig, mergeRslibConfig } from '@rslib/core';
import type { ExtendConfig, ExtendConfigFn } from '@rstest/core';

Expand Down Expand Up @@ -175,6 +175,7 @@ export function withRslibConfig(
// Copy over compatible configurations
root: finalLibConfig.root,
name: libId,
forceRerunTriggers: [normalize(filePath)],
plugins: finalLibConfig.plugins,
source: {
assetsInclude,
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-rslib/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default defineConfig({
cacheDigest: ['file-digest'],
buildDependencies: [join(__dirname, 'cache-extra.ts'), testConfigPath],
});
expect(config.forceRerunTriggers).toEqual([testConfigPath]);
expect(config.testEnvironment).toBe('node');
});

Expand Down
1 change: 1 addition & 0 deletions packages/adapter-rspack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ export function toRstestConfig({
...(output ? { output } : {}),
...(source ? { source } : {}),
...(resolve ? { resolve } : {}),
forceRerunTriggers: configPath ? [path.normalize(configPath)] : undefined,
performance: {
buildCache,
},
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-rspack/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default defineConfig([
cacheDigest: ['file-digest'],
buildDependencies: [join(__dirname, 'cache-extra.ts'), testConfigPath],
});
expect(config.forceRerunTriggers).toEqual([testConfigPath]);
expect(config.testEnvironment).toBe('happy-dom');
});

Expand Down
3 changes: 3 additions & 0 deletions packages/adapter-rspack/tests/toRstestConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ describe('toRstestConfig', () => {
normalize('/repo/configs/rspack.config.ts'),
],
});
expect(config.forceRerunTriggers).toEqual([
normalize('/repo/configs/rspack.config.ts'),
]);
});

it('should keep rstest-generated persistent cache in tools.rspack', () => {
Expand Down
62 changes: 61 additions & 1 deletion packages/core/src/cli/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cac, { type CAC, type Command } from 'cac';
import { normalize, resolve } from 'pathe';
import { normalize, relative, resolve } from 'pathe';
import picomatch from 'picomatch';
import type {
FileFilterMode,
ListCommandOptions,
Expand Down Expand Up @@ -279,6 +280,46 @@ const formatGitError = (error: unknown): string | undefined => {
return undefined;
};

export const getForceRerunTriggers = ({
rootTriggers,
projects,
}: {
rootTriggers: string[];
projects: Array<{ normalizedConfig: { forceRerunTriggers: string[] } }>;
}): string[] =>
Array.from(
new Set([
...rootTriggers,
...projects.flatMap(
(project) => project.normalizedConfig.forceRerunTriggers,
),
]),
);

export const hasForceRerunTrigger = ({
changedFiles,
triggers,
rootPath,
}: {
changedFiles: string[];
triggers: string[];
rootPath: string;
}): boolean => {
if (!triggers.length || !changedFiles.length) {
return false;
}

const matcher = picomatch(
triggers.map((trigger) => normalize(trigger)),
{ windows: true },
);

return changedFiles.some(
(file) =>
matcher(normalize(relative(rootPath, file))) || matcher(normalize(file)),
);
};

export const resolveChangedFiles = async (
cwd: string,
since?: string,
Expand Down Expand Up @@ -401,6 +442,25 @@ const resolveEffectiveCliFilters = async ({
)
: normalizedFilters;

if (
options.changed !== undefined &&
hasForceRerunTrigger({
changedFiles: sourceFilters,
triggers: getForceRerunTriggers({
rootTriggers: rstest.context.normalizedConfig.forceRerunTriggers,
projects: rstest.context.projects,
}),
rootPath: rstest.context.rootPath,
})
) {
return {
effectiveFilters: [],
fileFilterMode: 'fuzzy',
relatedFilters: sourceFilters,
relatedResolutionEmpty: false,
};
}

const relatedFiles = await resolveRelatedTestFiles(rstest.context, {
sourceFilters,
filterLabel: options.changed !== undefined ? '--changed' : '--related',
Expand Down
30 changes: 29 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ type ResolvedExtendEntry =
userConfig: Readonly<RstestConfig>,
) => Promise<ExtendConfig> | ExtendConfig);

const DEFAULT_FORCE_RERUN_TRIGGERS = [
'**/package.json/**',
'**/rstest.config.*',
];

const findConfig = (basePath: string): string | undefined => {
return DEFAULT_CONFIG_EXTENSIONS.map((ext) => basePath + ext).find(
fs.existsSync,
Expand Down Expand Up @@ -123,7 +128,24 @@ export const resolveExtends = async (
extendsEntries.map((entry) => resolveExtendEntry(entry, userConfig)),
);

return mergeRstestConfig(...resolvedExtends, config);
const merged = mergeRstestConfig(...resolvedExtends, config);

if (config.forceRerunTriggers === undefined) {
const extendedForceRerunTriggers = resolvedExtends.flatMap(
(entry) => entry.forceRerunTriggers || [],
);

if (extendedForceRerunTriggers.length) {
merged.forceRerunTriggers = Array.from(
new Set([
...DEFAULT_FORCE_RERUN_TRIGGERS,
...extendedForceRerunTriggers,
]),
);
}
}

return merged;
};

export const mergeProjectConfig = (
Expand Down Expand Up @@ -159,6 +181,8 @@ export const mergeRstestConfig = (...configs: RstestConfig[]): RstestConfig => {

// The following configurations need overrides
merged.include = config.include ?? merged.include;
merged.forceRerunTriggers =
config.forceRerunTriggers ?? merged.forceRerunTriggers;
merged.reporters = config.reporters ?? merged.reporters;
if (merged.coverage) {
merged.coverage.reporters =
Expand All @@ -184,6 +208,7 @@ const createDefaultConfig = (): NormalizedConfig => ({
setupFiles: [],
globalSetup: [],
includeSource: [],
forceRerunTriggers: DEFAULT_FORCE_RERUN_TRIGGERS,
pool: {
type: 'forks',
},
Expand Down Expand Up @@ -333,5 +358,8 @@ export const withDefaultConfig = (config: RstestConfig): NormalizedConfig => {
includeSource: merged.includeSource.map((p) =>
formatRootStr(p, merged.root),
),
forceRerunTriggers: merged.forceRerunTriggers.map((p) =>
formatRootStr(p, merged.root),
),
};
};
7 changes: 7 additions & 0 deletions packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,13 @@ export interface RstestConfig {
* @default []
*/
includeSource?: string[];
/**
* A list of glob patterns that trigger running the whole test suite when
* matched by changed files collected from `--changed`.
*
* @default ['**\/package.json/**', '**\/rstest.config.*']
*/
forceRerunTriggers?: string[];
/**
* Path to setup files. They will be run before each test file.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/core/tests/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ exports[`mergeRstestConfig > should merge config correctly with default config 1
"**/dist/.rstest-temp",
],
},
"forceRerunTriggers": [
"**/package.json/**",
"**/rstest.config.*",
],
"globalSetup": [
"./global-setup.ts",
],
Expand Down
74 changes: 74 additions & 0 deletions packages/core/tests/cli/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { normalize } from 'pathe';
import { describe, expect, it, onTestFinished, rs } from '@rstest/core';
import {
createCli,
getForceRerunTriggers,
hasForceRerunTrigger,
normalizeCliFilters,
resolveChangedFiles,
validateRelatedCliOptions,
Expand Down Expand Up @@ -110,6 +112,78 @@ const createGitFixture = async () => {
return cwd;
};

describe('getForceRerunTriggers', () => {
it('includes project-level force rerun triggers', () => {
expect(
getForceRerunTriggers({
rootTriggers: ['**/package.json/**', 'shared/rstest.config.ts'],
projects: [
{
normalizedConfig: {
forceRerunTriggers: ['apps/a/rsbuild.config.ts'],
},
},
{
normalizedConfig: {
forceRerunTriggers: [
'apps/b/rspack.config.ts',
'shared/rstest.config.ts',
],
},
},
],
}),
).toEqual([
'**/package.json/**',
'shared/rstest.config.ts',
'apps/a/rsbuild.config.ts',
'apps/b/rspack.config.ts',
]);
});
});

describe('hasForceRerunTrigger', () => {
it('matches changed files relative to the project root', () => {
const rootPath = normalize(join('workspace', 'project'));

expect(
hasForceRerunTrigger({
changedFiles: [normalize(join(rootPath, 'packages/app/package.json'))],
triggers: ['**/package.json/**'],
rootPath,
}),
).toBe(true);

expect(
hasForceRerunTrigger({
changedFiles: [normalize(join(rootPath, 'rstest.config.ts'))],
triggers: [normalize(join(rootPath, 'rstest.config.ts'))],
rootPath,
}),
).toBe(true);

expect(
hasForceRerunTrigger({
changedFiles: [normalize(join(rootPath, 'src/index.ts'))],
triggers: ['package.json'],
rootPath,
}),
).toBe(false);
});

it('matches Windows-style absolute triggers', () => {
const rootPath = 'C:\\repo';

expect(
hasForceRerunTrigger({
changedFiles: ['C:\\repo\\rsbuild.config.ts'],
triggers: ['C:\\repo\\rsbuild.config.ts'],
rootPath,
}),
).toBe(true);
});
});

describe('related CLI options', () => {
it('rejects related aliases used together', () => {
expect(() =>
Expand Down
Loading
Loading