Skip to content

Implemented Watch mode filter by filename and by test name #1530 #3372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
78a3768
Implemented Watch mode filter by filename and by test name #1530
Mar 26, 2025
8ecd51d
fix tests on onlder node versions
Mar 27, 2025
0d78fd3
Format documentation
novemberborn Apr 4, 2025
b72a9dd
Add newline at EOF
novemberborn Apr 4, 2025
a3cce75
Fix error handling in watcher tests
novemberborn Apr 4, 2025
b0762b0
Remove unnecessary this.done() calls in catch blocks
novemberborn Apr 4, 2025
5195cbe
Remove unnecessary multiline comments
novemberborn Apr 4, 2025
16630d4
implemented fixes
Apr 7, 2025
0b88520
lint fix
Apr 7, 2025
e958e4b
Merge branch 'main' into Watch_mode_filter
novemberborn Apr 16, 2025
63ff4fc
Revert whitespace changes in reporter
novemberborn Apr 17, 2025
0948247
Revert test worker changes
novemberborn Apr 25, 2025
747ab06
Move interactive filters tests to their own file
novemberborn Apr 30, 2025
8c14e1a
Improve empty line tracking in reporter
novemberborn Apr 29, 2025
1ade579
Add option in reporter line writer to not indent the line
novemberborn Apr 29, 2025
68dd12f
Remove dead chokidar link reference definition
novemberborn Apr 29, 2025
9c1e8c1
Remove special .only() behavior in watch mode
novemberborn Apr 29, 2025
65acae4
Implement --match using the selected flag
novemberborn Apr 29, 2025
6f2adff
Place available() function before reportEndMessage
novemberborn Apr 29, 2025
74d4077
Use helpers to read lines and process commands
novemberborn Apr 29, 2025
8561644
Refactor command instructions and filter prompts for improved clarity…
novemberborn Apr 29, 2025
7b98b2d
Simplify change signaling, don't require an argument
novemberborn Apr 29, 2025
fe34e34
Don't respond to changes when prompting for filters
novemberborn Apr 29, 2025
9575970
Implement interactive file filters using globs
novemberborn Apr 29, 2025
a05369a
Implement interactive test file filter akin to --match
novemberborn Apr 29, 2025
795e9a5
Change run-all to not reset filters
novemberborn May 1, 2025
ba49297
Fix docs and empty glob prompt bug
May 20, 2025
a0b769e
lint fix
May 20, 2025
121f5fd
fix return bug and allow ctrl+c to exit without any changes
May 27, 2025
416d921
fix lint and tests
May 27, 2025
f4a52bf
not sure why this failed in CI. It works on my macos node 18.20
May 27, 2025
e33f6a2
exit on close (like Ctrl+D)
May 27, 2025
5c3e573
Use runtime Promise.withResolvers if available
novemberborn Jun 5, 2025
9e56e7a
Tweak line reading
novemberborn Jun 5, 2025
c45bbef
Remove workaround for flaky test
novemberborn Jun 5, 2025
7f05001
Extra empty-line before Ctrl+C instructions
novemberborn Jun 5, 2025
91903fc
Remove split2
novemberborn Jun 5, 2025
ea5380d
Merge branch 'main' into Watch_mode_filter
novemberborn Jun 5, 2025
6e606fa
Merge branch 'main' into Watch_mode_filter
novemberborn Jun 6, 2025
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
29 changes: 28 additions & 1 deletion docs/recipes/watch-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ export default {

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

### Filter tests while watching

You may also filter tests while watching by using the CLI. For example, after running

```console
npx ava --watch
```

You will see a prompt like this:

```console
Type `g` followed by enter to filter test files by a glob pattern
Type `m` followed by enter to filter tests by their title
Type `r` followed by enter to rerun tests
Type `u` followed by enter to update snapshots in selected tests
>
```

So, to run only tests numbered like

- foo23434
- foo4343
- foo93823

You can type `m` and press enter, then type `foo*` and press enter. This will then run all tests that match that glob.

Afterwards you can use the `r` command to run the matched tests again, or `a` command to run **all** tests.

## Dependency tracking

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

[`chokidar`]: https://github.com/paulmillr/chokidar
[Install Troubleshooting]: https://github.com/paulmillr/chokidar#install-troubleshooting
[`ignore-by-default`]: https://github.com/novemberborn/ignore-by-default
[`.only` modifier]: ../01-writing-tests.md#running-specific-tests
Expand Down
15 changes: 9 additions & 6 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default class Api extends Emittery {
}
}

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

const apiOptions = this.options;
Expand Down Expand Up @@ -149,7 +149,9 @@ export default class Api extends Emittery {
let testFiles;
try {
testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
if (selectedFiles.length === 0) {
if (typeof testFileSelector === 'function') {
selectedFiles = testFileSelector(testFiles, selectedFiles);
} else if (selectedFiles.length === 0) {
selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
cwd: this.options.projectDir,
filter: filter.map(({pattern}) => pattern),
Expand All @@ -163,7 +165,7 @@ export default class Api extends Emittery {
}

const selectionInsights = {
filter,
filter: selectedFiles.appliedFilters ?? filter,
ignoredFilterPatternFiles: selectedFiles.ignoredFilterPatternFiles ?? [],
testFileCount: testFiles.length,
selectionCount: selectedFiles.length,
Expand Down Expand Up @@ -201,8 +203,8 @@ export default class Api extends Emittery {
failFastEnabled: failFast,
filePathPrefix: getFilePathPrefix(selectedFiles),
files: selectedFiles,
matching: apiOptions.match.length > 0,
previousFailures: runtimeOptions.previousFailures ?? 0,
matching: apiOptions.match.length > 0 || runtimeOptions.interactiveMatchPattern !== undefined,
previousFailures: runtimeOptions.countPreviousFailures?.() ?? 0,
firstRun: runtimeOptions.firstRun ?? true,
status: runStatus,
});
Expand Down Expand Up @@ -265,12 +267,13 @@ export default class Api extends Emittery {

const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
// Removing `providers` and `sortTestFiles` fields because they cannot be transferred to the worker threads.
const {providers, sortTestFiles, ...forkOptions} = apiOptions;
const {providers, sortTestFiles, match, ...forkOptions} = apiOptions;
const options = {
...forkOptions,
providerStates,
lineNumbers,
recordNewSnapshots: !isCi,
match: runtimeOptions.interactiveMatchPattern === undefined ? match : [...match, runtimeOptions.interactiveMatchPattern],
};

if (runtimeOptions.updateSnapshots) {
Expand Down
14 changes: 9 additions & 5 deletions lib/reporters/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,29 @@ class LineWriter extends stream.Writable {

this.dest = dest;
this.columns = dest.columns ?? 80;
this.lastLineIsEmpty = false;
this.lastLineIsEmpty = true;
}

_write(chunk, _, callback) {
this.dest.write(chunk);
callback();
}

writeLine(string) {
writeLine(string, indent = true) {
if (string) {
this.write(indentString(string, 2) + os.EOL);
this.write((indent ? indentString(string, 2) : string) + os.EOL);
this.lastLineIsEmpty = false;
} else {
this.write(os.EOL);
this.lastLineIsEmpty = true;
}
}

write(string) {
this.lastLineIsEmpty = false;
super.write(string);
}

ensureEmptyLine() {
if (!this.lastLineIsEmpty) {
this.writeLine();
Expand Down Expand Up @@ -120,7 +125,6 @@ export default class Reporter {
this.previousFailures = 0;

this.failFastEnabled = false;
this.lastLineIsEmpty = false;
this.matching = false;

this.removePreviousListener = null;
Expand Down Expand Up @@ -628,7 +632,7 @@ export default class Reporter {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t find any files to test` + firstLinePostfix));
} else {
const {testFileCount: count} = this.selectionInsights;
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));
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));
this.lineWriter.writeLine();
for (const {pattern} of this.selectionInsights.filter) {
this.lineWriter.writeLine(colors.error(`* ${pattern}`));
Expand Down
Loading