ava 5.3.1 → 6.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 (40) hide show
  1. package/entrypoints/internal.d.mts +7 -0
  2. package/lib/api-event-iterator.js +12 -0
  3. package/lib/api.js +14 -23
  4. package/lib/assert.js +289 -444
  5. package/lib/cli.js +95 -61
  6. package/lib/code-excerpt.js +2 -2
  7. package/lib/eslint-plugin-helper-worker.js +3 -3
  8. package/lib/fork.js +3 -13
  9. package/lib/glob-helpers.cjs +1 -9
  10. package/lib/globs.js +7 -3
  11. package/lib/line-numbers.js +1 -1
  12. package/lib/load-config.js +3 -3
  13. package/lib/parse-test-args.js +3 -3
  14. package/lib/plugin-support/shared-workers.js +4 -4
  15. package/lib/provider-manager.js +11 -13
  16. package/lib/reporters/beautify-stack.js +0 -1
  17. package/lib/reporters/default.js +92 -45
  18. package/lib/reporters/format-serialized-error.js +6 -6
  19. package/lib/reporters/improper-usage-messages.js +5 -5
  20. package/lib/reporters/tap.js +30 -30
  21. package/lib/run-status.js +9 -0
  22. package/lib/runner.js +7 -7
  23. package/lib/scheduler.js +14 -1
  24. package/lib/serialize-error.js +44 -116
  25. package/lib/slash.cjs +1 -1
  26. package/lib/snapshot-manager.js +14 -8
  27. package/lib/test.js +90 -81
  28. package/lib/watcher.js +494 -365
  29. package/lib/worker/base.js +90 -51
  30. package/lib/worker/channel.cjs +9 -53
  31. package/license +1 -1
  32. package/package.json +36 -42
  33. package/readme.md +6 -12
  34. package/types/assertions.d.cts +107 -49
  35. package/types/shared-worker.d.cts +0 -2
  36. package/types/state-change-events.d.cts +143 -0
  37. package/types/test-fn.d.cts +10 -5
  38. package/lib/worker/dependency-tracker.js +0 -48
  39. /package/entrypoints/{main.d.ts → main.d.mts} +0 -0
  40. /package/entrypoints/{plugin.d.ts → plugin.d.mts} +0 -0
@@ -1,9 +1,12 @@
1
+ import {mkdir} from 'node:fs/promises';
1
2
  import {createRequire} from 'node:module';
3
+ import {join as joinPath, resolve as resolvePath} from 'node:path';
2
4
  import process from 'node:process';
3
5
  import {pathToFileURL} from 'node:url';
4
6
  import {workerData} from 'node:worker_threads';
5
7
 
6
8
  import setUpCurrentlyUnhandled from 'currently-unhandled';
9
+ import writeFileAtomic from 'write-file-atomic';
7
10
 
8
11
  import {set as setChalk} from '../chalk.js';
9
12
  import nowAndTimers from '../now-and-timers.cjs';
@@ -12,7 +15,6 @@ import Runner from '../runner.js';
12
15
  import serializeError from '../serialize-error.js';
13
16
 
14
17
  import channel from './channel.cjs';
15
- import dependencyTracking from './dependency-tracker.js';
16
18
  import lineNumberSelection from './line-numbers.js';
17
19
  import {set as setOptions} from './options.cjs';
18
20
  import {flags, refs, sharedWorkerTeardowns} from './state.cjs';
@@ -21,37 +23,26 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
21
23
  const currentlyUnhandled = setUpCurrentlyUnhandled();
22
24
  let runner;
23
25
 
26
+ let forcingExit = false;
27
+
28
+ const forceExit = () => {
29
+ forcingExit = true;
30
+ process.exit(1);
31
+ };
32
+
24
33
  // Override process.exit with an undetectable replacement
25
34
  // to report when it is called from a test (which it should never be).
26
- const {apply} = Reflect;
27
- const realExit = process.exit;
28
-
29
- async function exit(code, forceSync = false) {
30
- dependencyTracking.flush();
31
- const flushing = channel.flush();
32
- if (!forceSync) {
33
- await flushing;
35
+ const handleProcessExit = (target, thisArg, args) => {
36
+ if (!forcingExit) {
37
+ const error = new Error('Unexpected process.exit()');
38
+ Error.captureStackTrace(error, handleProcessExit);
39
+ channel.send({type: 'process-exit', stack: error.stack});
34
40
  }
35
41
 
36
- apply(realExit, process, [code]);
37
- }
38
-
39
- const handleProcessExit = (fn, receiver, args) => {
40
- const error = new Error('Unexpected process.exit()');
41
- Error.captureStackTrace(error, handleProcessExit);
42
- const {stack} = serializeError('', true, error);
43
- channel.send({type: 'process-exit', stack});
44
-
45
- // Make sure to extract the code only from `args` rather than e.g. `Array.prototype`.
46
- // This level of paranoia is usually unwarranted, but we're dealing with test code
47
- // that has already colored outside the lines.
48
- const code = args.length > 0 ? args[0] : undefined;
49
-
50
- // Force a synchronous exit as guaranteed by the real process.exit().
51
- exit(code, true);
42
+ target.apply(thisArg, args);
52
43
  };
53
44
 
54
- process.exit = new Proxy(realExit, {
45
+ process.exit = new Proxy(process.exit, {
55
46
  apply: handleProcessExit,
56
47
  });
57
48
 
@@ -71,7 +62,7 @@ const run = async options => {
71
62
  lineNumbers: options.lineNumbers,
72
63
  });
73
64
  } catch (error) {
74
- channel.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)});
65
+ channel.send({type: 'line-number-selection-error', err: serializeError(error)});
75
66
  checkSelectedByLineNumbers = () => false;
76
67
  }
77
68
 
@@ -96,12 +87,12 @@ const run = async options => {
96
87
  runner.interrupt();
97
88
  });
98
89
 
99
- runner.on('dependency', dependencyTracking.track);
90
+ runner.on('accessed-snapshots', filename => channel.send({type: 'accessed-snapshots', filename}));
100
91
  runner.on('stateChange', state => channel.send(state));
101
92
 
102
93
  runner.on('error', error => {
103
- channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
104
- exit(1);
94
+ channel.send({type: 'internal-error', err: serializeError(error)});
95
+ forceExit();
105
96
  });
106
97
 
107
98
  runner.on('finish', async () => {
@@ -111,31 +102,36 @@ const run = async options => {
111
102
  channel.send({type: 'touched-files', files: touchedFiles});
112
103
  }
113
104
  } catch (error) {
114
- channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
115
- exit(1);
105
+ channel.send({type: 'internal-error', err: serializeError(error)});
106
+ forceExit();
116
107
  return;
117
108
  }
118
109
 
119
110
  try {
120
111
  await Promise.all(sharedWorkerTeardowns.map(fn => fn()));
121
112
  } catch (error) {
122
- channel.send({type: 'uncaught-exception', err: serializeError('Shared worker teardown error', false, error, runner.file)});
123
- exit(1);
113
+ channel.send({type: 'uncaught-exception', err: serializeError(error)});
114
+ forceExit();
124
115
  return;
125
116
  }
126
117
 
127
118
  nowAndTimers.setImmediate(() => {
128
- for (const rejection of currentlyUnhandled()) {
129
- channel.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)});
119
+ const unhandled = currentlyUnhandled();
120
+ if (unhandled.length === 0) {
121
+ return;
130
122
  }
131
123
 
132
- exit(0);
124
+ for (const rejection of unhandled) {
125
+ channel.send({type: 'unhandled-rejection', err: serializeError(rejection.reason, {testFile: options.file})});
126
+ }
127
+
128
+ forceExit();
133
129
  });
134
130
  });
135
131
 
136
132
  process.on('uncaughtException', error => {
137
- channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
138
- exit(1);
133
+ channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
134
+ forceExit();
139
135
  });
140
136
 
141
137
  // Store value to prevent required modules from modifying it.
@@ -149,9 +145,9 @@ const run = async options => {
149
145
  // require configuration the *compiled* helper will be loaded.
150
146
  const {projectDir, providerStates = []} = options;
151
147
  const providers = [];
152
- await Promise.all(providerStates.map(async ({type, state}) => {
148
+ await Promise.all(providerStates.map(async ({type, state, protocol}) => {
153
149
  if (type === 'typescript') {
154
- const provider = await providerManager.typescript(projectDir);
150
+ const provider = await providerManager.typescript(projectDir, {protocol});
155
151
  providers.push(provider.worker({extensionsToLoadAsModules, state}));
156
152
  }
157
153
  }));
@@ -174,16 +170,59 @@ const run = async options => {
174
170
  return require(ref);
175
171
  };
176
172
 
177
- try {
178
- for await (const ref of (options.require || [])) {
179
- await load(ref);
173
+ const loadRequiredModule = async ref => {
174
+ // If the provider can load the module, assume it's a local file and not a
175
+ // dependency.
176
+ for (const provider of providers) {
177
+ if (provider.canLoad(ref)) {
178
+ return provider.load(ref, {requireFn: require});
179
+ }
180
180
  }
181
181
 
182
- // Install dependency tracker after the require configuration has been evaluated
183
- // to make sure we also track dependencies with custom require hooks
184
- dependencyTracking.install(require.extensions, testPath);
182
+ // Try to load the module as a file, relative to the project directory.
183
+ // Match load() behavior.
184
+ const fullPath = resolvePath(projectDir, ref);
185
+ try {
186
+ for (const extension of extensionsToLoadAsModules) {
187
+ if (fullPath.endsWith(`.${extension}`)) {
188
+ return await import(pathToFileURL(fullPath)); // eslint-disable-line no-await-in-loop
189
+ }
190
+ }
191
+
192
+ return require(fullPath);
193
+ } catch (error) {
194
+ // If the module could not be found, assume it's not a file but a dependency.
195
+ if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
196
+ return importFromProject(ref);
197
+ }
198
+
199
+ throw error;
200
+ }
201
+ };
202
+
203
+ let importFromProject = async ref => {
204
+ // Do not use the cacheDir since it's not guaranteed to be inside node_modules.
205
+ const avaCacheDir = joinPath(projectDir, 'node_modules', '.cache', 'ava');
206
+ await mkdir(avaCacheDir, {recursive: true});
207
+ const stubPath = joinPath(avaCacheDir, 'import-from-project.mjs');
208
+ await writeFileAtomic(stubPath, 'export const importFromProject = ref => import(ref);\n');
209
+ ({importFromProject} = await import(pathToFileURL(stubPath)));
210
+ return importFromProject(ref);
211
+ };
212
+
213
+ try {
214
+ for await (const [ref, ...args] of (options.require ?? [])) {
215
+ const loadedModule = await loadRequiredModule(ref);
216
+
217
+ if (typeof loadedModule === 'function') { // CJS module
218
+ await loadedModule(...args);
219
+ } else if (typeof loadedModule.default === 'function') { // ES module, or exports.default from CJS
220
+ const {default: fn} = loadedModule;
221
+ await fn(...args);
222
+ }
223
+ }
185
224
 
186
- if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
225
+ if (options.debug?.port !== undefined && options.debug?.host !== undefined) {
187
226
  // If an inspector was active when the main process started, and is
188
227
  // already active for the worker process, do not open a new one.
189
228
  const {default: inspector} = await import('node:inspector');
@@ -205,11 +244,11 @@ const run = async options => {
205
244
  channel.unref();
206
245
  } else {
207
246
  channel.send({type: 'missing-ava-import'});
208
- exit(1);
247
+ forceExit();
209
248
  }
210
249
  } catch (error) {
211
- channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
212
- exit(1);
250
+ channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})});
251
+ forceExit();
213
252
  }
214
253
  };
215
254
 
@@ -7,42 +7,14 @@ const timers = require('../now-and-timers.cjs');
7
7
 
8
8
  const {isRunningInChildProcess, isRunningInThread} = require('./utils.cjs');
9
9
 
10
- let pEvent = async (emitter, event, options) => {
11
- // We need to import p-event, but import() is asynchronous. Buffer any events
12
- // emitted in the meantime. Don't handle errors.
13
- const buffer = [];
14
- const addToBuffer = (...args) => buffer.push(args);
15
- emitter.on(event, addToBuffer);
16
-
17
- try {
18
- ({pEvent} = await import('p-event'));
19
- } finally {
20
- emitter.off(event, addToBuffer);
21
- }
22
-
23
- if (buffer.length === 0) {
24
- return pEvent(emitter, event, options);
25
- }
26
-
27
- // Now replay buffered events.
28
- const replayEmitter = new events.EventEmitter();
29
- const promise = pEvent(replayEmitter, event, options);
30
- for (const args of buffer) {
31
- replayEmitter.emit(event, ...args);
32
- }
33
-
34
- const replay = (...args) => replayEmitter.emit(event, ...args);
35
- emitter.on(event, replay);
36
-
37
- try {
38
- return await promise;
39
- } finally {
40
- emitter.off(event, replay);
10
+ const selectAvaMessage = async (channel, type) => {
11
+ for await (const [message] of events.on(channel, 'message')) {
12
+ if (message.ava?.type === type) {
13
+ return message;
14
+ }
41
15
  }
42
16
  };
43
17
 
44
- const selectAvaMessage = type => message => message.ava && message.ava.type === type;
45
-
46
18
  class RefCounter {
47
19
  constructor() {
48
20
  this.count = 0;
@@ -133,27 +105,11 @@ if (isRunningInChildProcess) {
133
105
  // Node.js. In order to keep track, explicitly reference before attaching.
134
106
  handle.ref();
135
107
 
136
- exports.options = pEvent(handle.channel, 'message', selectAvaMessage('options')).then(message => message.ava.options); // eslint-disable-line unicorn/prefer-top-level-await
137
- exports.peerFailed = pEvent(handle.channel, 'message', selectAvaMessage('peer-failed'));
108
+ exports.options = selectAvaMessage(handle.channel, 'options').then(message => message.ava.options);
109
+ exports.peerFailed = selectAvaMessage(handle.channel, 'peer-failed');
138
110
  exports.send = handle.send.bind(handle);
139
111
  exports.unref = handle.unref.bind(handle);
140
112
 
141
- let pendingPings = Promise.resolve();
142
- async function flush() {
143
- handle.ref();
144
- const promise = pendingPings.then(async () => {
145
- handle.send({type: 'ping'});
146
- await pEvent(handle.channel, 'message', selectAvaMessage('pong'));
147
- if (promise === pendingPings) {
148
- handle.unref();
149
- }
150
- });
151
- pendingPings = promise;
152
- await promise;
153
- }
154
-
155
- exports.flush = flush;
156
-
157
113
  let channelCounter = 0;
158
114
  let messageCounter = 0;
159
115
 
@@ -202,7 +158,7 @@ function registerSharedWorker(filename, initialData) {
202
158
  // The attaching of message listeners will cause the port to be referenced by
203
159
  // Node.js. In order to keep track, explicitly reference before attaching.
204
160
  sharedWorkerHandle.ref();
205
- const ready = pEvent(ourPort, 'message', ({type}) => type === 'ready').then(() => {
161
+ const ready = selectAvaMessage(ourPort, 'ready').then(() => {
206
162
  currentlyAvailable = error === null;
207
163
  }).finally(() => {
208
164
  // Once ready, it's up to user code to subscribe to messages, which (see
@@ -214,7 +170,7 @@ function registerSharedWorker(filename, initialData) {
214
170
 
215
171
  // Errors are received over the test worker channel, not the message port
216
172
  // dedicated to the shared worker.
217
- pEvent(channelEmitter, 'shared-worker-error').then(() => {
173
+ events.once(channelEmitter, 'shared-worker-error').then(() => {
218
174
  unsubscribe();
219
175
  sharedWorkerHandle.forceUnref();
220
176
  error = new Error('The shared worker is no longer available');
package/license CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
3
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ava",
3
- "version": "5.3.1",
3
+ "version": "6.0.1",
4
4
  "description": "Node.js test runner that lets you develop with confidence.",
5
5
  "license": "MIT",
6
6
  "repository": "avajs/ava",
@@ -11,7 +11,7 @@
11
11
  "exports": {
12
12
  ".": {
13
13
  "import": {
14
- "types": "./entrypoints/main.d.ts",
14
+ "types": "./entrypoints/main.d.mts",
15
15
  "default": "./entrypoints/main.mjs"
16
16
  },
17
17
  "require": {
@@ -22,22 +22,24 @@
22
22
  "./eslint-plugin-helper": "./entrypoints/eslint-plugin-helper.cjs",
23
23
  "./plugin": {
24
24
  "import": {
25
- "types": "./entrypoints/plugin.d.ts",
25
+ "types": "./entrypoints/plugin.d.mts",
26
26
  "default": "./entrypoints/plugin.mjs"
27
27
  },
28
28
  "require": {
29
29
  "types": "./entrypoints/plugin.d.cts",
30
30
  "default": "./entrypoints/plugin.cjs"
31
31
  }
32
+ },
33
+ "./internal": {
34
+ "types": "./entrypoints/internal.d.mts"
32
35
  }
33
36
  },
34
37
  "type": "module",
35
38
  "engines": {
36
- "node": ">=14.19 <15 || >=16.15 <17 || >=18"
39
+ "node": "^18.18 || ^20.8 || ^21"
37
40
  },
38
41
  "scripts": {
39
- "cover": "c8 --report=none test-ava && c8 --report=none --no-clean tap && c8 report",
40
- "test": "xo && tsc --noEmit && npm run -s cover"
42
+ "test": "./scripts/test.sh"
41
43
  },
42
44
  "files": [
43
45
  "entrypoints",
@@ -81,45 +83,42 @@
81
83
  "typescript"
82
84
  ],
83
85
  "dependencies": {
84
- "acorn": "^8.8.2",
85
- "acorn-walk": "^8.2.0",
86
+ "@vercel/nft": "^0.24.4",
87
+ "acorn": "^8.11.2",
88
+ "acorn-walk": "^8.3.0",
86
89
  "ansi-styles": "^6.2.1",
87
90
  "arrgv": "^1.0.2",
88
91
  "arrify": "^3.0.0",
89
- "callsites": "^4.0.0",
90
- "cbor": "^8.1.0",
91
- "chalk": "^5.2.0",
92
- "chokidar": "^3.5.3",
92
+ "callsites": "^4.1.0",
93
+ "cbor": "^9.0.1",
94
+ "chalk": "^5.3.0",
93
95
  "chunkd": "^2.0.1",
94
- "ci-info": "^3.8.0",
96
+ "ci-info": "^4.0.0",
95
97
  "ci-parallel-vars": "^1.0.1",
96
- "clean-yaml-object": "^0.1.0",
97
- "cli-truncate": "^3.1.0",
98
+ "cli-truncate": "^4.0.0",
98
99
  "code-excerpt": "^4.0.0",
99
100
  "common-path-prefix": "^3.0.0",
100
101
  "concordance": "^5.0.4",
101
102
  "currently-unhandled": "^0.4.1",
102
103
  "debug": "^4.3.4",
103
104
  "emittery": "^1.0.1",
104
- "figures": "^5.0.0",
105
- "globby": "^13.1.4",
105
+ "figures": "^6.0.1",
106
+ "globby": "^14.0.0",
106
107
  "ignore-by-default": "^2.1.0",
107
108
  "indent-string": "^5.0.0",
108
- "is-error": "^2.2.2",
109
109
  "is-plain-object": "^5.0.0",
110
110
  "is-promise": "^4.0.0",
111
111
  "matcher": "^5.0.0",
112
- "mem": "^9.0.2",
112
+ "memoize": "^10.0.0",
113
113
  "ms": "^2.1.3",
114
- "p-event": "^5.0.1",
115
- "p-map": "^5.5.0",
116
- "picomatch": "^2.3.1",
117
- "pkg-conf": "^4.0.0",
114
+ "p-map": "^6.0.0",
115
+ "package-config": "^5.0.0",
116
+ "picomatch": "^3.0.1",
118
117
  "plur": "^5.1.0",
119
118
  "pretty-ms": "^8.0.0",
120
119
  "resolve-cwd": "^3.0.0",
121
120
  "stack-utils": "^2.0.6",
122
- "strip-ansi": "^7.0.1",
121
+ "strip-ansi": "^7.1.0",
123
122
  "supertap": "^3.0.1",
124
123
  "temp-dir": "^3.0.0",
125
124
  "write-file-atomic": "^5.0.1",
@@ -127,24 +126,19 @@
127
126
  },
128
127
  "devDependencies": {
129
128
  "@ava/test": "github:avajs/test",
130
- "@ava/typescript": "^4.0.0",
131
- "@sindresorhus/tsconfig": "^3.0.1",
129
+ "@ava/typescript": "^4.1.0",
130
+ "@sindresorhus/tsconfig": "^5.0.0",
131
+ "@types/node": "^20.10.3",
132
132
  "ansi-escapes": "^6.2.0",
133
- "c8": "^7.13.0",
134
- "delay": "^5.0.0",
135
- "execa": "^7.1.1",
136
- "expect": "^29.5.0",
137
- "fs-extra": "^11.1.1",
138
- "get-stream": "^6.0.1",
139
- "replace-string": "^4.0.0",
140
- "sinon": "^15.1.0",
141
- "tap": "^16.3.4",
142
- "temp-write": "^5.0.0",
143
- "tempy": "^3.0.0",
144
- "touch": "^3.1.0",
145
- "tsd": "^0.28.1",
146
- "typescript": "^4.9.5",
147
- "xo": "^0.54.2",
133
+ "c8": "^8.0.1",
134
+ "execa": "^8.0.1",
135
+ "expect": "^29.7.0",
136
+ "sinon": "^17.0.1",
137
+ "tap": "^18.6.1",
138
+ "tempy": "^3.1.0",
139
+ "tsd": "^0.29.0",
140
+ "typescript": "~5.3.2",
141
+ "xo": "^0.56.0",
148
142
  "zen-observable": "^0.10.0"
149
143
  },
150
144
  "peerDependencies": {
@@ -156,6 +150,6 @@
156
150
  }
157
151
  },
158
152
  "volta": {
159
- "node": "20.2.0"
153
+ "node": "20.10.0"
160
154
  }
161
155
  }
package/readme.md CHANGED
@@ -1,10 +1,12 @@
1
- [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine/)
1
+ *[Please support our friend Vadim Demedes and the people in Ukraine.](https://stand-with-ukraine.pp.ua/)*
2
+
3
+ ---
2
4
 
3
5
  # <img src="media/header.png" title="AVA" alt="AVA logo" width="530">
4
6
 
5
- AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and process isolation that lets you develop with confidence 🚀
7
+ AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and htread isolation that lets you develop with confidence 🚀
6
8
 
7
- Follow the [AVA Twitter account](https://twitter.com/ava__js) for updates.
9
+ Watch this repository and follow the [Discussions](https://github.com/avajs/ava/discussions) for updates.
8
10
 
9
11
  Read our [contributing guide](.github/CONTRIBUTING.md) if you're looking to contribute (issues / PRs / etc).
10
12
 
@@ -23,7 +25,7 @@ Translations: [Español](https://github.com/avajs/ava-docs/blob/main/es_ES/readm
23
25
  - No implicit globals
24
26
  - Includes TypeScript definitions
25
27
  - [Magic assert](#magic-assert)
26
- - [Isolated environment for each test file](./docs/01-writing-tests.md#process-isolation)
28
+ - [Isolated environment for each test file](./docs/01-writing-tests.md#test-isolation)
27
29
  - [Promise support](./docs/01-writing-tests.md#promise-support)
28
30
  - [Async function support](./docs/01-writing-tests.md#async-function-support)
29
31
  - [Observable support](./docs/01-writing-tests.md#observable-support)
@@ -166,14 +168,6 @@ We have a growing list of [common pitfalls](docs/08-common-pitfalls.md) you may
166
168
 
167
169
  ## FAQ
168
170
 
169
- ### Why not `mocha`, `tape`, `tap`?
170
-
171
- Mocha requires you to use implicit globals like `describe` and `it` with the default interface (which most people use). It's not very opinionated and executes tests serially without process isolation, making it slow.
172
-
173
- Tape and tap are pretty good. AVA is highly inspired by their syntax. They too execute tests serially. Their default [TAP](https://testanything.org) output isn't very user-friendly though so you always end up using an external tap reporter.
174
-
175
- In contrast AVA is highly opinionated and runs tests concurrently, with a separate process for each test file. Its default reporter is easy on the eyes and yet AVA still supports TAP output through a CLI flag.
176
-
177
171
  ### How is the name written and pronounced?
178
172
 
179
173
  AVA, not Ava or ava. Pronounced [`/ˈeɪvə/`](media/pronunciation.m4a?raw=true): Ay (f**a**ce, m**a**de) V (**v**ie, ha**v**e) A (comm**a**, **a**go)