Skip to content

Commit a067531

Browse files
authored
feat: introduce watchTriggerPatterns option (#7778)
1 parent 029c078 commit a067531

File tree

10 files changed

+138
-5
lines changed

10 files changed

+138
-5
lines changed

docs/config/index.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,36 @@ In interactive environments, this is the default, unless `--run` is specified ex
697697

698698
In CI, or when run from a non-interactive shell, "watch" mode is not the default, but can be enabled explicitly with this flag.
699699

700+
### watchTriggerPatterns <Version>3.2.0</Version><NonProjectOption /> {#watchtriggerpatterns}
701+
702+
- **Type:** `WatcherTriggerPattern[]`
703+
704+
Vitest reruns tests based on the module graph which is populated by static and dynamic `import` statements. However, if you are reading from the file system or fetching from a proxy, then Vitest cannot detect those dependencies.
705+
706+
To correctly rerun those tests, you can define a regex pattern and a function that retuns a list of test files to run.
707+
708+
```ts
709+
import { defineConfig } from 'vitest/config'
710+
711+
export default defineConfig({
712+
test: {
713+
watchTriggerPatterns: [
714+
{
715+
pattern: /^src\/(mailers|templates)\/(.*)\.(ts|html|txt)$/,
716+
testToRun: (match) => {
717+
// relative to the root value
718+
return `./api/tests/mailers/${match[2]}.test.ts`
719+
},
720+
},
721+
],
722+
},
723+
})
724+
```
725+
726+
::: warning
727+
Returned files should be either absolute or relative to the root. Note that this is a global option, and it cannot be used inside of [project](/guide/workspace) configs.
728+
:::
729+
700730
### root
701731

702732
- **Type:** `string`

packages/vitest/src/node/cli/cli-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
863863
json: null,
864864
provide: null,
865865
filesOnly: null,
866+
watchTriggerPatterns: null,
866867
}
867868

868869
export const benchCliOptionsConfig: Pick<

packages/vitest/src/node/types/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
BuiltinReporters,
1717
} from '../reporters'
1818
import type { TestSequencerConstructor } from '../sequencers/types'
19+
import type { WatcherTriggerPattern } from '../watcher'
1920
import type { BenchmarkUserOptions } from './benchmark'
2021
import type { BrowserConfigOptions, ResolvedBrowserOptions } from './browser'
2122
import type { CoverageOptions, ResolvedCoverageOptions } from './coverage'
@@ -491,6 +492,12 @@ export interface InlineConfig {
491492
*/
492493
forceRerunTriggers?: string[]
493494

495+
/**
496+
* Pattern configuration to rerun only the tests that are affected
497+
* by the changes of specific files in the repository.
498+
*/
499+
watchTriggerPatterns?: WatcherTriggerPattern[]
500+
494501
/**
495502
* Coverage options
496503
*/
@@ -1094,6 +1101,7 @@ type NonProjectOptions =
10941101
| 'minWorkers'
10951102
| 'fileParallelism'
10961103
| 'workspace'
1104+
| 'watchTriggerPatterns'
10971105

10981106
export type ProjectConfig = Omit<
10991107
InlineConfig,

packages/vitest/src/node/watcher.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { TestProject } from './project'
33
import { readFileSync } from 'node:fs'
44
import { noop, slash } from '@vitest/utils'
55
import mm from 'micromatch'
6+
import { resolve } from 'pathe'
67

78
export class VitestWatcher {
89
/**
@@ -54,14 +55,42 @@ export class VitestWatcher {
5455
this._onRerun.forEach(cb => cb(file))
5556
}
5657

58+
private getTestFilesFromWatcherTrigger(id: string): boolean {
59+
if (!this.vitest.config.watchTriggerPatterns) {
60+
return false
61+
}
62+
let triggered = false
63+
this.vitest.config.watchTriggerPatterns.forEach((definition) => {
64+
const exec = definition.pattern.exec(id)
65+
if (exec) {
66+
const files = definition.testsToRun(id, exec)
67+
if (Array.isArray(files)) {
68+
triggered = true
69+
files.forEach(file => this.changedTests.add(resolve(this.vitest.config.root, file)))
70+
}
71+
else if (typeof files === 'string') {
72+
triggered = true
73+
this.changedTests.add(resolve(this.vitest.config.root, files))
74+
}
75+
}
76+
})
77+
return triggered
78+
}
79+
5780
private onChange = (id: string): void => {
5881
id = slash(id)
5982
this.vitest.logger.clearHighlightCache(id)
6083
this.vitest.invalidateFile(id)
61-
const needsRerun = this.handleFileChanged(id)
62-
if (needsRerun) {
84+
const testFiles = this.getTestFilesFromWatcherTrigger(id)
85+
if (testFiles) {
6386
this.scheduleRerun(id)
6487
}
88+
else {
89+
const needsRerun = this.handleFileChanged(id)
90+
if (needsRerun) {
91+
this.scheduleRerun(id)
92+
}
93+
}
6594
}
6695

6796
private onUnlink = (id: string): void => {
@@ -82,6 +111,13 @@ export class VitestWatcher {
82111
private onAdd = (id: string): void => {
83112
id = slash(id)
84113
this.vitest.invalidateFile(id)
114+
115+
const testFiles = this.getTestFilesFromWatcherTrigger(id)
116+
if (testFiles) {
117+
this.scheduleRerun(id)
118+
return
119+
}
120+
85121
let fileContent: string | undefined
86122

87123
const matchingProjects: TestProject[] = []
@@ -171,3 +207,11 @@ export class VitestWatcher {
171207
return !!files.length
172208
}
173209
}
210+
211+
export interface WatcherTriggerPattern {
212+
pattern: RegExp
213+
testsToRun: (
214+
file: string,
215+
match: RegExpMatchArray
216+
) => string[] | string | null | undefined | void
217+
}

packages/vitest/src/public/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export {
1919
defaultExclude,
2020
defaultInclude,
2121
} from '../defaults'
22+
export type { WatcherTriggerPattern } from '../node/watcher'
2223
export { mergeConfig } from 'vite'
2324
export type { Plugin } from 'vite'
2425

packages/vitest/src/public/node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,15 @@ export type { TestRunResult } from '../node/types/tests'
130130
export const TestFile: typeof _TestFile = _TestFile
131131
export type { WorkerContext } from '../node/types/worker'
132132
export { createViteLogger } from '../node/viteLogger'
133-
export { distDir, rootDir } from '../paths'
133+
export type { WatcherTriggerPattern } from '../node/watcher'
134134

135135
/**
136136
* @deprecated Use `ModuleDiagnostic` instead
137137
*/
138138
export type FileDiagnostic = _FileDiagnostic
139139

140+
export { distDir, rootDir } from '../paths'
141+
140142
export type {
141143
CollectLineNumbers as TypeCheckCollectLineNumbers,
142144
CollectLines as TypeCheckCollectLines,
@@ -147,9 +149,7 @@ export type {
147149
} from '../typecheck/types'
148150

149151
export type { TestExecutionMethod as TestExecutionType } from '../types/worker'
150-
151152
export { createDebugger } from '../utils/debugger'
152-
153153
export type {
154154
RunnerTask,
155155
RunnerTaskResult,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { readFileSync } from 'node:fs';
2+
import { resolve } from 'node:path';
3+
import { expect, test } from 'vitest';
4+
5+
const filepath = resolve(import.meta.dirname, './text.txt');
6+
7+
test('basic', () => {
8+
expect(readFileSync(filepath, 'utf-8')).toBe('hello world\n');
9+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello world
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { defineConfig } from 'vitest/config';
2+
3+
export default defineConfig({
4+
test: {
5+
watchTriggerPatterns: [
6+
{
7+
pattern: /folder\/(\w+)\/.*\.txt$/,
8+
testsToRun: (id, match) => {
9+
return `./folder/${match[1]}/basic.test.ts`;
10+
},
11+
}
12+
]
13+
}
14+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { resolve } from 'node:path'
2+
import { expect, test } from 'vitest'
3+
import { editFile, runVitest } from '../../test-utils'
4+
5+
const root = resolve(import.meta.dirname, '../fixtures/watch-trigger-pattern')
6+
7+
test('watch trigger pattern picks up the file', async () => {
8+
const { stderr, vitest } = await runVitest({
9+
root,
10+
watch: true,
11+
})
12+
13+
expect(stderr).toBe('')
14+
15+
await vitest.waitForStdout('Waiting for file changes')
16+
17+
editFile(
18+
resolve(root, 'folder/fs/text.txt'),
19+
content => content.replace('world', 'vitest'),
20+
)
21+
22+
await vitest.waitForStderr('basic.test.ts')
23+
24+
expect(vitest.stderr).toContain(`expected 'hello vitest\\n' to be 'hello world\\n'`)
25+
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy