Skip to content

Commit

Permalink
Merge pull request #607 from sindresorhus/document-watch
Browse files Browse the repository at this point in the history
Document watch mode
  • Loading branch information
sindresorhus committed Mar 8, 2016
2 parents 1868204 + 81b7f1e commit cac933e
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 74 deletions.
8 changes: 4 additions & 4 deletions cli.js
Expand Up @@ -54,9 +54,8 @@ var cli = meow([
' --verbose, -v Enable verbose output',
' --no-cache Disable the transpiler cache',
' --match, -m Only run tests with matching title (Can be repeated)',
// Leave --watch and --sources undocumented until they're stable enough
// ' --watch, -w Re-run tests when tests and source files change',
// ' --source Pattern to match source files so tests can be re-run (Can be repeated)',
' --watch, -w Re-run tests when tests and source files change',
' --source, -S Pattern to match source files so tests can be re-run (Can be repeated)',
'',
'Examples',
' ava',
Expand Down Expand Up @@ -88,8 +87,9 @@ var cli = meow([
v: 'verbose',
r: 'require',
s: 'serial',
m: 'match',
w: 'watch',
m: 'match'
S: 'source'
}
});

Expand Down
96 changes: 96 additions & 0 deletions docs/recipes/watch-mode.md
@@ -0,0 +1,96 @@
# Watch mode

AVA comes with an intelligent watch mode. It watches for files to change and runs just those tests that are affected.

## Running tests with watch mode enabled

You can enable watch mode using the `--watch` or `-w` flags. If you have installed AVA globally:

```console
$ ava --watch
```

If you've configured it in your `package.json` like this:

```json
{
"scripts": {
"test": "ava"
}
}
```

You can run:

```console
$ npm test -- --watch
```

You could also set up a special script:

```json
{
"scripts": {
"test": "ava",
"test:watch": "ava --watch"
}
}
```

And then use:

```console
$ npm run test:watch
```

## Requirements

AVA uses [`chokidar`] as the file watcher. It's configured as an optional dependency since `chokidar` sometimes can't be installed. Watch mode is not available if `chokidar` fails to install, instead you'll see a message like:

> The optional dependency chokidar failed to install and is required for --watch. Chokidar is likely not supported on your platform.
Please refer to the [`chokidar` documentation][`chokidar`] for how to resolve this problem.

## Source files and test files

In AVA there's a distinction between *source files* and *test files*. As you can imagine the *test files* contain your tests. *Source files* are all other files that are needed for the tests to run, be it your source code or test fixtures.

By default AVA watches for changes to the test files, `package.json`, and any other `.js` files. It'll ignore files in [certain directories](https://github.com/novemberborn/ignore-by-default/blob/master/index.js) as provided by the [`ignore-by-default`] package.

You can configure patterns for the source files using the [`--source` CLI flag] or in the `ava` section of your `package.json` file. Note that if you specify a negative pattern the directories from [`ignore-by-default`] will no longer be ignored, so you may want to repeat these in your config.

If your tests write to disk they may trigger the watcher to rerun your tests. If this occurs you will need to use the `--source` flag.

## 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.

Dependency tracking works for required modules. Custom extensions and transpilers are supported, provided you loaded them using the [`--require` CLI flag] and not from inside your test file. Files accessed using the `fs` module are not tracked.

## Manually rerunning all tests

You can quickly rerun all tests by typing <kbd>r</kbd> on the console, followed by <kbd>Enter</kbd>.

## Debugging

Sometimes watch mode does something surprising like rerunning all tests when you thought only a single test would be run. To see its reasoning you can enable a debug mode:

```console
$ DEBUG=ava:watcher npm test -- --watch
```

On Windows use:

```console
$ set DEBUG=ava:watcher
$ npm test -- --watch
```

## Help us make watch mode better

Watch mode is relatively new and there might be some rough edges. Please [report](https://github.com/sindresorhus/ava/issues) any issues you encounter. Thanks!

[`chokidar`]: https://github.com/paulmillr/chokidar
[`ignore-by-default`]: https://github.com/novemberborn/ignore-by-default
[`--require` CLI flag]: https://github.com/sindresorhus/ava#cli
[`--source` CLI flag]: https://github.com/sindresorhus/ava#cli
2 changes: 1 addition & 1 deletion lib/watcher.js
Expand Up @@ -120,7 +120,7 @@ Watcher.prototype.observeStdin = function (stdin) {
stdin.setEncoding('utf8');
stdin.on('data', function (data) {
data = data.trim().toLowerCase();
if (data !== 'rs') {
if (data !== 'r' && data !== 'rs') {
return;
}

Expand Down
15 changes: 15 additions & 0 deletions readme.md
Expand Up @@ -117,6 +117,14 @@ test('bar', async t => {
$ npm test
```

### Watch it

```console
$ npm test -- --watch
```

AVA comes with an intelligent watch mode. [Learn more in its recipe](docs/recipes/watch-mode.md).

## CLI

```console
Expand All @@ -134,6 +142,8 @@ $ ava --help
--verbose, -v Enable verbose output
--no-cache Disable the transpiler cache
--match, -m Only run tests with matching title (Can be repeated)',
--watch, -w Re-run tests when tests and source files change
--source, -S Pattern to match source files so tests can be re-run (Can be repeated)

Examples
ava
Expand Down Expand Up @@ -164,6 +174,10 @@ All of the CLI options can be configured in the `ava` section of your `package.j
"my-test-folder/*.js",
"!**/not-this-file.js"
],
"source": [
"**/*.{js,jsx}",
"!dist/**/*"
],
"match": [
"*oo",
"!foo"
Expand Down Expand Up @@ -880,6 +894,7 @@ It's the [Andromeda galaxy](https://simple.wikipedia.org/wiki/Andromeda_galaxy).
## Recipes

- [Code coverage](docs/recipes/code-coverage.md)
- [Watch mode](docs/recipes/watch-mode.md)
- [Endpoint testing](docs/recipes/endpoint-testing.md)
- [When to use `t.plan()`](docs/recipes/when-to-use-plan.md)
- [Browser testing](docs/recipes/browser-testing.md)
Expand Down
140 changes: 71 additions & 69 deletions test/watcher.js
Expand Up @@ -508,91 +508,93 @@ group('chokidar is installed', function (beforeEach, test, group) {
});
});

test('reruns initial tests when "rs" is entered on stdin', function (t) {
t.plan(2);
api.run.returns(Promise.resolve());
start().observeStdin(stdin);
["r", "rs"].forEach(function (input) {
test('reruns initial tests when "' + input + '" is entered on stdin', function (t) {
t.plan(2);
api.run.returns(Promise.resolve());
start().observeStdin(stdin);

stdin.write('rs\n');
return delay().then(function () {
t.ok(api.run.calledTwice);
stdin.write(input + '\n');
return delay().then(function () {
t.ok(api.run.calledTwice);

stdin.write('\trs \n');
return delay();
}).then(function () {
t.ok(api.run.calledThrice);
stdin.write('\t' + input + ' \n');
return delay();
}).then(function () {
t.ok(api.run.calledThrice);
});
});
});

test('entering "rs" on stdin cancels any debouncing', function (t) {
t.plan(7);
api.run.returns(Promise.resolve());
start().observeStdin(stdin);
test('entering "' + input + '" on stdin cancels any debouncing', function (t) {
t.plan(7);
api.run.returns(Promise.resolve());
start().observeStdin(stdin);

var before = clock.now;
var done;
api.run.returns(new Promise(function (resolve) {
done = resolve;
}));
var before = clock.now;
var done;
api.run.returns(new Promise(function (resolve) {
done = resolve;
}));

add();
stdin.write('rs\n');
return delay().then(function () {
// Processing "rs" caused a new run.
t.ok(api.run.calledTwice);
add();
stdin.write(input + '\n');
return delay().then(function () {
// Processing "rs" caused a new run.
t.ok(api.run.calledTwice);

// Try to advance the clock. This is *after* "rs" was processed. The
// debounce timeout should have been canceled, so the clock can't have
// advanced.
clock.next();
t.is(before, clock.now);
// Try to advance the clock. This is *after* input was processed. The
// debounce timeout should have been canceled, so the clock can't have
// advanced.
clock.next();
t.is(before, clock.now);

add();
// Advance clock *before* "rs" is received. Note that the previous run
// hasn't finished yet.
clock.next();
stdin.write('rs\n');
add();
// Advance clock *before* input is received. Note that the previous run
// hasn't finished yet.
clock.next();
stdin.write(input + '\n');

return delay();
}).then(function () {
// No new runs yet.
t.ok(api.run.calledTwice);
// Though the clock has advanced.
t.is(clock.now - before, 10);
before = clock.now;
return delay();
}).then(function () {
// No new runs yet.
t.ok(api.run.calledTwice);
// Though the clock has advanced.
t.is(clock.now - before, 10);
before = clock.now;

var previous = done;
api.run.returns(new Promise(function (resolve) {
done = resolve;
}));
var previous = done;
api.run.returns(new Promise(function (resolve) {
done = resolve;
}));

// Finish the previous run.
previous();
// Finish the previous run.
previous();

return delay();
}).then(function () {
// There's only one new run.
t.ok(api.run.calledThrice);
return delay();
}).then(function () {
// There's only one new run.
t.ok(api.run.calledThrice);

stdin.write('rs\n');
return delay();
}).then(function () {
add();
stdin.write(input + '\n');
return delay();
}).then(function () {
add();

// Finish the previous run. This should cause a new run due to the "rs"
// input.
done();
// Finish the previous run. This should cause a new run due to the
// input.
done();

return delay();
}).then(function () {
// Again there's only one new run.
t.is(api.run.callCount, 4);
return delay();
}).then(function () {
// Again there's only one new run.
t.is(api.run.callCount, 4);

// Try to advance the clock. This is *after* "rs" was processed. The
// debounce timeout should have been canceled, so the clock can't have
// advanced.
clock.next();
t.is(before, clock.now);
// Try to advance the clock. This is *after* input was processed. The
// debounce timeout should have been canceled, so the clock can't have
// advanced.
clock.next();
t.is(before, clock.now);
});
});
});

Expand Down

0 comments on commit cac933e

Please sign in to comment.