Skip to content

Commit 78a3768

Browse files
committed
Implemented Watch mode filter by filename and by test name #1530
1 parent 024de32 commit 78a3768

File tree

12 files changed

+674
-20
lines changed

12 files changed

+674
-20
lines changed

docs/recipes/watch-mode.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,35 @@ export default {
3434

3535
If your tests write to disk they may trigger the watcher to rerun your tests. Configuring additional ignore patterns helps avoid this.
3636

37+
### Filter tests while watching
38+
You may also filter tests while watching by using the cli. For example, after running
39+
```console
40+
$ npx ava --watch
41+
```
42+
You will see a prompt like this :
43+
```console
44+
Type `p` and press enter to filter by a filename regex pattern
45+
[Current filename filter is $pattern]
46+
Type `t` and press enter to filter by a test name regex pattern
47+
[Current test filter is $pattern]
48+
49+
[Type `a` and press enter to run *all* tests]
50+
(Type `r` and press enter to rerun tests ||
51+
Type \`r\` and press enter to rerun tests that match your filters)
52+
Type `u` and press enter to update snapshots
53+
54+
command >
55+
```
56+
So, to run only tests numbered like
57+
- foo23434
58+
- foo4343
59+
- foo93823
60+
61+
You can type `t` and press enter, then type `foo\d+` and press enter.
62+
This will then run all tests that match that pattern.
63+
Afterwards you can use the `r` command to run the matched tests again,
64+
or `a` command to run **all** tests.
65+
3766
## Dependency tracking
3867

3968
AVA tracks which source files your test files depend on. If you change such a dependency only the test file that depends on it will be rerun. AVA will rerun all tests if it cannot determine which test file depends on the changed source file.

lib/api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export default class Api extends Emittery {
206206
runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
207207
firstRun: runtimeOptions.firstRun ?? true,
208208
status: runStatus,
209+
watchModeSkipTestsOrUndefined: runtimeOptions.watchModeSkipTestsOrUndefined,
209210
});
210211

211212
if (setupOrGlobError) {
@@ -273,6 +274,7 @@ export default class Api extends Emittery {
273274
providerStates,
274275
lineNumbers,
275276
recordNewSnapshots: !isCi,
277+
watchModeSkipTestsData: runtimeOptions.watchModeSkipTestsOrUndefined,
276278
// If we're looking for matches, run every single test process in exclusive-only mode
277279
runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
278280
};

lib/reporters/default.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export default class Reporter {
139139
this.previousFailures = plan.previousFailures;
140140
this.emptyParallelRun = plan.status.emptyParallelRun;
141141
this.selectionInsights = plan.status.selectionInsights;
142+
this.watchModeSkipTestsOrUndefined = plan.watchModeSkipTestsOrUndefined;
142143

143144
if (this.watching || plan.files.length > 1) {
144145
this.prefixTitle = (testFile, title) => prefixTitle(this.extensions, plan.filePathPrefix, testFile, title);
@@ -251,9 +252,19 @@ export default class Reporter {
251252

252253
case 'selected-test': {
253254
if (event.skip) {
254-
this.lineWriter.writeLine(colors.skip(`- [skip] ${this.prefixTitle(event.testFile, event.title)}`));
255+
if (!this.watchModeSkipTestsOrUndefined?.shouldSkipEvent(event)) {
256+
this.lineWriter.writeLine(
257+
colors.skip(
258+
`- [skip] ${this.prefixTitle(event.testFile, event.title)}`,
259+
),
260+
);
261+
}
255262
} else if (event.todo) {
256-
this.lineWriter.writeLine(colors.todo(`- [todo] ${this.prefixTitle(event.testFile, event.title)}`));
263+
this.lineWriter.writeLine(
264+
colors.todo(
265+
`- [todo] ${this.prefixTitle(event.testFile, event.title)}`,
266+
),
267+
);
257268
}
258269

259270
break;

lib/runner.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import parseTestArgs from './parse-test-args.js';
1010
import serializeError from './serialize-error.js';
1111
import {load as loadSnapshots, determineSnapshotDir} from './snapshot-manager.js';
1212
import Runnable from './test.js';
13+
import {WatchModeSkipTests} from './watch-mode-skip-tests.js';
1314
import {waitForReady} from './worker/state.cjs';
1415

1516
const makeFileURL = file => file.startsWith('file://') ? file : pathToFileURL(file).toString();
@@ -29,6 +30,10 @@ export default class Runner extends Emittery {
2930
this.serial = options.serial === true;
3031
this.snapshotDir = options.snapshotDir;
3132
this.updateSnapshots = options.updateSnapshots;
33+
this.watchModeSkipTests
34+
= new WatchModeSkipTests(options.watchModeSkipTestsData);
35+
this.skipAllTestsInThisFile = this.watchModeSkipTests
36+
.shouldSkipFile(this.file);
3237

3338
this.activeRunnables = new Set();
3439
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
@@ -91,6 +96,11 @@ export default class Runner extends Emittery {
9196
metadata.taskIndex = this.nextTaskIndex++;
9297

9398
const {args, implementation, title} = parseTestArgs(testArgs);
99+
if (
100+
this.shouldSkipThisTestBecauseItDoesNotMatchARegexSetAtTheWatchCLI(metadata, title.value)
101+
) {
102+
metadata.skipped = true;
103+
}
94104

95105
if (this.checkSelectedByLineNumbers) {
96106
metadata.selected = this.checkSelectedByLineNumbers();
@@ -180,7 +190,7 @@ export default class Runner extends Emittery {
180190
}, {
181191
serial: false,
182192
exclusive: false,
183-
skipped: false,
193+
skipped: this.skipAllTestsInThisFile,
184194
todo: false,
185195
failing: false,
186196
callback: false,
@@ -254,8 +264,8 @@ export default class Runner extends Emittery {
254264
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-array-reduce
255265
if (runnable.metadata.serial || this.serial) {
256266
waitForSerial = previous.then(() =>
257-
// Serial runnables run as long as there was no previous failure, unless
258-
// the runnable should always be run.
267+
// Serial runnables run as long as there was no previous failure, unless
268+
// the runnable should always be run.
259269
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
260270
);
261271
return waitForSerial;
@@ -264,10 +274,10 @@ export default class Runner extends Emittery {
264274
return Promise.all([
265275
previous,
266276
waitForSerial.then(() =>
267-
// Concurrent runnables are kicked off after the previous serial
268-
// runnables have completed, as long as there was no previous failure
269-
// (or if the runnable should always be run). One concurrent runnable's
270-
// failure does not prevent the next runnable from running.
277+
// Concurrent runnables are kicked off after the previous serial
278+
// runnables have completed, as long as there was no previous failure
279+
// (or if the runnable should always be run). One concurrent runnable's
280+
// failure does not prevent the next runnable from running.
271281
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
272282
),
273283
]);
@@ -551,4 +561,12 @@ export default class Runner extends Emittery {
551561
interrupt() {
552562
this.interrupted = true;
553563
}
564+
565+
shouldSkipThisTestBecauseItDoesNotMatchARegexSetAtTheWatchCLI(metadata, testTitle) {
566+
if (metadata.skipped || this.skipAllTestsInThisFile) {
567+
return true;
568+
}
569+
570+
return this.watchModeSkipTests.shouldSkipTest(testTitle);
571+
}
554572
}

lib/watch-mode-skip-tests.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* The WatchModeSkipTests class is used to determine
3+
* if a test should be skipped using filters provided
4+
* by the user in watch mode.
5+
*/
6+
export class WatchModeSkipTests {
7+
/**
8+
* Properties are public to allow
9+
* for easy sending to the worker.
10+
*/
11+
fileRegexOrNull = null;
12+
testRegexOrNull = null;
13+
14+
constructor(watchModeSkipTestsData = undefined) {
15+
if (!watchModeSkipTestsData) {
16+
return;
17+
}
18+
19+
this.fileRegexOrNull = watchModeSkipTestsData.fileRegexOrNull;
20+
this.testRegexOrNull = watchModeSkipTestsData.testRegexOrNull;
21+
}
22+
23+
shouldSkipFile(file) {
24+
if (this.fileRegexOrNull === null) {
25+
return false;
26+
}
27+
28+
return !this.fileRegexOrNull.test(file);
29+
}
30+
31+
shouldSkipTest(testTitle) {
32+
if (this.testRegexOrNull === null) {
33+
return false;
34+
}
35+
36+
return !this.testRegexOrNull.test(testTitle);
37+
}
38+
39+
hasAnyFilters() {
40+
return this.fileRegexOrNull !== null || this.testRegexOrNull !== null;
41+
}
42+
43+
shouldSkipEvent(event) {
44+
return (
45+
this.shouldSkipFile(event.testFile) || this.shouldSkipTest(event.title)
46+
);
47+
}
48+
49+
replaceFileRegex(fileRegexOrNull) {
50+
this.fileRegexOrNull = fileRegexOrNull;
51+
}
52+
53+
replaceTestRegex(testRegexOrNull) {
54+
this.testRegexOrNull = testRegexOrNull;
55+
}
56+
}

0 commit comments

Comments
 (0)