ava 4.0.0-alpha.1 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/{eslint-plugin-helper.js → entrypoints/eslint-plugin-helper.cjs} +20 -7
  3. package/entrypoints/main.cjs +2 -0
  4. package/entrypoints/main.mjs +1 -0
  5. package/entrypoints/plugin.cjs +2 -0
  6. package/entrypoints/plugin.mjs +4 -0
  7. package/index.d.ts +6 -709
  8. package/lib/api.js +95 -46
  9. package/lib/assert.js +122 -173
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +105 -97
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +30 -31
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -4
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +16 -26
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +42 -83
  20. package/lib/glob-helpers.cjs +140 -0
  21. package/lib/globs.js +136 -163
  22. package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
  23. package/lib/is-ci.js +4 -2
  24. package/lib/like-selector.js +7 -13
  25. package/lib/line-numbers.js +10 -17
  26. package/lib/load-config.js +62 -56
  27. package/lib/module-types.js +3 -3
  28. package/lib/node-arguments.js +4 -5
  29. package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
  30. package/lib/parse-test-args.js +22 -11
  31. package/lib/pkg.cjs +2 -0
  32. package/lib/plugin-support/shared-worker-loader.js +45 -48
  33. package/lib/plugin-support/shared-workers.js +24 -43
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -11
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +115 -350
  38. package/lib/reporters/format-serialized-error.js +7 -18
  39. package/lib/reporters/improper-usage-messages.js +8 -9
  40. package/lib/reporters/prefix-title.js +17 -15
  41. package/lib/reporters/tap.js +15 -16
  42. package/lib/run-status.js +25 -23
  43. package/lib/runner.js +138 -127
  44. package/lib/scheduler.js +42 -36
  45. package/lib/serialize-error.js +34 -34
  46. package/lib/snapshot-manager.js +83 -76
  47. package/lib/test.js +114 -195
  48. package/lib/watcher.js +65 -40
  49. package/lib/worker/base.js +48 -99
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +22 -22
  52. package/lib/worker/guard-environment.cjs +19 -0
  53. package/lib/worker/line-numbers.js +57 -19
  54. package/lib/worker/main.cjs +12 -0
  55. package/lib/worker/{options.js → options.cjs} +0 -0
  56. package/lib/worker/{plugin.js → plugin.cjs} +31 -16
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/{utils.js → utils.cjs} +1 -1
  59. package/package.json +60 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -12
  62. package/types/assertions.d.ts +327 -0
  63. package/types/subscribable.ts +6 -0
  64. package/types/test-fn.d.ts +231 -0
  65. package/types/try-fn.d.ts +58 -0
  66. package/cli.js +0 -11
  67. package/index.js +0 -8
  68. package/lib/worker/channel.js +0 -218
  69. package/lib/worker/main.js +0 -20
  70. package/plugin.js +0 -9
package/lib/test.js CHANGED
@@ -1,11 +1,11 @@
1
- 'use strict';
2
- const concordance = require('concordance');
3
- const isPromise = require('is-promise');
4
- const plur = require('plur');
5
- const assert = require('./assert');
6
- const nowAndTimers = require('./now-and-timers');
7
- const parseTestArgs = require('./parse-test-args');
8
- const concordanceOptions = require('./concordance-options').default;
1
+ import concordance from 'concordance';
2
+ import isPromise from 'is-promise';
3
+ import plur from 'plur';
4
+
5
+ import {AssertionError, Assertions, checkAssertionMessage} from './assert.js';
6
+ import concordanceOptions from './concordance-options.js';
7
+ import nowAndTimers from './now-and-timers.cjs';
8
+ import parseTestArgs from './parse-test-args.js';
9
9
 
10
10
  function formatErrorValue(label, error) {
11
11
  const formatted = concordance.format(error, concordanceOptions);
@@ -21,7 +21,7 @@ const captureSavedError = () => {
21
21
  };
22
22
 
23
23
  const testMap = new WeakMap();
24
- class ExecutionContext extends assert.Assertions {
24
+ class ExecutionContext extends Assertions {
25
25
  constructor(test) {
26
26
  super({
27
27
  pass: () => {
@@ -36,12 +36,9 @@ class ExecutionContext extends assert.Assertions {
36
36
  skip: () => {
37
37
  test.countPassedAssertion();
38
38
  },
39
- compareWithSnapshot: options => {
40
- return test.compareWithSnapshot(options);
41
- },
42
- powerAssert: test.powerAssert,
39
+ compareWithSnapshot: options => test.compareWithSnapshot(options),
43
40
  experiments: test.experiments,
44
- disableSnapshots: test.isHook === true
41
+ disableSnapshots: test.isHook === true,
45
42
  });
46
43
  testMap.set(this, test);
47
44
 
@@ -50,11 +47,9 @@ class ExecutionContext extends assert.Assertions {
50
47
  };
51
48
 
52
49
  this.log = (...inputArgs) => {
53
- const args = inputArgs.map(value => {
54
- return typeof value === 'string' ?
55
- value :
56
- concordance.format(value, concordanceOptions);
57
- });
50
+ const args = inputArgs.map(value => typeof value === 'string'
51
+ ? value
52
+ : concordance.format(value, concordanceOptions));
58
53
  if (args.length > 0) {
59
54
  test.addLog(args.join(' '));
60
55
  }
@@ -81,90 +76,86 @@ class ExecutionContext extends assert.Assertions {
81
76
  throw error;
82
77
  }
83
78
 
84
- const {args, buildTitle, implementations, receivedImplementationArray} = parseTestArgs(attemptArgs);
79
+ const {args, implementation, title} = parseTestArgs(attemptArgs);
85
80
 
86
- if (implementations.length === 0) {
81
+ if (!implementation) {
87
82
  throw new TypeError('Expected an implementation.');
88
83
  }
89
84
 
90
- const attemptPromises = implementations.map((implementation, index) => {
91
- let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
85
+ if (Array.isArray(implementation)) {
86
+ throw new TypeError('AVA 4 no longer supports t.try() with multiple implementations.');
87
+ }
92
88
 
93
- if (!isSet || isEmpty) {
94
- title = `${test.title} ─ attempt ${test.attemptCount + 1 + index}`;
95
- } else if (isValid) {
96
- title = `${test.title} ─ ${title}`;
97
- } else {
98
- throw new TypeError('`t.try()` titles must be strings'); // Throw synchronously!
99
- }
89
+ let attemptTitle;
90
+ if (!title.isSet || title.isEmpty) {
91
+ attemptTitle = `${test.title} attempt ${test.attemptCount + 1}`;
92
+ } else if (title.isValid) {
93
+ attemptTitle = `${test.title} ${title.value}`;
94
+ } else {
95
+ throw new TypeError('`t.try()` titles must be strings');
96
+ }
100
97
 
101
- if (!test.registerUniqueTitle(title)) {
102
- throw new Error(`Duplicate test title: ${title}`);
103
- }
98
+ if (!test.registerUniqueTitle(attemptTitle)) {
99
+ throw new Error(`Duplicate test title: ${attemptTitle}`);
100
+ }
101
+
102
+ let committed = false;
103
+ let discarded = false;
104
104
 
105
- return {implementation, title};
106
- }).map(async ({implementation, title}) => {
107
- let committed = false;
108
- let discarded = false;
109
-
110
- const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(title, t => implementation(t, ...args));
111
-
112
- return {
113
- errors,
114
- logs: [...logs], // Don't allow modification of logs.
115
- passed,
116
- title,
117
- commit: ({retainLogs = true} = {}) => {
118
- if (committed) {
119
- return;
120
- }
121
-
122
- if (discarded) {
123
- test.saveFirstError(new Error('Can’t commit a result that was previously discarded'));
124
- return;
125
- }
126
-
127
- committed = true;
128
- test.finishAttempt({
129
- assertCount,
130
- commit: true,
131
- deferredSnapshotRecordings,
132
- errors,
133
- logs,
134
- passed,
135
- retainLogs,
136
- snapshotCount,
137
- startingSnapshotCount
138
- });
139
- },
140
- discard: ({retainLogs = false} = {}) => {
141
- if (committed) {
142
- test.saveFirstError(new Error('Can’t discard a result that was previously committed'));
143
- return;
144
- }
145
-
146
- if (discarded) {
147
- return;
148
- }
149
-
150
- discarded = true;
151
- test.finishAttempt({
152
- assertCount: 0,
153
- commit: false,
154
- deferredSnapshotRecordings,
155
- errors,
156
- logs,
157
- passed,
158
- retainLogs,
159
- snapshotCount,
160
- startingSnapshotCount
161
- });
105
+ const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(attemptTitle, t => implementation(t, ...args));
106
+
107
+ return {
108
+ errors,
109
+ logs: [...logs], // Don't allow modification of logs.
110
+ passed,
111
+ title: attemptTitle,
112
+ commit: ({retainLogs = true} = {}) => {
113
+ if (committed) {
114
+ return;
115
+ }
116
+
117
+ if (discarded) {
118
+ test.saveFirstError(new Error('Can’t commit a result that was previously discarded'));
119
+ return;
120
+ }
121
+
122
+ committed = true;
123
+ test.finishAttempt({
124
+ assertCount,
125
+ commit: true,
126
+ deferredSnapshotRecordings,
127
+ errors,
128
+ logs,
129
+ passed,
130
+ retainLogs,
131
+ snapshotCount,
132
+ startingSnapshotCount,
133
+ });
134
+ },
135
+ discard: ({retainLogs = false} = {}) => {
136
+ if (committed) {
137
+ test.saveFirstError(new Error('Can’t discard a result that was previously committed'));
138
+ return;
139
+ }
140
+
141
+ if (discarded) {
142
+ return;
162
143
  }
163
- };
164
- });
165
144
 
166
- const results = await Promise.all(attemptPromises);
167
- return receivedImplementationArray ? results : results[0];
145
+ discarded = true;
146
+ test.finishAttempt({
147
+ assertCount: 0,
148
+ commit: false,
149
+ deferredSnapshotRecordings,
150
+ errors,
151
+ logs,
152
+ passed,
153
+ retainLogs,
154
+ snapshotCount,
155
+ startingSnapshotCount,
156
+ });
157
+ },
158
+ };
168
159
  };
169
160
  }
170
161
 
@@ -184,17 +175,9 @@ class ExecutionContext extends assert.Assertions {
184
175
  const test = testMap.get(this);
185
176
  return test.isHook ? test.testPassed : !test.assertError;
186
177
  }
187
-
188
- _throwsArgStart(assertion, file, line) {
189
- testMap.get(this).trackThrows({assertion, file, line});
190
- }
191
-
192
- _throwsArgEnd() {
193
- testMap.get(this).trackThrows(null);
194
- }
195
178
  }
196
179
 
197
- class Test {
180
+ export default class Test {
198
181
  constructor(options) {
199
182
  this.contextRef = options.contextRef;
200
183
  this.experiments = options.experiments || {};
@@ -202,12 +185,12 @@ class Test {
202
185
  this.fn = options.fn;
203
186
  this.isHook = options.isHook === true;
204
187
  this.metadata = options.metadata;
205
- this.powerAssert = options.powerAssert;
206
188
  this.title = options.title;
207
189
  this.testPassed = options.testPassed;
208
190
  this.registerUniqueTitle = options.registerUniqueTitle;
209
191
  this.logs = [];
210
192
  this.teardowns = [];
193
+ this.notifyTimeoutUpdate = options.notifyTimeoutUpdate;
211
194
 
212
195
  const {snapshotBelongsTo = this.title, nextSnapshotIndex = 0} = options;
213
196
  this.snapshotBelongsTo = snapshotBelongsTo;
@@ -229,7 +212,7 @@ class Test {
229
212
  expected,
230
213
  index,
231
214
  label,
232
- taskIndex: this.metadata.taskIndex
215
+ taskIndex: this.metadata.taskIndex,
233
216
  });
234
217
  if (record) {
235
218
  this.deferredSnapshotRecordings.push(record);
@@ -244,7 +227,7 @@ class Test {
244
227
  belongsTo: snapshotBelongsTo,
245
228
  index: this.nextSnapshotIndex,
246
229
  deferRecording,
247
- taskIndex: this.metadata.taskIndex
230
+ taskIndex: this.metadata.taskIndex,
248
231
  });
249
232
  if (record) {
250
233
  this.deferredSnapshotRecordings.push(record);
@@ -272,7 +255,7 @@ class Test {
272
255
  contextRef: contextRef.copy(),
273
256
  snapshotBelongsTo,
274
257
  nextSnapshotIndex,
275
- title
258
+ title,
276
259
  });
277
260
 
278
261
  const {deferredSnapshotRecordings, error, logs, passed, assertCount, snapshotCount} = await attempt.run();
@@ -285,13 +268,11 @@ class Test {
285
268
  this.attemptCount = 0;
286
269
  this.calledEnd = false;
287
270
  this.duration = null;
288
- this.finishDueToAttributedError = null;
289
271
  this.finishDueToInactivity = null;
290
272
  this.finishDueToTimeout = null;
291
273
  this.finishing = false;
292
274
  this.pendingAssertionCount = 0;
293
275
  this.pendingAttemptCount = 0;
294
- this.pendingThrowsAssertion = null;
295
276
  this.planCount = null;
296
277
  this.startedAt = 0;
297
278
  this.timeoutMs = 0;
@@ -334,7 +315,7 @@ class Test {
334
315
 
335
316
  promise
336
317
  .catch(error => this.saveFirstError(error))
337
- .then(() => { // eslint-disable-line promise/prefer-await-to-then
318
+ .then(() => {
338
319
  this.pendingAssertionCount--;
339
320
  this.refreshTimeout();
340
321
  });
@@ -411,7 +392,7 @@ class Test {
411
392
  }
412
393
 
413
394
  timeout(ms, message) {
414
- const result = assert.checkAssertionMessage('timeout', message);
395
+ const result = checkAssertionMessage('timeout', message);
415
396
  if (result !== true) {
416
397
  this.saveFirstError(result);
417
398
  // Allow the timeout to be set even when the message is invalid.
@@ -431,6 +412,8 @@ class Test {
431
412
  this.finishDueToTimeout();
432
413
  }
433
414
  }, ms);
415
+
416
+ this.notifyTimeoutUpdate(this.timeoutMs);
434
417
  }
435
418
 
436
419
  refreshTimeout() {
@@ -482,11 +465,11 @@ class Test {
482
465
 
483
466
  verifyPlan() {
484
467
  if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) {
485
- this.saveFirstError(new assert.AssertionError({
468
+ this.saveFirstError(new AssertionError({
486
469
  assertion: 'plan',
487
470
  message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`,
488
471
  operator: '===',
489
- savedError: this.planError
472
+ savedError: this.planError,
490
473
  }));
491
474
  }
492
475
  }
@@ -517,70 +500,16 @@ class Test {
517
500
  }
518
501
  }
519
502
 
520
- trackThrows(pending) {
521
- this.pendingThrowsAssertion = pending;
522
- }
523
-
524
- detectImproperThrows(error) {
525
- if (!this.pendingThrowsAssertion) {
526
- return false;
527
- }
528
-
529
- const pending = this.pendingThrowsAssertion;
530
- this.pendingThrowsAssertion = null;
531
-
532
- const values = [];
533
- if (error) {
534
- values.push(formatErrorValue(`The following error was thrown, possibly before \`t.${pending.assertion}()\` could be called:`, error));
535
- }
536
-
537
- this.saveFirstError(new assert.AssertionError({
538
- assertion: pending.assertion,
539
- fixedSource: {file: pending.file, line: pending.line},
540
- improperUsage: true,
541
- message: `Improper usage of \`t.${pending.assertion}()\` detected`,
542
- savedError: error instanceof Error && error,
543
- values
544
- }));
545
- return true;
546
- }
547
-
548
- waitForPendingThrowsAssertion() {
549
- return new Promise(resolve => {
550
- this.finishDueToAttributedError = () => {
551
- resolve(this.finish());
552
- };
553
-
554
- this.finishDueToInactivity = () => {
555
- this.detectImproperThrows();
556
- resolve(this.finish());
557
- };
558
-
559
- // Wait up to a second to see if an error can be attributed to the
560
- // pending assertion.
561
- nowAndTimers.setTimeout(() => this.finishDueToInactivity(), 1000).unref();
562
- });
563
- }
564
-
565
- attributeLeakedError(error) {
566
- if (!this.detectImproperThrows(error)) {
567
- return false;
568
- }
569
-
570
- this.finishDueToAttributedError();
571
- return true;
572
- }
573
-
574
503
  callFn() {
575
504
  try {
576
505
  return {
577
506
  ok: true,
578
- retval: this.fn.call(null, this.createExecutionContext())
507
+ retval: this.fn.call(null, this.createExecutionContext()),
579
508
  };
580
509
  } catch (error) {
581
510
  return {
582
511
  ok: false,
583
- error
512
+ error,
584
513
  };
585
514
  }
586
515
  }
@@ -590,13 +519,11 @@ class Test {
590
519
 
591
520
  const result = this.callFn();
592
521
  if (!result.ok) {
593
- if (!this.detectImproperThrows(result.error)) {
594
- this.saveFirstError(new assert.AssertionError({
595
- message: 'Error thrown in test',
596
- savedError: result.error instanceof Error && result.error,
597
- values: [formatErrorValue('Error thrown in test:', result.error)]
598
- }));
599
- }
522
+ this.saveFirstError(new AssertionError({
523
+ message: 'Error thrown in test',
524
+ savedError: result.error instanceof Error && result.error,
525
+ values: [formatErrorValue('Error thrown in test:', result.error)],
526
+ }));
600
527
 
601
528
  return this.finish();
602
529
  }
@@ -609,7 +536,7 @@ class Test {
609
536
  promise = new Promise((resolve, reject) => {
610
537
  result.retval.subscribe({
611
538
  error: reject,
612
- complete: () => resolve()
539
+ complete: () => resolve(),
613
540
  });
614
541
  });
615
542
  } else if (returnedPromise) {
@@ -628,24 +555,22 @@ class Test {
628
555
  };
629
556
 
630
557
  this.finishDueToInactivity = () => {
631
- const error = returnedObservable ?
632
- new Error('Observable returned by test never completed') :
633
- new Error('Promise returned by test never resolved');
558
+ const error = returnedObservable
559
+ ? new Error('Observable returned by test never completed')
560
+ : new Error('Promise returned by test never resolved');
634
561
  this.saveFirstError(error);
635
562
  resolve(this.finish());
636
563
  };
637
564
 
638
565
  promise
639
566
  .catch(error => {
640
- if (!this.detectImproperThrows(error)) {
641
- this.saveFirstError(new assert.AssertionError({
642
- message: 'Rejected promise returned by test',
643
- savedError: error instanceof Error && error,
644
- values: [formatErrorValue('Rejected promise returned by test. Reason:', error)]
645
- }));
646
- }
567
+ this.saveFirstError(new AssertionError({
568
+ message: 'Rejected promise returned by test',
569
+ savedError: error instanceof Error && error,
570
+ values: [formatErrorValue('Rejected promise returned by test. Reason:', error)],
571
+ }));
647
572
  })
648
- .then(() => resolve(this.finish())); // eslint-disable-line promise/prefer-await-to-then
573
+ .then(() => resolve(this.finish()));
649
574
  });
650
575
  }
651
576
 
@@ -655,10 +580,6 @@ class Test {
655
580
  async finish() {
656
581
  this.finishing = true;
657
582
 
658
- if (!this.assertError && this.pendingThrowsAssertion) {
659
- return this.waitForPendingThrowsAssertion();
660
- }
661
-
662
583
  this.clearTimeout();
663
584
  this.verifyPlan();
664
585
  this.verifyAssertions();
@@ -684,9 +605,7 @@ class Test {
684
605
  passed,
685
606
  snapshotCount: this.snapshotCount,
686
607
  assertCount: this.assertCount,
687
- title: this.title
608
+ title: this.title,
688
609
  };
689
610
  }
690
611
  }
691
-
692
- module.exports = Test;
package/lib/watcher.js CHANGED
@@ -1,12 +1,20 @@
1
- 'use strict';
2
- const nodePath = require('path');
3
- const debug = require('debug')('ava:watcher');
4
- const chokidar = require('chokidar');
5
- const diff = require('lodash/difference');
6
- const flatten = require('lodash/flatten');
7
- const chalk = require('./chalk').get();
8
- const {applyTestFileFilter, classify, getChokidarIgnorePatterns} = require('./globs');
9
- const {levels: providerLevels} = require('./provider-manager');
1
+ import nodePath from 'node:path';
2
+
3
+ import chokidar_ from 'chokidar';
4
+ import createDebug from 'debug';
5
+
6
+ import {chalk} from './chalk.js';
7
+ import {applyTestFileFilter, classify, getChokidarIgnorePatterns} from './globs.js';
8
+
9
+ let chokidar = chokidar_;
10
+ export function _testOnlyReplaceChokidar(replacement) {
11
+ chokidar = replacement;
12
+ }
13
+
14
+ let debug = createDebug('ava:watcher');
15
+ export function _testOnlyReplaceDebug(replacement) {
16
+ debug = replacement('ava:watcher');
17
+ }
10
18
 
11
19
  function rethrowAsync(error) {
12
20
  // Don't swallow exceptions. Note that any
@@ -77,7 +85,7 @@ class TestDependency {
77
85
  }
78
86
  }
79
87
 
80
- class Watcher {
88
+ export default class Watcher {
81
89
  constructor({api, filter = [], globs, projectDir, providers, reporter}) {
82
90
  this.debouncer = new Debouncer(this);
83
91
 
@@ -88,7 +96,7 @@ class Watcher {
88
96
 
89
97
  const patternFilters = filter.map(({pattern}) => pattern);
90
98
 
91
- this.providers = providers.filter(({level}) => level >= providerLevels.pathRewrites);
99
+ this.providers = providers;
92
100
  this.run = (specificFiles = [], updateSnapshots = false) => {
93
101
  const clearLogOnNextRun = this.clearLogOnNextRun && this.runVector > 0;
94
102
  if (this.runVector > 0) {
@@ -104,12 +112,18 @@ class Watcher {
104
112
  if (runOnlyExclusive) {
105
113
  // The test files that previously contained exclusive tests are always
106
114
  // run, together with the remaining specific files.
107
- const remainingFiles = diff(specificFiles, exclusiveFiles);
115
+ const remainingFiles = specificFiles.filter(file => !exclusiveFiles.includes(file));
108
116
  specificFiles = [...this.filesWithExclusiveTests, ...remainingFiles];
109
117
  }
110
118
 
111
119
  if (filter.length > 0) {
112
- specificFiles = applyTestFileFilter({cwd: projectDir, filter: patternFilters, testFiles: specificFiles});
120
+ specificFiles = applyTestFileFilter({
121
+ cwd: projectDir,
122
+ expandDirectories: false,
123
+ filter: patternFilters,
124
+ testFiles: specificFiles,
125
+ treatFilterPatternsAsFiles: false,
126
+ });
113
127
  }
114
128
 
115
129
  this.pruneFailures(specificFiles);
@@ -125,21 +139,21 @@ class Watcher {
125
139
  previousFailures: this.sumPreviousFailures(this.runVector),
126
140
  runOnlyExclusive,
127
141
  runVector: this.runVector,
128
- updateSnapshots: updateSnapshots === true
129
- }
142
+ updateSnapshots: updateSnapshots === true,
143
+ },
130
144
  })
131
- .then(runStatus => { // eslint-disable-line promise/prefer-await-to-then
145
+ .then(runStatus => {
132
146
  reporter.endRun();
133
147
  reporter.lineWriter.writeLine(END_MESSAGE);
134
148
 
135
149
  if (this.clearLogOnNextRun && (
136
- runStatus.stats.failedHooks > 0 ||
137
- runStatus.stats.failedTests > 0 ||
138
- runStatus.stats.failedWorkers > 0 ||
139
- runStatus.stats.internalErrors > 0 ||
140
- runStatus.stats.timeouts > 0 ||
141
- runStatus.stats.uncaughtExceptions > 0 ||
142
- runStatus.stats.unhandledRejections > 0
150
+ runStatus.stats.failedHooks > 0
151
+ || runStatus.stats.failedTests > 0
152
+ || runStatus.stats.failedWorkers > 0
153
+ || runStatus.stats.internalErrors > 0
154
+ || runStatus.stats.timeouts > 0
155
+ || runStatus.stats.uncaughtExceptions > 0
156
+ || runStatus.stats.unhandledRejections > 0
143
157
  )) {
144
158
  this.clearLogOnNextRun = false;
145
159
  }
@@ -150,6 +164,7 @@ class Watcher {
150
164
  this.testDependencies = [];
151
165
  this.trackTestDependencies(api);
152
166
 
167
+ this.temporaryFiles = new Set();
153
168
  this.touchedFiles = new Set();
154
169
  this.trackTouchedFiles(api);
155
170
 
@@ -168,7 +183,7 @@ class Watcher {
168
183
  chokidar.watch(['**/*'], {
169
184
  cwd: this.globs.cwd,
170
185
  ignored: getChokidarIgnorePatterns(this.globs),
171
- ignoreInitial: true
186
+ ignoreInitial: true,
172
187
  }).on('all', (event, path) => {
173
188
  if (event === 'add' || event === 'change' || event === 'unlink') {
174
189
  debug('Detected %s of %s', event, path);
@@ -231,9 +246,13 @@ class Watcher {
231
246
  return;
232
247
  }
233
248
 
234
- for (const file of evt.files) {
249
+ for (const file of evt.files.changedFiles) {
235
250
  this.touchedFiles.add(file);
236
251
  }
252
+
253
+ for (const file of evt.files.temporaryFiles) {
254
+ this.temporaryFiles.add(file);
255
+ }
237
256
  });
238
257
  });
239
258
  }
@@ -307,7 +326,7 @@ class Watcher {
307
326
  this.filesWithFailures.push({
308
327
  file,
309
328
  vector,
310
- count: 1
329
+ count: 1,
311
330
  });
312
331
  }
313
332
  }
@@ -379,6 +398,14 @@ class Watcher {
379
398
  return false;
380
399
  }
381
400
 
401
+ // Unlike touched files, temporary files are never cleared. We may see
402
+ // adds and unlinks detected separately, so we track the temporary files
403
+ // as long as AVA is running.
404
+ if (this.temporaryFiles.has(path)) {
405
+ debug('Ignoring known temporary file %s', path);
406
+ return false;
407
+ }
408
+
382
409
  return true;
383
410
  });
384
411
 
@@ -394,21 +421,23 @@ class Watcher {
394
421
  }
395
422
 
396
423
  const dirtyHelpersAndSources = [];
397
- const dirtyTests = [];
424
+ const addedOrChangedTests = [];
425
+ const unlinkedTests = [];
398
426
  for (const filePath of dirtyPaths) {
399
427
  const {isIgnoredByWatcher, isTest} = classify(filePath, this.globs);
400
428
  if (!isIgnoredByWatcher) {
401
429
  if (isTest) {
402
- dirtyTests.push(filePath);
430
+ if (dirtyStates[filePath] === 'unlink') {
431
+ unlinkedTests.push(filePath);
432
+ } else {
433
+ addedOrChangedTests.push(filePath);
434
+ }
403
435
  } else {
404
436
  dirtyHelpersAndSources.push(filePath);
405
437
  }
406
438
  }
407
439
  }
408
440
 
409
- const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink');
410
- const unlinkedTests = diff(dirtyTests, addedOrChangedTests);
411
-
412
441
  this.cleanUnlinkedTests(unlinkedTests);
413
442
 
414
443
  // No need to rerun tests if the only change is that tests were deleted
@@ -423,12 +452,10 @@ class Watcher {
423
452
  }
424
453
 
425
454
  // Try to find tests that depend on the changed source files
426
- const testsByHelpersOrSource = dirtyHelpersAndSources.map(path => {
427
- return this.testDependencies.filter(dep => dep.contains(path)).map(dep => {
428
- debug('%s is a dependency of %s', path, dep.file);
429
- return dep.file;
430
- });
431
- }, this).filter(tests => tests.length > 0);
455
+ const testsByHelpersOrSource = dirtyHelpersAndSources.map(path => this.testDependencies.filter(dep => dep.contains(path)).map(dep => {
456
+ debug('%s is a dependency of %s', path, dep.file);
457
+ return dep.file;
458
+ })).filter(tests => tests.length > 0);
432
459
 
433
460
  // Rerun all tests if source files were changed that could not be traced to
434
461
  // specific tests
@@ -440,8 +467,6 @@ class Watcher {
440
467
  }
441
468
 
442
469
  // Run all affected tests
443
- this.run([...new Set([...addedOrChangedTests, ...flatten(testsByHelpersOrSource)])]);
470
+ this.run([...new Set([addedOrChangedTests, testsByHelpersOrSource].flat(2))]);
444
471
  }
445
472
  }
446
-
447
- module.exports = Watcher;