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/runner.js CHANGED
@@ -1,14 +1,19 @@
1
- 'use strict';
2
- const Emittery = require('emittery');
3
- const matcher = require('matcher');
4
- const ContextRef = require('./context-ref');
5
- const createChain = require('./create-chain');
6
- const parseTestArgs = require('./parse-test-args');
7
- const snapshotManager = require('./snapshot-manager');
8
- const serializeError = require('./serialize-error');
9
- const Runnable = require('./test');
10
-
11
- class Runner extends Emittery {
1
+ import process from 'node:process';
2
+ import {pathToFileURL} from 'node:url';
3
+
4
+ import Emittery from 'emittery';
5
+ import {matcher} from 'matcher';
6
+
7
+ import ContextRef from './context-ref.js';
8
+ import createChain from './create-chain.js';
9
+ import parseTestArgs from './parse-test-args.js';
10
+ import serializeError from './serialize-error.js';
11
+ import {load as loadSnapshots, determineSnapshotDir} from './snapshot-manager.js';
12
+ import Runnable from './test.js';
13
+ import {waitForReady} from './worker/state.cjs';
14
+
15
+ const makeFileURL = file => file.startsWith('file://') ? file : pathToFileURL(file).toString();
16
+ export default class Runner extends Emittery {
12
17
  constructor(options = {}) {
13
18
  super();
14
19
 
@@ -18,7 +23,6 @@ class Runner extends Emittery {
18
23
  this.file = options.file;
19
24
  this.checkSelectedByLineNumbers = options.checkSelectedByLineNumbers;
20
25
  this.match = options.match || [];
21
- this.powerAssert = undefined; // Assigned later.
22
26
  this.projectDir = options.projectDir;
23
27
  this.recordNewSnapshots = options.recordNewSnapshots === true;
24
28
  this.runOnlyExclusive = options.runOnlyExclusive === true;
@@ -30,16 +34,6 @@ class Runner extends Emittery {
30
34
  this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
31
35
  this.boundSkipSnapshot = this.skipSnapshot.bind(this);
32
36
  this.interrupted = false;
33
- this.snapshots = snapshotManager.load({
34
- file: this.file,
35
- fixedLocation: this.snapshotDir,
36
- projectDir: this.projectDir,
37
- recordNewSnapshots: this.recordNewSnapshots,
38
- updating: this.updateSnapshots
39
- });
40
- if (this.snapshots.snapPath !== undefined) {
41
- this.emit('dependency', this.snapshots.snapPath);
42
- }
43
37
 
44
38
  this.nextTaskIndex = 0;
45
39
  this.tasks = {
@@ -51,9 +45,9 @@ class Runner extends Emittery {
51
45
  beforeEach: [],
52
46
  concurrent: [],
53
47
  serial: [],
54
- todo: []
48
+ todo: [],
55
49
  };
56
- this.waitForReady = [];
50
+ this.waitForReady = waitForReady;
57
51
 
58
52
  const uniqueTestTitles = new Set();
59
53
  this.registerUniqueTitle = title => {
@@ -65,14 +59,21 @@ class Runner extends Emittery {
65
59
  return true;
66
60
  };
67
61
 
62
+ this.notifyTimeoutUpdate = timeoutMs => {
63
+ this.emit('stateChange', {
64
+ type: 'test-timeout-configured',
65
+ period: timeoutMs,
66
+ });
67
+ };
68
+
68
69
  let hasStarted = false;
69
70
  let scheduledStart = false;
70
71
  const meta = Object.freeze({
71
- file: options.file,
72
+ file: makeFileURL(options.file),
72
73
  get snapshotDirectory() {
73
74
  const {file, snapshotDir: fixedLocation, projectDir} = options;
74
- return snapshotManager.determineSnapshotDir({file, fixedLocation, projectDir});
75
- }
75
+ return makeFileURL(determineSnapshotDir({file, fixedLocation, projectDir}));
76
+ },
76
77
  });
77
78
  this.chain = createChain((metadata, testArgs) => { // eslint-disable-line complexity
78
79
  if (hasStarted) {
@@ -89,94 +90,95 @@ class Runner extends Emittery {
89
90
 
90
91
  metadata.taskIndex = this.nextTaskIndex++;
91
92
 
92
- const {args, buildTitle, implementations, rawTitle} = parseTestArgs(testArgs);
93
+ const {args, implementation, title} = parseTestArgs(testArgs);
93
94
 
94
95
  if (this.checkSelectedByLineNumbers) {
95
96
  metadata.selected = this.checkSelectedByLineNumbers();
96
97
  }
97
98
 
98
99
  if (metadata.todo) {
99
- if (implementations.length > 0) {
100
+ if (implementation) {
100
101
  throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
101
102
  }
102
103
 
103
- if (!rawTitle) { // Either undefined or a string.
104
+ if (!title.raw) { // Either undefined or a string.
104
105
  throw new TypeError('`todo` tests require a title');
105
106
  }
106
107
 
107
- if (!this.registerUniqueTitle(rawTitle)) {
108
- throw new Error(`Duplicate test title: ${rawTitle}`);
108
+ if (!this.registerUniqueTitle(title.value)) {
109
+ throw new Error(`Duplicate test title: ${title.value}`);
109
110
  }
110
111
 
111
112
  // --match selects TODO tests.
112
- if (this.match.length > 0 && matcher([rawTitle], this.match).length === 1) {
113
+ if (this.match.length > 0 && matcher(title.value, this.match).length === 1) {
113
114
  metadata.exclusive = true;
114
115
  this.runOnlyExclusive = true;
115
116
  }
116
117
 
117
- this.tasks.todo.push({title: rawTitle, metadata});
118
+ this.tasks.todo.push({title: title.value, metadata});
118
119
  this.emit('stateChange', {
119
120
  type: 'declared-test',
120
- title: rawTitle,
121
+ title: title.value,
121
122
  knownFailing: false,
122
- todo: true
123
+ todo: true,
123
124
  });
124
125
  } else {
125
- if (implementations.length === 0) {
126
+ if (!implementation) {
126
127
  throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
127
128
  }
128
129
 
129
- for (const implementation of implementations) {
130
- let {title, isSet, isValid, isEmpty} = buildTitle(implementation);
130
+ if (Array.isArray(implementation)) {
131
+ throw new TypeError('AVA 4 no longer supports multiple implementations.');
132
+ }
131
133
 
132
- if (isSet && !isValid) {
133
- throw new TypeError('Test & hook titles must be strings');
134
+ if (title.isSet && !title.isValid) {
135
+ throw new TypeError('Test & hook titles must be strings');
136
+ }
137
+
138
+ let fallbackTitle = title.value;
139
+ if (title.isEmpty) {
140
+ if (metadata.type === 'test') {
141
+ throw new TypeError('Tests must have a title');
142
+ } else if (metadata.always) {
143
+ fallbackTitle = `${metadata.type}.always hook`;
144
+ } else {
145
+ fallbackTitle = `${metadata.type} hook`;
134
146
  }
147
+ }
148
+
149
+ if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {
150
+ throw new Error(`Duplicate test title: ${title.value}`);
151
+ }
135
152
 
136
- if (isEmpty) {
137
- if (metadata.type === 'test') {
138
- throw new TypeError('Tests must have a title');
139
- } else if (metadata.always) {
140
- title = `${metadata.type}.always hook`;
141
- } else {
142
- title = `${metadata.type} hook`;
143
- }
153
+ const task = {
154
+ title: title.value || fallbackTitle,
155
+ implementation,
156
+ args,
157
+ metadata: {...metadata},
158
+ };
159
+
160
+ if (metadata.type === 'test') {
161
+ if (this.match.length > 0) {
162
+ // --match overrides .only()
163
+ task.metadata.exclusive = matcher(title.value, this.match).length === 1;
144
164
  }
145
165
 
146
- if (metadata.type === 'test' && !this.registerUniqueTitle(title)) {
147
- throw new Error(`Duplicate test title: ${title}`);
166
+ if (task.metadata.exclusive) {
167
+ this.runOnlyExclusive = true;
148
168
  }
149
169
 
150
- const task = {
151
- title,
152
- implementation,
153
- args,
154
- metadata: {...metadata}
155
- };
170
+ this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
156
171
 
157
- if (metadata.type === 'test') {
158
- if (this.match.length > 0) {
159
- // --match overrides .only()
160
- task.metadata.exclusive = matcher([title], this.match).length === 1;
161
- }
162
-
163
- if (task.metadata.exclusive) {
164
- this.runOnlyExclusive = true;
165
- }
166
-
167
- this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
168
-
169
- this.snapshots.touch(title, metadata.taskIndex);
170
-
171
- this.emit('stateChange', {
172
- type: 'declared-test',
173
- title,
174
- knownFailing: metadata.failing,
175
- todo: false
176
- });
177
- } else if (!metadata.skipped) {
178
- this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
179
- }
172
+ this.snapshots.touch(title.value, metadata.taskIndex);
173
+
174
+ this.emit('stateChange', {
175
+ type: 'declared-test',
176
+ title: title.value,
177
+ knownFailing: metadata.failing,
178
+ todo: false,
179
+ });
180
+ } else if (!metadata.skipped) {
181
+ this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
180
182
  }
181
183
  }
182
184
  }, {
@@ -187,10 +189,33 @@ class Runner extends Emittery {
187
189
  failing: false,
188
190
  callback: false,
189
191
  inline: false, // Set for attempt metadata created by `t.try()`
190
- always: false
192
+ always: false,
191
193
  }, meta);
192
194
  }
193
195
 
196
+ get snapshots() {
197
+ if (this._snapshots) {
198
+ return this._snapshots;
199
+ }
200
+
201
+ // Lazy load not when the runner is instantiated but when snapshots are
202
+ // needed. This should be after the test file has been loaded and source
203
+ // maps are available.
204
+ const snapshots = loadSnapshots({
205
+ file: this.file,
206
+ fixedLocation: this.snapshotDir,
207
+ projectDir: this.projectDir,
208
+ recordNewSnapshots: this.recordNewSnapshots,
209
+ updating: this.updateSnapshots,
210
+ });
211
+ if (snapshots.snapPath !== undefined) {
212
+ this.emit('dependency', snapshots.snapPath);
213
+ }
214
+
215
+ this._snapshots = snapshots;
216
+ return snapshots;
217
+ }
218
+
194
219
  compareTestSnapshot(options) {
195
220
  return this.snapshots.compare(options);
196
221
  }
@@ -199,8 +224,8 @@ class Runner extends Emittery {
199
224
  return this.snapshots.skipSnapshot(options);
200
225
  }
201
226
 
202
- saveSnapshotState() {
203
- return {touchedFiles: this.snapshots.save()};
227
+ async saveSnapshotState() {
228
+ return {touchedFiles: await this.snapshots.save()};
204
229
  }
205
230
 
206
231
  onRun(runnable) {
@@ -211,16 +236,6 @@ class Runner extends Emittery {
211
236
  this.activeRunnables.delete(runnable);
212
237
  }
213
238
 
214
- attributeLeakedError(error) {
215
- for (const runnable of this.activeRunnables) {
216
- if (runnable.attributeLeakedError(error)) {
217
- return true;
218
- }
219
- }
220
-
221
- return false;
222
- }
223
-
224
239
  beforeExitHandler() {
225
240
  for (const runnable of this.activeRunnables) {
226
241
  runnable.finishDueToInactivity();
@@ -242,23 +257,23 @@ class Runner extends Emittery {
242
257
  let waitForSerial = Promise.resolve();
243
258
  await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-array-reduce
244
259
  if (runnable.metadata.serial || this.serial) {
245
- waitForSerial = previous.then(() => {
260
+ waitForSerial = previous.then(() =>
246
261
  // Serial runnables run as long as there was no previous failure, unless
247
262
  // the runnable should always be run.
248
- return (allPassed || runnable.metadata.always) && runAndStoreResult(runnable);
249
- });
263
+ (allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
264
+ );
250
265
  return waitForSerial;
251
266
  }
252
267
 
253
268
  return Promise.all([
254
269
  previous,
255
- waitForSerial.then(() => {
270
+ waitForSerial.then(() =>
256
271
  // Concurrent runnables are kicked off after the previous serial
257
272
  // runnables have completed, as long as there was no previous failure
258
273
  // (or if the runnable should always be run). One concurrent runnable's
259
274
  // failure does not prevent the next runnable from running.
260
- return (allPassed || runnable.metadata.always) && runAndStoreResult(runnable);
261
- })
275
+ (allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
276
+ ),
262
277
  ]);
263
278
  }, waitForSerial);
264
279
 
@@ -279,17 +294,17 @@ class Runner extends Emittery {
279
294
  contextRef,
280
295
  experiments: this.experiments,
281
296
  failWithoutAssertions: false,
282
- fn: task.args.length === 0 ?
283
- task.implementation :
284
- t => Reflect.apply(task.implementation, null, [t, ...task.args]),
297
+ fn: task.args.length === 0
298
+ ? task.implementation
299
+ : t => Reflect.apply(task.implementation, null, [t, ...task.args]),
285
300
  compareTestSnapshot: this.boundCompareTestSnapshot,
286
301
  skipSnapshot: this.boundSkipSnapshot,
287
302
  updateSnapshots: this.updateSnapshots,
288
303
  metadata: task.metadata,
289
- powerAssert: this.powerAssert,
290
304
  title: `${task.title}${titleSuffix || ''}`,
291
305
  isHook: true,
292
- testPassed
306
+ testPassed,
307
+ notifyTimeoutUpdate: this.notifyTimeoutUpdate,
293
308
  }));
294
309
  const outcome = await this.runMultiple(hooks, this.serial);
295
310
  for (const result of outcome.storedResults) {
@@ -298,7 +313,7 @@ class Runner extends Emittery {
298
313
  type: 'hook-finished',
299
314
  title: result.title,
300
315
  duration: result.duration,
301
- logs: result.logs
316
+ logs: result.logs,
302
317
  });
303
318
  } else {
304
319
  this.emit('stateChange', {
@@ -306,7 +321,7 @@ class Runner extends Emittery {
306
321
  title: result.title,
307
322
  err: serializeError('Hook failure', true, result.error),
308
323
  duration: result.duration,
309
- logs: result.logs
324
+ logs: result.logs,
310
325
  });
311
326
  }
312
327
  }
@@ -320,8 +335,8 @@ class Runner extends Emittery {
320
335
  this.tasks.beforeEach,
321
336
  contextRef,
322
337
  {
323
- titleSuffix: hookSuffix
324
- }
338
+ titleSuffix: hookSuffix,
339
+ },
325
340
  );
326
341
 
327
342
  let testOk = false;
@@ -331,16 +346,16 @@ class Runner extends Emittery {
331
346
  contextRef,
332
347
  experiments: this.experiments,
333
348
  failWithoutAssertions: this.failWithoutAssertions,
334
- fn: task.args.length === 0 ?
335
- task.implementation :
336
- t => Reflect.apply(task.implementation, null, [t, ...task.args]),
349
+ fn: task.args.length === 0
350
+ ? task.implementation
351
+ : t => Reflect.apply(task.implementation, null, [t, ...task.args]),
337
352
  compareTestSnapshot: this.boundCompareTestSnapshot,
338
353
  skipSnapshot: this.boundSkipSnapshot,
339
354
  updateSnapshots: this.updateSnapshots,
340
355
  metadata: task.metadata,
341
- powerAssert: this.powerAssert,
342
356
  title: task.title,
343
- registerUniqueTitle: this.registerUniqueTitle
357
+ registerUniqueTitle: this.registerUniqueTitle,
358
+ notifyTimeoutUpdate: this.notifyTimeoutUpdate,
344
359
  });
345
360
 
346
361
  const result = await this.runSingle(test);
@@ -352,7 +367,7 @@ class Runner extends Emittery {
352
367
  title: result.title,
353
368
  duration: result.duration,
354
369
  knownFailing: result.metadata.failing,
355
- logs: result.logs
370
+ logs: result.logs,
356
371
  });
357
372
 
358
373
  hooksOk = await this.runHooks(
@@ -360,7 +375,7 @@ class Runner extends Emittery {
360
375
  contextRef,
361
376
  {
362
377
  titleSuffix: hookSuffix,
363
- testPassed: testOk
378
+ testPassed: testOk,
364
379
  });
365
380
  } else {
366
381
  this.emit('stateChange', {
@@ -369,7 +384,7 @@ class Runner extends Emittery {
369
384
  err: serializeError('Test failure', true, result.error, this.file),
370
385
  duration: result.duration,
371
386
  knownFailing: result.metadata.failing,
372
- logs: result.logs
387
+ logs: result.logs,
373
388
  });
374
389
  // Don't run `afterEach` hooks if the test failed.
375
390
  }
@@ -380,7 +395,7 @@ class Runner extends Emittery {
380
395
  contextRef,
381
396
  {
382
397
  titleSuffix: hookSuffix,
383
- testPassed: testOk
398
+ testPassed: testOk,
384
399
  });
385
400
  return alwaysOk && hooksOk && testOk;
386
401
  }
@@ -404,7 +419,7 @@ class Runner extends Emittery {
404
419
  title: task.title,
405
420
  knownFailing: task.metadata.failing,
406
421
  skip: task.metadata.skipped,
407
- todo: false
422
+ todo: false,
408
423
  });
409
424
 
410
425
  if (task.metadata.skipped) {
@@ -430,7 +445,7 @@ class Runner extends Emittery {
430
445
  title: task.title,
431
446
  knownFailing: task.metadata.failing,
432
447
  skip: task.metadata.skipped,
433
- todo: false
448
+ todo: false,
434
449
  });
435
450
 
436
451
  if (task.metadata.skipped) {
@@ -456,7 +471,7 @@ class Runner extends Emittery {
456
471
  title: task.title,
457
472
  knownFailing: false,
458
473
  skip: false,
459
- todo: true
474
+ todo: true,
460
475
  });
461
476
  }
462
477
 
@@ -472,7 +487,7 @@ class Runner extends Emittery {
472
487
 
473
488
  // Note that the hooks and tests always begin running asynchronously.
474
489
  const beforePromise = this.runHooks(this.tasks.before, contextRef);
475
- const serialPromise = beforePromise.then(beforeHooksOk => { // eslint-disable-line promise/prefer-await-to-then
490
+ const serialPromise = beforePromise.then(beforeHooksOk => {
476
491
  // Don't run tests if a `before` hook failed.
477
492
  if (!beforeHooksOk) {
478
493
  return false;
@@ -494,7 +509,7 @@ class Runner extends Emittery {
494
509
  return this.runTest(task, contextRef.copy());
495
510
  }, true);
496
511
  });
497
- const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => { // eslint-disable-line promise/prefer-await-to-then
512
+ const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
498
513
  // Don't run tests if a `before` hook failed, or if `failFast` is enabled
499
514
  // and a previous serial test failed.
500
515
  if (!beforeHooksOk || (!serialOk && this.failFast)) {
@@ -508,9 +523,7 @@ class Runner extends Emittery {
508
523
 
509
524
  // If a concurrent test fails, even if `failFast` is enabled it won't
510
525
  // stop other concurrent tests from running.
511
- const allOkays = await Promise.all(concurrentTests.map(task => {
512
- return this.runTest(task, contextRef.copy());
513
- }));
526
+ const allOkays = await Promise.all(concurrentTests.map(task => this.runTest(task, contextRef.copy())));
514
527
  return allOkays.every(ok => ok);
515
528
  });
516
529
 
@@ -537,5 +550,3 @@ class Runner extends Emittery {
537
550
  this.interrupted = true;
538
551
  }
539
552
  }
540
-
541
- module.exports = Runner;
package/lib/scheduler.js CHANGED
@@ -1,47 +1,53 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const writeFileAtomic = require('write-file-atomic');
4
- const isCi = require('./is-ci');
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
5
3
 
6
- const FILENAME = 'failing-tests.json';
4
+ import writeFileAtomic from 'write-file-atomic';
7
5
 
8
- module.exports.storeFailedTestFiles = (runStatus, cacheDir) => {
9
- if (isCi || !cacheDir) {
10
- return;
11
- }
6
+ import isCi from './is-ci.js';
12
7
 
13
- try {
14
- writeFileAtomic.sync(path.join(cacheDir, FILENAME), JSON.stringify(runStatus.getFailedTestFiles()));
15
- } catch {}
16
- };
8
+ const FILENAME = 'failing-tests.json';
17
9
 
18
- // Order test-files, so that files with failing tests come first
19
- module.exports.failingTestsFirst = (selectedFiles, cacheDir, cacheEnabled) => {
20
- if (isCi || cacheEnabled === false) {
21
- return selectedFiles;
22
- }
23
-
24
- const filePath = path.join(cacheDir, FILENAME);
25
- let failedTestFiles;
26
- try {
27
- failedTestFiles = JSON.parse(fs.readFileSync(filePath));
28
- } catch {
29
- return selectedFiles;
30
- }
31
-
32
- return [...selectedFiles].sort((f, s) => {
33
- if (failedTestFiles.includes(f) && failedTestFiles.includes(s)) {
34
- return 0;
10
+ const scheduler = {
11
+ storeFailedTestFiles(runStatus, cacheDir) {
12
+ if (isCi || !cacheDir) {
13
+ return;
35
14
  }
36
15
 
37
- if (failedTestFiles.includes(f)) {
38
- return -1;
16
+ try {
17
+ writeFileAtomic.sync(path.join(cacheDir, FILENAME), JSON.stringify(runStatus.getFailedTestFiles()));
18
+ } catch {}
19
+ },
20
+
21
+ // Order test-files, so that files with failing tests come first
22
+ failingTestsFirst(selectedFiles, cacheDir, cacheEnabled) {
23
+ if (isCi || cacheEnabled === false) {
24
+ return selectedFiles;
39
25
  }
40
26
 
41
- if (failedTestFiles.includes(s)) {
42
- return 1;
27
+ const filePath = path.join(cacheDir, FILENAME);
28
+ let failedTestFiles;
29
+ try {
30
+ failedTestFiles = JSON.parse(fs.readFileSync(filePath));
31
+ } catch {
32
+ return selectedFiles;
43
33
  }
44
34
 
45
- return 0;
46
- });
35
+ return [...selectedFiles].sort((f, s) => {
36
+ if (failedTestFiles.includes(f) && failedTestFiles.includes(s)) {
37
+ return 0;
38
+ }
39
+
40
+ if (failedTestFiles.includes(f)) {
41
+ return -1;
42
+ }
43
+
44
+ if (failedTestFiles.includes(s)) {
45
+ return 1;
46
+ }
47
+
48
+ return 0;
49
+ });
50
+ },
47
51
  };
52
+
53
+ export default scheduler;