diff --git a/e2e/filter/changed.test.ts b/e2e/filter/changed.test.ts index 11311fbad..9bf3c6f29 100644 --- a/e2e/filter/changed.test.ts +++ b/e2e/filter/changed.test.ts @@ -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'; @@ -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'); diff --git a/packages/adapter-rsbuild/src/toRstestConfig.ts b/packages/adapter-rsbuild/src/toRstestConfig.ts index 006c7f93f..82a3eba0a 100644 --- a/packages/adapter-rsbuild/src/toRstestConfig.ts +++ b/packages/adapter-rsbuild/src/toRstestConfig.ts @@ -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 diff --git a/packages/adapter-rsbuild/tests/index.test.ts b/packages/adapter-rsbuild/tests/index.test.ts index b4186a48e..f6cd019d3 100644 --- a/packages/adapter-rsbuild/tests/index.test.ts +++ b/packages/adapter-rsbuild/tests/index.test.ts @@ -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'); }); diff --git a/packages/adapter-rsbuild/tests/toRstestConfig.test.ts b/packages/adapter-rsbuild/tests/toRstestConfig.test.ts index bb916f884..682b0d9c4 100644 --- a/packages/adapter-rsbuild/tests/toRstestConfig.test.ts +++ b/packages/adapter-rsbuild/tests/toRstestConfig.test.ts @@ -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', () => { diff --git a/packages/adapter-rslib/src/index.ts b/packages/adapter-rslib/src/index.ts index 6d9fa0249..b10e55048 100644 --- a/packages/adapter-rslib/src/index.ts +++ b/packages/adapter-rslib/src/index.ts @@ -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'; @@ -175,6 +175,7 @@ export function withRslibConfig( // Copy over compatible configurations root: finalLibConfig.root, name: libId, + forceRerunTriggers: [normalize(filePath)], plugins: finalLibConfig.plugins, source: { assetsInclude, diff --git a/packages/adapter-rslib/tests/index.test.ts b/packages/adapter-rslib/tests/index.test.ts index 2db1e45d9..b61b31c5b 100644 --- a/packages/adapter-rslib/tests/index.test.ts +++ b/packages/adapter-rslib/tests/index.test.ts @@ -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'); }); diff --git a/packages/adapter-rspack/src/index.ts b/packages/adapter-rspack/src/index.ts index b847395fe..3ff11c319 100644 --- a/packages/adapter-rspack/src/index.ts +++ b/packages/adapter-rspack/src/index.ts @@ -372,6 +372,7 @@ export function toRstestConfig({ ...(output ? { output } : {}), ...(source ? { source } : {}), ...(resolve ? { resolve } : {}), + forceRerunTriggers: configPath ? [path.normalize(configPath)] : undefined, performance: { buildCache, }, diff --git a/packages/adapter-rspack/tests/index.test.ts b/packages/adapter-rspack/tests/index.test.ts index b38ec2b58..dec97648f 100644 --- a/packages/adapter-rspack/tests/index.test.ts +++ b/packages/adapter-rspack/tests/index.test.ts @@ -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'); }); diff --git a/packages/adapter-rspack/tests/toRstestConfig.test.ts b/packages/adapter-rspack/tests/toRstestConfig.test.ts index b0188db64..dd322296a 100644 --- a/packages/adapter-rspack/tests/toRstestConfig.test.ts +++ b/packages/adapter-rspack/tests/toRstestConfig.test.ts @@ -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', () => { diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 8e9ccc48d..95ad84d80 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -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, @@ -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, @@ -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', diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 56a019841..9fee9b0b9 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -31,6 +31,11 @@ type ResolvedExtendEntry = userConfig: Readonly, ) => Promise | 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, @@ -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 = ( @@ -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 = @@ -184,6 +208,7 @@ const createDefaultConfig = (): NormalizedConfig => ({ setupFiles: [], globalSetup: [], includeSource: [], + forceRerunTriggers: DEFAULT_FORCE_RERUN_TRIGGERS, pool: { type: 'forks', }, @@ -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), + ), }; }; diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index c517bbde5..336731cf6 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -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. */ diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index b2b3b3bc1..8aa32e09f 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -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", ], diff --git a/packages/core/tests/cli/commands.test.ts b/packages/core/tests/cli/commands.test.ts index 9ff4ea554..86b7ddd75 100644 --- a/packages/core/tests/cli/commands.test.ts +++ b/packages/core/tests/cli/commands.test.ts @@ -5,6 +5,8 @@ import { normalize } from 'pathe'; import { describe, expect, it, onTestFinished, rs } from '@rstest/core'; import { createCli, + getForceRerunTriggers, + hasForceRerunTrigger, normalizeCliFilters, resolveChangedFiles, validateRelatedCliOptions, @@ -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(() => diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index 1d479de62..37b130dbd 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -1,5 +1,9 @@ import { resolve } from 'pathe'; -import { mergeRstestConfig, withDefaultConfig } from '../src/config'; +import { + mergeRstestConfig, + resolveExtends, + withDefaultConfig, +} from '../src/config'; import { Rstest } from '../src/core/rstest'; import type { RstestConfig } from '../src/types'; import { normalizeBuildCache, resolveProjectBuildCache } from '../src/utils'; @@ -31,6 +35,47 @@ describe('mergeRstestConfig', () => { expect(merged.globalSetup).toEqual(['./single-global-setup.ts']); }); + it('should override forceRerunTriggers', () => { + expect( + withDefaultConfig({ + forceRerunTriggers: ['custom/**'], + }).forceRerunTriggers, + ).toEqual(['custom/**']); + + expect( + withDefaultConfig({ + forceRerunTriggers: [], + }).forceRerunTriggers, + ).toEqual([]); + }); + + it('should append adapter forceRerunTriggers unless user config overrides them', async () => { + await expect( + resolveExtends({ + extends: { + forceRerunTriggers: ['/repo/rsbuild.config.ts'], + }, + }), + ).resolves.toMatchObject({ + forceRerunTriggers: [ + '**/package.json/**', + '**/rstest.config.*', + '/repo/rsbuild.config.ts', + ], + }); + + await expect( + resolveExtends({ + extends: { + forceRerunTriggers: ['/repo/rsbuild.config.ts'], + }, + forceRerunTriggers: ['custom/**'], + }), + ).resolves.toMatchObject({ + forceRerunTriggers: ['custom/**'], + }); + }); + it('should respect output.distPath.root when appending rstest temp exclude', () => { const merged = withDefaultConfig({ root: __dirname, diff --git a/packages/core/tests/core/__snapshots__/rstest.test.ts.snap b/packages/core/tests/core/__snapshots__/rstest.test.ts.snap index ff69150de..35667d243 100644 --- a/packages/core/tests/core/__snapshots__/rstest.test.ts.snap +++ b/packages/core/tests/core/__snapshots__/rstest.test.ts.snap @@ -50,6 +50,10 @@ exports[`rstest context > should generate rstest context correctly 1`] = ` "**/dist/.rstest-temp", ], }, + "forceRerunTriggers": [ + "**/package.json/**", + "**/rstest.config.*", + ], "globalSetup": [], "globals": false, "hideSkippedTestFiles": false, @@ -145,6 +149,10 @@ exports[`rstest context > should generate rstest context correctly with multiple "**/dist/.rstest-temp", ], }, + "forceRerunTriggers": [ + "**/package.json/**", + "**/rstest.config.*", + ], "globalSetup": [], "globals": false, "hideSkippedTestFiles": false, @@ -243,6 +251,10 @@ exports[`rstest context > should generate rstest context correctly with multiple "**/dist/.rstest-temp", ], }, + "forceRerunTriggers": [ + "**/package.json/**", + "**/rstest.config.*", + ], "globalSetup": [], "globals": false, "hideSkippedTestFiles": false, diff --git a/website/docs/en/config/test/_meta.json b/website/docs/en/config/test/_meta.json index 58827d470..ca70f639a 100644 --- a/website/docs/en/config/test/_meta.json +++ b/website/docs/en/config/test/_meta.json @@ -10,6 +10,7 @@ "globals", "pass-with-no-tests", "include-source", + "force-rerun-triggers", "test-name-pattern", "extends", diff --git a/website/docs/en/config/test/force-rerun-triggers.mdx b/website/docs/en/config/test/force-rerun-triggers.mdx new file mode 100644 index 000000000..7ab6ca3e3 --- /dev/null +++ b/website/docs/en/config/test/force-rerun-triggers.mdx @@ -0,0 +1,34 @@ +--- +description: Configure changed-file patterns that force Rstest to run the whole test suite. +--- + +import { ApiMeta } from '@components/ApiMeta'; + +# forceRerunTriggers + + + +- **Type:** `string[]` +- **Default:** `['**/package.json/**', '**/rstest.config.*']` + +`forceRerunTriggers` defines glob patterns that make `--changed` run the whole test suite when any changed file matches them. + +This is useful for files that can affect many tests but are not always represented in the module graph, such as package manifests, test configs, or generated files executed by child processes. + +If you extend config from an adapter such as `withRsbuildConfig`, `withRslibConfig`, or `withRspackConfig`, Rstest also treats the loaded Rsbuild, Rslib, or Rspack config file as a force-rerun trigger. This adapter-provided trigger is appended to the defaults unless you explicitly configure `forceRerunTriggers` in your Rstest config. + +```ts +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + forceRerunTriggers: [ + '**/package.json/**', + '**/rstest.config.*', + 'scripts/cli-output/**', + ], +}); +``` + +For example, if `scripts/cli-output/index.js` changes and matches the configured pattern, `npx rstest run --changed` will run all matched test files instead of only tests resolved from related source files. + +`forceRerunTriggers` uses replace semantics: when you set it, your array becomes the full trigger list. Include the default patterns in your config if you still want them, or set `forceRerunTriggers: []` to disable this behavior. diff --git a/website/docs/en/guide/basic/cli.mdx b/website/docs/en/guide/basic/cli.mdx index de2cb640c..831adcc39 100644 --- a/website/docs/en/guide/basic/cli.mdx +++ b/website/docs/en/guide/basic/cli.mdx @@ -102,6 +102,8 @@ npx rstest run --changed=HEAD~1 npx rstest run --changed=origin/main ``` +When a changed file matches [`forceRerunTriggers`](/config/test/force-rerun-triggers), Rstest runs the whole test suite instead of only related tests. + Use `rstest list` to preview which tests would run without executing them: ```bash diff --git a/website/docs/en/guide/integration/rsbuild.mdx b/website/docs/en/guide/integration/rsbuild.mdx index e4a93b9cb..0c0ec3674 100644 --- a/website/docs/en/guide/integration/rsbuild.mdx +++ b/website/docs/en/guide/integration/rsbuild.mdx @@ -51,6 +51,7 @@ This will automatically: - Load your `rsbuild.config.ts` file - Extract and map test-relevant Rsbuild options to Rstest configuration - Merge with any additional Rstest config you provide +- Add the loaded Rsbuild config file to [`forceRerunTriggers`](/config/test/force-rerun-triggers), so `--changed` runs the full test suite when that config changes The adapter does not reuse the entire Rsbuild config as-is. It keeps the parts that matter for test execution, such as module resolution, transforms, CSS Modules, static asset handling, and `target`, and automatically drops options like `dev`, `server`, and `html` that are specific to the dev server, page entry, or production output. diff --git a/website/docs/en/guide/integration/rslib.mdx b/website/docs/en/guide/integration/rslib.mdx index be6ef417c..7c9e70f7b 100644 --- a/website/docs/en/guide/integration/rslib.mdx +++ b/website/docs/en/guide/integration/rslib.mdx @@ -51,6 +51,7 @@ This will automatically: - Load your `rslib.config.ts` file - Extract and map test-relevant Rslib options to Rstest configuration - Merge with any additional Rstest config you provide +- Add the loaded Rslib config file to [`forceRerunTriggers`](/config/test/force-rerun-triggers), so `--changed` runs the full test suite when that config changes When `performance.buildCache` is enabled in your Rslib config, `withRslibConfig()` passes it through to Rstest and Rstest will still append its own cache defaults, such as runtime digest inputs and config-file-based invalidation. diff --git a/website/docs/en/guide/integration/rspack.mdx b/website/docs/en/guide/integration/rspack.mdx index 593788670..e35f1b595 100644 --- a/website/docs/en/guide/integration/rspack.mdx +++ b/website/docs/en/guide/integration/rspack.mdx @@ -35,6 +35,7 @@ This will automatically: - Load your `rspack.config.ts` file - Map compatible Rspack options to Rstest configuration - Merge with any additional Rstest config you provide +- Add the loaded Rspack config file to [`forceRerunTriggers`](/config/test/force-rerun-triggers), so `--changed` runs the full test suite when that config changes By default, the adapter uses `process.cwd()` to resolve the Rspack config. If your config lives elsewhere, you can use the `cwd` option. See [API](#api) for more details. diff --git a/website/docs/zh/config/test/_meta.json b/website/docs/zh/config/test/_meta.json index 58827d470..ca70f639a 100644 --- a/website/docs/zh/config/test/_meta.json +++ b/website/docs/zh/config/test/_meta.json @@ -10,6 +10,7 @@ "globals", "pass-with-no-tests", "include-source", + "force-rerun-triggers", "test-name-pattern", "extends", diff --git a/website/docs/zh/config/test/force-rerun-triggers.mdx b/website/docs/zh/config/test/force-rerun-triggers.mdx new file mode 100644 index 000000000..8bf144e38 --- /dev/null +++ b/website/docs/zh/config/test/force-rerun-triggers.mdx @@ -0,0 +1,34 @@ +--- +description: 配置哪些变更文件会强制 Rstest 运行完整测试套件。 +--- + +import { ApiMeta } from '@components/ApiMeta'; + +# forceRerunTriggers + + + +- **类型:** `string[]` +- **默认值:** `['**/package.json/**', '**/rstest.config.*']` + +`forceRerunTriggers` 用于定义一组 glob pattern。当 `--changed` 收集到的变更文件命中这些 pattern 时,Rstest 会运行完整测试套件。 + +这适用于可能影响大量测试、但不一定会出现在 module graph 中的文件,例如 package manifest、测试配置,或由子进程执行的生成文件。 + +如果你通过 `withRsbuildConfig`、`withRslibConfig` 或 `withRspackConfig` 等 adapter 扩展配置,Rstest 也会把被加载的 Rsbuild、Rslib 或 Rspack 配置文件作为 force-rerun trigger。除非你在 Rstest 配置中显式设置 `forceRerunTriggers`,否则 adapter 提供的 trigger 会追加到默认值之后。 + +```ts +import { defineConfig } from '@rstest/core'; + +export default defineConfig({ + forceRerunTriggers: [ + '**/package.json/**', + '**/rstest.config.*', + 'scripts/cli-output/**', + ], +}); +``` + +例如,当 `scripts/cli-output/index.js` 发生变更并命中配置的 pattern 时,`npx rstest run --changed` 会运行所有匹配的测试文件,而不是只运行通过相关源码解析出的测试。 + +`forceRerunTriggers` 使用替换语义:一旦设置,该数组就是完整的 trigger 列表。如果仍然需要默认 pattern,请在配置中一并写入;如果要禁用该行为,可以设置 `forceRerunTriggers: []`。 diff --git a/website/docs/zh/guide/basic/cli.mdx b/website/docs/zh/guide/basic/cli.mdx index e114fe801..8b2326260 100644 --- a/website/docs/zh/guide/basic/cli.mdx +++ b/website/docs/zh/guide/basic/cli.mdx @@ -102,6 +102,8 @@ npx rstest run --changed=HEAD~1 npx rstest run --changed=origin/main ``` +当变更文件命中 [`forceRerunTriggers`](/config/test/force-rerun-triggers) 时,Rstest 会运行完整测试套件,而不是只运行相关测试。 + 如果只想预览将要执行哪些测试,可以配合 `rstest list` 使用: ```bash diff --git a/website/docs/zh/guide/integration/rsbuild.mdx b/website/docs/zh/guide/integration/rsbuild.mdx index 93f0cfcb6..d90f20330 100644 --- a/website/docs/zh/guide/integration/rsbuild.mdx +++ b/website/docs/zh/guide/integration/rsbuild.mdx @@ -51,6 +51,7 @@ export default defineConfig({ - 加载你的 `rsbuild.config.ts` 文件 - 提取并映射测试相关的 Rsbuild 选项到 Rstest 配置 - 与你提供的任何其他 Rstest 配置合并 +- 将加载到的 Rsbuild 配置文件加入 [`forceRerunTriggers`](/config/test/force-rerun-triggers),因此该配置变更时 `--changed` 会运行完整测试套件 适配器不会原样复用整份 Rsbuild 配置,而是只保留测试运行需要的部分,例如模块解析、代码转换、CSS Modules、静态资源处理和 `target`。像 `dev`、`server`、`html` 这类面向开发服务器、页面入口或产物输出的配置,会在转换时自动裁剪掉,避免把测试无关配置带入 Rstest。 diff --git a/website/docs/zh/guide/integration/rslib.mdx b/website/docs/zh/guide/integration/rslib.mdx index dcd532d66..facdea5ea 100644 --- a/website/docs/zh/guide/integration/rslib.mdx +++ b/website/docs/zh/guide/integration/rslib.mdx @@ -51,6 +51,7 @@ export default defineConfig({ - 加载你的 `rslib.config.ts` 文件 - 提取并映射测试相关的 Rslib 选项到 Rstest 配置 - 与你提供的其他 Rstest 配置合并 +- 将加载到的 Rslib 配置文件加入 [`forceRerunTriggers`](/config/test/force-rerun-triggers),因此该配置变更时 `--changed` 会运行完整测试套件 如果你在 Rslib 配置里开启了 `performance.buildCache`,`withRslibConfig()` 会把这项配置传给 Rstest,Rstest 仍会继续补充自己的缓存默认值,例如运行时摘要项和基于配置文件的失效条件。 diff --git a/website/docs/zh/guide/integration/rspack.mdx b/website/docs/zh/guide/integration/rspack.mdx index a3ab7cb80..76e78f64e 100644 --- a/website/docs/zh/guide/integration/rspack.mdx +++ b/website/docs/zh/guide/integration/rspack.mdx @@ -35,6 +35,7 @@ export default defineConfig({ - 加载你的 `rspack.config.ts` 文件 - 将兼容的 Rspack 选项映射到 Rstest 配置 - 与你提供的任何其他 Rstest 配置合并 +- 将加载到的 Rspack 配置文件加入 [`forceRerunTriggers`](/config/test/force-rerun-triggers),因此该配置变更时 `--changed` 会运行完整测试套件 默认情况下,适配器使用 `process.cwd()` 来解析 Rspack 配置。如果你的配置文件在其他地方,你可以使用 `cwd` 选项。更多详情请参阅 [API](#api) 部分。