Skip to content

Commit 57a3bbe

Browse files
mmuletMichael Muletnovemberborn
authored
Implement file globbing and test matching within watch mode
Make watch mode interactive by allowing users to specify an additional glob pattern to select specific files, and a `--match` pattern to select specific tests. This means you no longer have to exit watch mode to achieve the same. Co-authored-by: Michael Mulet <[email protected]> Co-authored-by: Mark Wubben <[email protected]>
1 parent 29cb29a commit 57a3bbe

File tree

11 files changed

+564
-75
lines changed

11 files changed

+564
-75
lines changed

docs/recipes/watch-mode.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,34 @@ 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+
39+
You may also filter tests while watching by using the CLI. For example, after running
40+
41+
```console
42+
npx ava --watch
43+
```
44+
45+
You will see a prompt like this:
46+
47+
```console
48+
Type `g` followed by enter to filter test files by a glob pattern
49+
Type `m` followed by enter to filter tests by their title
50+
Type `r` followed by enter to rerun tests
51+
Type `u` followed by enter to update snapshots in selected tests
52+
>
53+
```
54+
55+
So, to run only tests numbered like
56+
57+
- foo23434
58+
- foo4343
59+
- foo93823
60+
61+
You can type `m` and press enter, then type `foo*` and press enter. This will then run all tests that match that glob.
62+
63+
Afterwards you can use the `r` command to run the matched tests again, or `a` command to run **all** tests.
64+
3765
## Dependency tracking
3866

3967
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.
@@ -62,7 +90,6 @@ Sometimes watch mode does something surprising like rerunning all tests when you
6290
$ DEBUG=ava:watcher npx ava --watch
6391
```
6492

65-
[`chokidar`]: https://github.com/paulmillr/chokidar
6693
[Install Troubleshooting]: https://github.com/paulmillr/chokidar#install-troubleshooting
6794
[`ignore-by-default`]: https://github.com/novemberborn/ignore-by-default
6895
[`.only` modifier]: ../01-writing-tests.md#running-specific-tests

lib/api.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default class Api extends Emittery {
8888
}
8989
}
9090

91-
async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) { // eslint-disable-line complexity
91+
async run({files: selectedFiles = [], filter = [], runtimeOptions = {}, testFileSelector} = {}) { // eslint-disable-line complexity
9292
let setupOrGlobError;
9393

9494
const apiOptions = this.options;
@@ -149,7 +149,9 @@ export default class Api extends Emittery {
149149
let testFiles;
150150
try {
151151
testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
152-
if (selectedFiles.length === 0) {
152+
if (typeof testFileSelector === 'function') {
153+
selectedFiles = testFileSelector(testFiles, selectedFiles);
154+
} else if (selectedFiles.length === 0) {
153155
selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
154156
cwd: this.options.projectDir,
155157
filter: filter.map(({pattern}) => pattern),
@@ -163,7 +165,7 @@ export default class Api extends Emittery {
163165
}
164166

165167
const selectionInsights = {
166-
filter,
168+
filter: selectedFiles.appliedFilters ?? filter,
167169
ignoredFilterPatternFiles: selectedFiles.ignoredFilterPatternFiles ?? [],
168170
testFileCount: testFiles.length,
169171
selectionCount: selectedFiles.length,
@@ -201,8 +203,8 @@ export default class Api extends Emittery {
201203
failFastEnabled: failFast,
202204
filePathPrefix: getFilePathPrefix(selectedFiles),
203205
files: selectedFiles,
204-
matching: apiOptions.match.length > 0,
205-
previousFailures: runtimeOptions.previousFailures ?? 0,
206+
matching: apiOptions.match.length > 0 || runtimeOptions.interactiveMatchPattern !== undefined,
207+
previousFailures: runtimeOptions.countPreviousFailures?.() ?? 0,
206208
firstRun: runtimeOptions.firstRun ?? true,
207209
status: runStatus,
208210
});
@@ -265,12 +267,13 @@ export default class Api extends Emittery {
265267

266268
const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
267269
// Removing `providers` and `sortTestFiles` fields because they cannot be transferred to the worker threads.
268-
const {providers, sortTestFiles, ...forkOptions} = apiOptions;
270+
const {providers, sortTestFiles, match, ...forkOptions} = apiOptions;
269271
const options = {
270272
...forkOptions,
271273
providerStates,
272274
lineNumbers,
273275
recordNewSnapshots: !isCi,
276+
match: runtimeOptions.interactiveMatchPattern === undefined ? match : [...match, runtimeOptions.interactiveMatchPattern],
274277
};
275278

276279
if (runtimeOptions.updateSnapshots) {

lib/reporters/default.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,29 @@ class LineWriter extends stream.Writable {
2626

2727
this.dest = dest;
2828
this.columns = dest.columns ?? 80;
29-
this.lastLineIsEmpty = false;
29+
this.lastLineIsEmpty = true;
3030
}
3131

3232
_write(chunk, _, callback) {
3333
this.dest.write(chunk);
3434
callback();
3535
}
3636

37-
writeLine(string) {
37+
writeLine(string, indent = true) {
3838
if (string) {
39-
this.write(indentString(string, 2) + os.EOL);
39+
this.write((indent ? indentString(string, 2) : string) + os.EOL);
4040
this.lastLineIsEmpty = false;
4141
} else {
4242
this.write(os.EOL);
4343
this.lastLineIsEmpty = true;
4444
}
4545
}
4646

47+
write(string) {
48+
this.lastLineIsEmpty = false;
49+
super.write(string);
50+
}
51+
4752
ensureEmptyLine() {
4853
if (!this.lastLineIsEmpty) {
4954
this.writeLine();
@@ -120,7 +125,6 @@ export default class Reporter {
120125
this.previousFailures = 0;
121126

122127
this.failFastEnabled = false;
123-
this.lastLineIsEmpty = false;
124128
this.matching = false;
125129

126130
this.removePreviousListener = null;
@@ -628,7 +632,7 @@ export default class Reporter {
628632
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t find any files to test` + firstLinePostfix));
629633
} else {
630634
const {testFileCount: count} = this.selectionInsights;
631-
this.lineWriter.writeLine(colors.error(`${figures.cross} Based on your configuration, ${count} test ${plur('file was', 'files were', count)} found, but did not match the CLI arguments:` + firstLinePostfix));
635+
this.lineWriter.writeLine(colors.error(`${figures.cross} Based on your configuration, ${count} test ${plur('file was', 'files were', count)} found, but did not match the filters:` + firstLinePostfix));
632636
this.lineWriter.writeLine();
633637
for (const {pattern} of this.selectionInsights.filter) {
634638
this.lineWriter.writeLine(colors.error(`* ${pattern}`));

0 commit comments

Comments
 (0)