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
@@ -1,44 +1,34 @@
1
- 'use strict';
2
- const {pathToFileURL} = require('url');
3
- const path = require('path');
4
- const currentlyUnhandled = require('currently-unhandled')();
5
- const {isRunningInThread, isRunningInChildProcess} = require('./utils');
6
-
7
- // Check if the test is being run without AVA cli
8
- if (!isRunningInChildProcess && !isRunningInThread) {
9
- const chalk = require('chalk'); // Use default Chalk instance.
10
- if (process.argv[1]) {
11
- const fp = path.relative('.', process.argv[1]);
12
-
13
- console.log();
14
- console.error(`Test files must be run with the AVA CLI:\n\n ${chalk.grey.dim('$')} ${chalk.cyan('ava ' + fp)}\n`);
15
-
16
- process.exit(1);
17
- } else {
18
- throw new Error('The ’ava’ module can only be imported in test files');
19
- }
20
- }
1
+ import {createRequire} from 'node:module';
2
+ import process from 'node:process';
3
+ import {pathToFileURL} from 'node:url';
4
+ import {workerData} from 'node:worker_threads';
5
+
6
+ import setUpCurrentlyUnhandled from 'currently-unhandled';
7
+
8
+ import {set as setChalk} from '../chalk.js';
9
+ import nowAndTimers from '../now-and-timers.cjs';
10
+ import providerManager from '../provider-manager.js';
11
+ import Runner from '../runner.js';
12
+ import serializeError from '../serialize-error.js';
13
+
14
+ import channel from './channel.cjs';
15
+ import dependencyTracking from './dependency-tracker.js';
16
+ import lineNumberSelection from './line-numbers.js';
17
+ import {set as setOptions} from './options.cjs';
18
+ import {flags, refs, sharedWorkerTeardowns} from './state.cjs';
19
+ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
21
20
 
22
- const channel = require('./channel');
21
+ const currentlyUnhandled = setUpCurrentlyUnhandled();
23
22
 
24
23
  const run = async options => {
25
- require('./options').set(options);
26
- require('../chalk').set(options.chalkOptions);
24
+ setOptions(options);
25
+ setChalk(options.chalkOptions);
27
26
 
28
27
  if (options.chalkOptions.level > 0) {
29
28
  const {stdout, stderr} = process;
30
29
  global.console = Object.assign(global.console, new console.Console({stdout, stderr, colorMode: true}));
31
30
  }
32
31
 
33
- const nowAndTimers = require('../now-and-timers');
34
- const providerManager = require('../provider-manager');
35
- const Runner = require('../runner');
36
- const serializeError = require('../serialize-error');
37
- const dependencyTracking = require('./dependency-tracker');
38
- const lineNumberSelection = require('./line-numbers');
39
-
40
- const sharedWorkerTeardowns = [];
41
-
42
32
  async function exit(code) {
43
33
  if (!process.exitCode) {
44
34
  process.exitCode = code;
@@ -46,16 +36,14 @@ const run = async options => {
46
36
 
47
37
  dependencyTracking.flush();
48
38
  await channel.flush();
49
- process.exit();
39
+ process.exit(); // eslint-disable-line unicorn/no-process-exit
50
40
  }
51
41
 
52
- // TODO: Initialize providers here, then pass to lineNumberSelection() so they
53
- // can be used to parse the test file.
54
42
  let checkSelectedByLineNumbers;
55
43
  try {
56
44
  checkSelectedByLineNumbers = lineNumberSelection({
57
45
  file: options.file,
58
- lineNumbers: options.lineNumbers
46
+ lineNumbers: options.lineNumbers,
59
47
  });
60
48
  } catch (error) {
61
49
  channel.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)});
@@ -74,18 +62,13 @@ const run = async options => {
74
62
  runOnlyExclusive: options.runOnlyExclusive,
75
63
  serial: options.serial,
76
64
  snapshotDir: options.snapshotDir,
77
- updateSnapshots: options.updateSnapshots
65
+ updateSnapshots: options.updateSnapshots,
78
66
  });
79
67
 
80
- channel.peerFailed.then(() => { // eslint-disable-line promise/prefer-await-to-then
81
- runner.interrupt();
82
- });
68
+ refs.runnerChain = runner.chain;
83
69
 
84
- const attributedRejections = new Set();
85
- process.on('unhandledRejection', (reason, promise) => {
86
- if (runner.attributeLeakedError(reason)) {
87
- attributedRejections.add(promise);
88
- }
70
+ channel.peerFailed.then(() => {
71
+ runner.interrupt();
89
72
  });
90
73
 
91
74
  runner.on('dependency', dependencyTracking.track);
@@ -98,7 +81,7 @@ const run = async options => {
98
81
 
99
82
  runner.on('finish', async () => {
100
83
  try {
101
- const {touchedFiles} = runner.saveSnapshotState();
84
+ const {touchedFiles} = await runner.saveSnapshotState();
102
85
  if (touchedFiles) {
103
86
  channel.send({type: 'touched-files', files: touchedFiles});
104
87
  }
@@ -117,7 +100,7 @@ const run = async options => {
117
100
  }
118
101
 
119
102
  nowAndTimers.setImmediate(() => {
120
- for (const rejection of currentlyUnhandled().filter(rejection => !attributedRejections.has(rejection.promise))) {
103
+ for (const rejection of currentlyUnhandled()) {
121
104
  channel.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)});
122
105
  }
123
106
 
@@ -126,43 +109,13 @@ const run = async options => {
126
109
  });
127
110
 
128
111
  process.on('uncaughtException', error => {
129
- if (runner.attributeLeakedError(error)) {
130
- return;
131
- }
132
-
133
112
  channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
134
113
  exit(1);
135
114
  });
136
115
 
137
- let accessedRunner = false;
138
- exports.getRunner = () => {
139
- accessedRunner = true;
140
- return runner;
141
- };
142
-
143
- exports.registerSharedWorker = (filename, initialData, teardown) => {
144
- const {channel: sharedWorkerChannel, forceUnref, ready} = channel.registerSharedWorker(filename, initialData);
145
- runner.waitForReady.push(ready);
146
- sharedWorkerTeardowns.push(async () => {
147
- try {
148
- await teardown();
149
- } finally {
150
- forceUnref();
151
- }
152
- });
153
- return sharedWorkerChannel;
154
- };
155
-
156
116
  // Store value to prevent required modules from modifying it.
157
117
  const testPath = options.file;
158
118
 
159
- // Install basic source map support.
160
- const sourceMapSupport = require('source-map-support');
161
- sourceMapSupport.install({
162
- environment: 'node',
163
- handleUncaughtExceptions: false
164
- });
165
-
166
119
  const extensionsToLoadAsModules = Object.entries(options.moduleTypes)
167
120
  .filter(([, type]) => type === 'module')
168
121
  .map(([extension]) => extension);
@@ -170,33 +123,29 @@ const run = async options => {
170
123
  // Install before processing options.require, so if helpers are added to the
171
124
  // require configuration the *compiled* helper will be loaded.
172
125
  const {projectDir, providerStates = []} = options;
173
- const providers = providerStates.map(({type, state}) => {
174
- if (type === 'babel') {
175
- const provider = providerManager.babel(projectDir).worker({extensionsToLoadAsModules, state});
176
- runner.powerAssert = provider.powerAssert;
177
- return provider;
178
- }
179
-
126
+ const providers = [];
127
+ await Promise.all(providerStates.map(async ({type, state}) => {
180
128
  if (type === 'typescript') {
181
- return providerManager.typescript(projectDir).worker({extensionsToLoadAsModules, state});
129
+ const provider = await providerManager.typescript(projectDir);
130
+ providers.push(provider.worker({extensionsToLoadAsModules, state}));
182
131
  }
132
+ }));
183
133
 
184
- return null;
185
- }).filter(provider => provider !== null);
186
-
134
+ const require = createRequire(import.meta.url);
187
135
  const load = async ref => {
188
- for (const extension of extensionsToLoadAsModules) {
189
- if (ref.endsWith(`.${extension}`)) {
190
- return import(pathToFileURL(ref)); // eslint-disable-line node/no-unsupported-features/es-syntax
191
- }
192
- }
193
-
194
136
  for (const provider of providers) {
195
137
  if (provider.canLoad(ref)) {
196
138
  return provider.load(ref, {requireFn: require});
197
139
  }
198
140
  }
199
141
 
142
+ for (const extension of extensionsToLoadAsModules) {
143
+ if (ref.endsWith(`.${extension}`)) {
144
+ return import(pathToFileURL(ref)); // eslint-disable-line node/no-unsupported-features/es-syntax
145
+ }
146
+ }
147
+
148
+ // We still support require() since it's more easily monkey-patched.
200
149
  return require(ref);
201
150
  };
202
151
 
@@ -207,12 +156,12 @@ const run = async options => {
207
156
 
208
157
  // Install dependency tracker after the require configuration has been evaluated
209
158
  // to make sure we also track dependencies with custom require hooks
210
- dependencyTracking.install(testPath);
159
+ dependencyTracking.install(require.extensions, testPath);
211
160
 
212
161
  if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
213
162
  // If an inspector was active when the main process started, and is
214
163
  // already active for the worker process, do not open a new one.
215
- const inspector = require('inspector'); // eslint-disable-line node/no-unsupported-features/node-builtins
164
+ const {default: inspector} = await import('node:inspector'); // eslint-disable-line node/no-unsupported-features/es-syntax
216
165
  if (!options.debug.active || inspector.url() === undefined) {
217
166
  inspector.open(options.debug.port, options.debug.host, true);
218
167
  }
@@ -224,7 +173,7 @@ const run = async options => {
224
173
 
225
174
  await load(testPath);
226
175
 
227
- if (accessedRunner) {
176
+ if (flags.loadedMain) {
228
177
  // Unreference the channel if the test file required AVA. This stops it
229
178
  // from keeping the event loop busy, which means the `beforeExit` event can be
230
179
  // used to detect when tests stall.
@@ -249,11 +198,11 @@ const onError = error => {
249
198
  };
250
199
 
251
200
  if (isRunningInThread) {
252
- const {workerData} = require('worker_threads');
201
+ channel.send({type: 'starting'}); // AVA won't terminate the worker thread until it's seen this message.
253
202
  const {options} = workerData;
254
203
  delete workerData.options; // Don't allow user code access.
255
204
  run(options).catch(onError);
256
205
  } else if (isRunningInChildProcess) {
257
206
  channel.send({type: 'ready-for-options'});
258
- channel.options.then(run).catch(onError); // eslint-disable-line promise/prefer-await-to-then
207
+ channel.options.then(run).catch(onError);
259
208
  }
@@ -0,0 +1,290 @@
1
+ 'use strict';
2
+ const events = require('events');
3
+ const process = require('process');
4
+ const {MessageChannel, threadId} = require('worker_threads');
5
+
6
+ const timers = require('../now-and-timers.cjs');
7
+
8
+ const {isRunningInChildProcess, isRunningInThread} = require('./utils.cjs');
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')); // eslint-disable-line node/no-unsupported-features/es-syntax
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);
41
+ }
42
+ };
43
+
44
+ const selectAvaMessage = type => message => message.ava && message.ava.type === type;
45
+
46
+ class RefCounter {
47
+ constructor() {
48
+ this.count = 0;
49
+ }
50
+
51
+ refAndTest() {
52
+ return ++this.count === 1;
53
+ }
54
+
55
+ testAndUnref() {
56
+ return this.count > 0 && --this.count === 0;
57
+ }
58
+ }
59
+
60
+ class MessagePortHandle {
61
+ constructor(port) {
62
+ this.counter = new RefCounter();
63
+ this.unreferenceable = false;
64
+ this.channel = port;
65
+ // Referencing the port does not immediately prevent the thread from
66
+ // exiting. Use a timer to keep a reference for at least a second.
67
+ this.workaroundTimer = timers.setTimeout(() => {}, 1000).unref();
68
+ }
69
+
70
+ forceUnref() {
71
+ if (this.unreferenceable) {
72
+ return;
73
+ }
74
+
75
+ this.unreferenceable = true;
76
+ this.workaroundTimer.unref();
77
+ this.channel.unref();
78
+ }
79
+
80
+ ref() {
81
+ if (!this.unreferenceable && this.counter.refAndTest()) {
82
+ this.workaroundTimer.refresh().ref();
83
+ this.channel.ref();
84
+ }
85
+ }
86
+
87
+ unref() {
88
+ if (!this.unreferenceable && this.counter.testAndUnref()) {
89
+ this.workaroundTimer.unref();
90
+ this.channel.unref();
91
+ }
92
+ }
93
+
94
+ send(evt, transferList) {
95
+ this.channel.postMessage({ava: evt}, transferList);
96
+ }
97
+ }
98
+
99
+ class IpcHandle {
100
+ constructor(bufferedSend) {
101
+ this.counter = new RefCounter();
102
+ this.channel = process;
103
+ this.sendRaw = bufferedSend;
104
+ }
105
+
106
+ ref() {
107
+ if (this.counter.refAndTest()) {
108
+ process.channel.ref();
109
+ }
110
+ }
111
+
112
+ unref() {
113
+ if (this.counter.testAndUnref()) {
114
+ process.channel.unref();
115
+ }
116
+ }
117
+
118
+ send(evt) {
119
+ this.sendRaw({ava: evt});
120
+ }
121
+ }
122
+
123
+ let handle;
124
+ if (isRunningInChildProcess) {
125
+ const {controlFlow} = require('../ipc-flow-control.cjs');
126
+ handle = new IpcHandle(controlFlow(process));
127
+ } else if (isRunningInThread) {
128
+ const {parentPort} = require('worker_threads');
129
+ handle = new MessagePortHandle(parentPort);
130
+ }
131
+
132
+ // The attaching of message listeners will cause the port to be referenced by
133
+ // Node.js. In order to keep track, explicitly reference before attaching.
134
+ handle.ref();
135
+
136
+ exports.options = pEvent(handle.channel, 'message', selectAvaMessage('options')).then(message => message.ava.options);
137
+ exports.peerFailed = pEvent(handle.channel, 'message', selectAvaMessage('peer-failed'));
138
+ exports.send = handle.send.bind(handle);
139
+ exports.unref = handle.unref.bind(handle);
140
+
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
+ let channelCounter = 0;
158
+ let messageCounter = 0;
159
+
160
+ const channelEmitters = new Map();
161
+ function createChannelEmitter(channelId) {
162
+ if (channelEmitters.size === 0) {
163
+ handle.channel.on('message', message => {
164
+ if (!message.ava) {
165
+ return;
166
+ }
167
+
168
+ const {channelId, type, ...payload} = message.ava;
169
+ if (type === 'shared-worker-error') {
170
+ const emitter = channelEmitters.get(channelId);
171
+ if (emitter !== undefined) {
172
+ emitter.emit(type, payload);
173
+ }
174
+ }
175
+ });
176
+ }
177
+
178
+ const emitter = new events.EventEmitter();
179
+ channelEmitters.set(channelId, emitter);
180
+ return [emitter, () => channelEmitters.delete(channelId)];
181
+ }
182
+
183
+ function registerSharedWorker(filename, initialData) {
184
+ const channelId = `${threadId}/channel/${++channelCounter}`;
185
+
186
+ const {port1: ourPort, port2: theirPort} = new MessageChannel();
187
+ const sharedWorkerHandle = new MessagePortHandle(ourPort);
188
+
189
+ const [channelEmitter, unsubscribe] = createChannelEmitter(channelId);
190
+
191
+ handle.send({
192
+ type: 'shared-worker-connect',
193
+ channelId,
194
+ filename,
195
+ initialData,
196
+ port: theirPort,
197
+ }, [theirPort]);
198
+
199
+ let currentlyAvailable = false;
200
+ let error = null;
201
+
202
+ // The attaching of message listeners will cause the port to be referenced by
203
+ // Node.js. In order to keep track, explicitly reference before attaching.
204
+ sharedWorkerHandle.ref();
205
+ const ready = pEvent(ourPort, 'message', ({type}) => type === 'ready').then(() => {
206
+ currentlyAvailable = error === null;
207
+ }).finally(() => {
208
+ // Once ready, it's up to user code to subscribe to messages, which (see
209
+ // below) causes us to reference the port.
210
+ sharedWorkerHandle.unref();
211
+ });
212
+
213
+ const messageEmitters = new Set();
214
+
215
+ // Errors are received over the test worker channel, not the message port
216
+ // dedicated to the shared worker.
217
+ pEvent(channelEmitter, 'shared-worker-error').then(() => {
218
+ unsubscribe();
219
+ sharedWorkerHandle.forceUnref();
220
+ error = new Error('The shared worker is no longer available');
221
+ currentlyAvailable = false;
222
+ for (const emitter of messageEmitters) {
223
+ emitter.emit('error', error);
224
+ }
225
+ });
226
+
227
+ ourPort.on('message', message => {
228
+ if (message.type === 'message') {
229
+ // Wait for a turn of the event loop, to allow new subscriptions to be set
230
+ // up in response to the previous message.
231
+ setImmediate(() => {
232
+ for (const emitter of messageEmitters) {
233
+ emitter.emit('message', message);
234
+ }
235
+ });
236
+ }
237
+ });
238
+
239
+ return {
240
+ forceUnref: () => sharedWorkerHandle.forceUnref(),
241
+ ready,
242
+ channel: {
243
+ available: ready,
244
+
245
+ get currentlyAvailable() {
246
+ return currentlyAvailable;
247
+ },
248
+
249
+ async * receive() {
250
+ if (error !== null) {
251
+ throw error;
252
+ }
253
+
254
+ const emitter = new events.EventEmitter();
255
+ messageEmitters.add(emitter);
256
+ try {
257
+ sharedWorkerHandle.ref();
258
+ for await (const [message] of events.on(emitter, 'message')) {
259
+ yield message;
260
+ }
261
+ } finally {
262
+ sharedWorkerHandle.unref();
263
+ messageEmitters.delete(emitter);
264
+ }
265
+ },
266
+
267
+ post(data, replyTo) {
268
+ if (error !== null) {
269
+ throw error;
270
+ }
271
+
272
+ if (!currentlyAvailable) {
273
+ throw new Error('Shared worker is not yet available');
274
+ }
275
+
276
+ const messageId = `${channelId}/message/${++messageCounter}`;
277
+ ourPort.postMessage({
278
+ type: 'message',
279
+ messageId,
280
+ replyTo,
281
+ data,
282
+ });
283
+
284
+ return messageId;
285
+ },
286
+ },
287
+ };
288
+ }
289
+
290
+ exports.registerSharedWorker = registerSharedWorker;
@@ -1,6 +1,6 @@
1
- /* eslint-disable node/no-deprecated-api */
2
- 'use strict';
3
- const channel = require('./channel');
1
+ import process from 'node:process';
2
+
3
+ import channel from './channel.cjs';
4
4
 
5
5
  const seenDependencies = new Set();
6
6
  let newDependencies = [];
@@ -14,8 +14,6 @@ function flush() {
14
14
  newDependencies = [];
15
15
  }
16
16
 
17
- exports.flush = flush;
18
-
19
17
  function track(filename) {
20
18
  if (seenDependencies.has(filename)) {
21
19
  return;
@@ -29,20 +27,22 @@ function track(filename) {
29
27
  newDependencies.push(filename);
30
28
  }
31
29
 
32
- exports.track = track;
33
-
34
- function install(testPath) {
35
- for (const ext of Object.keys(require.extensions)) {
36
- const wrappedHandler = require.extensions[ext];
37
-
38
- require.extensions[ext] = (module, filename) => {
39
- if (filename !== testPath) {
40
- track(filename);
41
- }
42
-
43
- wrappedHandler(module, filename);
44
- };
45
- }
46
- }
47
-
48
- exports.install = install;
30
+ const tracker = {
31
+ flush,
32
+ track,
33
+ install(extensions, testPath) {
34
+ for (const ext of Object.keys(extensions)) {
35
+ const wrappedHandler = extensions[ext];
36
+
37
+ extensions[ext] = (module, filename) => {
38
+ if (filename !== testPath) {
39
+ track(filename);
40
+ }
41
+
42
+ wrappedHandler(module, filename);
43
+ };
44
+ }
45
+ },
46
+ };
47
+
48
+ export default tracker;
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const process = require('process');
4
+
5
+ const {isRunningInThread, isRunningInChildProcess} = require('./utils.cjs');
6
+
7
+ // Check if the test is being run without AVA cli
8
+ if (!isRunningInChildProcess && !isRunningInThread) {
9
+ if (process.argv[1]) {
10
+ const fp = path.relative('.', process.argv[1]);
11
+
12
+ console.log();
13
+ console.error(`Test files must be run with the AVA CLI:\n\n $ ava ${fp}\n`);
14
+
15
+ process.exit(1); // eslint-disable-line unicorn/no-process-exit
16
+ } else {
17
+ throw new Error('The ’ava’ module can only be imported in test files');
18
+ }
19
+ }