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,42 +1,45 @@
1
- 'use strict';
2
- const path = require('path');
3
- const cleanYamlObject = require('clean-yaml-object');
4
- const concordance = require('concordance');
5
- const isError = require('is-error');
6
- const slash = require('slash');
7
- const StackUtils = require('stack-utils');
8
- const assert = require('./assert');
9
- const concordanceOptions = require('./concordance-options').default;
1
+ import path from 'node:path';
2
+ import process from 'node:process';
3
+ import {fileURLToPath, pathToFileURL} from 'node:url';
4
+
5
+ import cleanYamlObject from 'clean-yaml-object';
6
+ import concordance from 'concordance';
7
+ import isError from 'is-error';
8
+ import StackUtils from 'stack-utils';
9
+
10
+ import {AssertionError} from './assert.js';
11
+ import concordanceOptions from './concordance-options.js';
10
12
 
11
13
  function isAvaAssertionError(source) {
12
- return source instanceof assert.AssertionError;
14
+ return source instanceof AssertionError;
13
15
  }
14
16
 
15
17
  function filter(propertyName, isRoot) {
16
18
  return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
17
19
  }
18
20
 
21
+ function normalizeFile(file, ...base) {
22
+ return file.startsWith('file://') ? file : pathToFileURL(path.resolve(...base, file)).toString();
23
+ }
24
+
19
25
  const stackUtils = new StackUtils();
20
26
  function extractSource(stack, testFile) {
21
27
  if (!stack || !testFile) {
22
28
  return null;
23
29
  }
24
30
 
25
- // Normalize the test file so it matches `callSite.file`.
26
- const relFile = path.relative(process.cwd(), testFile);
27
- const normalizedFile = process.platform === 'win32' ? slash(relFile) : relFile;
31
+ testFile = normalizeFile(testFile);
32
+
28
33
  for (const line of stack.split('\n')) {
29
- try {
30
- const callSite = stackUtils.parseLine(line);
31
- if (callSite.file === normalizedFile) {
32
- return {
33
- isDependency: false,
34
- isWithinProject: true,
35
- file: path.resolve(process.cwd(), callSite.file),
36
- line: callSite.line
37
- };
38
- }
39
- } catch {}
34
+ const callSite = stackUtils.parseLine(line);
35
+ if (callSite && normalizeFile(callSite.file) === testFile) {
36
+ return {
37
+ isDependency: false,
38
+ isWithinProject: true,
39
+ file: testFile,
40
+ line: callSite.line,
41
+ };
42
+ }
40
43
  }
41
44
 
42
45
  return null;
@@ -52,8 +55,8 @@ function buildSource(source) {
52
55
  // directory set to the project directory.
53
56
  const projectDir = process.cwd();
54
57
 
55
- const file = path.resolve(projectDir, source.file.trim());
56
- const rel = path.relative(projectDir, file);
58
+ const file = normalizeFile(source.file.trim(), projectDir);
59
+ const rel = path.relative(projectDir, fileURLToPath(file));
57
60
 
58
61
  const [segment] = rel.split(path.sep);
59
62
  const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':'));
@@ -63,7 +66,7 @@ function buildSource(source) {
63
66
  isDependency,
64
67
  isWithinProject,
65
68
  file,
66
- line: source.line
69
+ line: source.line,
67
70
  };
68
71
  }
69
72
 
@@ -75,7 +78,7 @@ function trySerializeError(error, shouldBeautifyStack, testFile) {
75
78
  nonErrorObject: false,
76
79
  source: extractSource(stack, testFile),
77
80
  stack,
78
- shouldBeautifyStack
81
+ shouldBeautifyStack,
79
82
  };
80
83
 
81
84
  if (error.actualStack) {
@@ -86,7 +89,6 @@ function trySerializeError(error, shouldBeautifyStack, testFile) {
86
89
  retval.improperUsage = error.improperUsage;
87
90
  retval.message = error.message;
88
91
  retval.name = error.name;
89
- retval.statements = error.statements;
90
92
  retval.values = error.values;
91
93
 
92
94
  if (error.fixedSource) {
@@ -143,12 +145,12 @@ function trySerializeError(error, shouldBeautifyStack, testFile) {
143
145
  return retval;
144
146
  }
145
147
 
146
- function serializeError(origin, shouldBeautifyStack, error, testFile) {
148
+ export default function serializeError(origin, shouldBeautifyStack, error, testFile) {
147
149
  if (!isError(error)) {
148
150
  return {
149
151
  avaAssertionError: false,
150
152
  nonErrorObject: true,
151
- formatted: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions)
153
+ formatted: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions),
152
154
  };
153
155
  }
154
156
 
@@ -162,9 +164,7 @@ function serializeError(origin, shouldBeautifyStack, error, testFile) {
162
164
  name: replacement.name,
163
165
  message: replacement.message,
164
166
  stack: replacement.stack,
165
- summary: replacement.message
167
+ summary: replacement.message,
166
168
  };
167
169
  }
168
170
  }
169
-
170
- module.exports = serializeError;
@@ -1,19 +1,19 @@
1
- 'use strict';
2
-
3
- const crypto = require('crypto');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const zlib = require('zlib');
7
-
8
- const concordance = require('concordance');
9
- const indentString = require('indent-string');
10
- const convertSourceMap = require('convert-source-map');
11
- const slash = require('slash');
12
- const writeFileAtomic = require('write-file-atomic');
13
- const mem = require('mem');
14
- const cbor = require('cbor');
15
-
16
- const concordanceOptions = require('./concordance-options').snapshotManager;
1
+ import {Buffer} from 'node:buffer';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import {findSourceMap} from 'node:module';
5
+ import path from 'node:path';
6
+ import {fileURLToPath} from 'node:url';
7
+ import zlib from 'node:zlib';
8
+
9
+ import cbor from 'cbor';
10
+ import concordance from 'concordance';
11
+ import indentString from 'indent-string';
12
+ import mem from 'mem';
13
+ import slash from 'slash';
14
+ import writeFileAtomic from 'write-file-atomic';
15
+
16
+ import {snapshotManager as concordanceOptions} from './concordance-options.js';
17
17
 
18
18
  // Increment if encoding layout or Concordance serialization versions change. Previous AVA versions will not be able to
19
19
  // decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself.
@@ -30,24 +30,22 @@ const REPORT_TRAILING_NEWLINE = Buffer.from('\n', 'ascii');
30
30
 
31
31
  const SHA_256_HASH_LENGTH = 32;
32
32
 
33
- class SnapshotError extends Error {
33
+ export class SnapshotError extends Error {
34
34
  constructor(message, snapPath) {
35
35
  super(message);
36
36
  this.name = 'SnapshotError';
37
37
  this.snapPath = snapPath;
38
38
  }
39
39
  }
40
- exports.SnapshotError = SnapshotError;
41
40
 
42
- class ChecksumError extends SnapshotError {
41
+ export class ChecksumError extends SnapshotError {
43
42
  constructor(snapPath) {
44
43
  super('Checksum mismatch', snapPath);
45
44
  this.name = 'ChecksumError';
46
45
  }
47
46
  }
48
- exports.ChecksumError = ChecksumError;
49
47
 
50
- class VersionMismatchError extends SnapshotError {
48
+ export class VersionMismatchError extends SnapshotError {
51
49
  constructor(snapPath, version) {
52
50
  super('Unexpected snapshot version', snapPath);
53
51
  this.name = 'VersionMismatchError';
@@ -55,20 +53,25 @@ class VersionMismatchError extends SnapshotError {
55
53
  this.expectedVersion = VERSION;
56
54
  }
57
55
  }
58
- exports.VersionMismatchError = VersionMismatchError;
56
+
57
+ export class InvalidSnapshotError extends SnapshotError {
58
+ constructor(snapPath) {
59
+ super('Invalid snapshot file', snapPath);
60
+ this.name = 'InvalidSnapshotError';
61
+ }
62
+ }
59
63
 
60
64
  const LEGACY_SNAPSHOT_HEADER = Buffer.from('// Jest Snapshot v1');
61
65
  function isLegacySnapshot(buffer) {
62
66
  return LEGACY_SNAPSHOT_HEADER.equals(buffer.slice(0, LEGACY_SNAPSHOT_HEADER.byteLength));
63
67
  }
64
68
 
65
- class LegacyError extends SnapshotError {
69
+ export class LegacyError extends SnapshotError {
66
70
  constructor(snapPath) {
67
71
  super('Legacy snapshot file', snapPath);
68
72
  this.name = 'LegacyError';
69
73
  }
70
74
  }
71
- exports.LegacyError = LegacyError;
72
75
 
73
76
  function tryRead(file) {
74
77
  try {
@@ -85,14 +88,16 @@ function tryRead(file) {
85
88
  function formatEntry(snapshot, index) {
86
89
  const {
87
90
  data,
88
- label = `Snapshot ${index + 1}` // Human-readable labels start counting at 1.
91
+ label = `Snapshot ${index + 1}`, // Human-readable labels start counting at 1.
89
92
  } = snapshot;
90
93
 
91
- const description = data ?
92
- concordance.formatDescriptor(concordance.deserialize(data), concordanceOptions) :
93
- '<No Data>';
94
+ const description = data
95
+ ? concordance.formatDescriptor(concordance.deserialize(data), concordanceOptions)
96
+ : '<No Data>';
94
97
 
95
- return `> ${label}\n\n${indentString(description, 4)}`;
98
+ const blockquote = label.split(/\n/).map(line => '> ' + line).join('\n');
99
+
100
+ return `${blockquote}\n\n${indentString(description, 4)}`;
96
101
  }
97
102
 
98
103
  function combineEntries({blocks}) {
@@ -173,14 +178,14 @@ function sortBlocks(blocksByTitle, blockIndices) {
173
178
  }
174
179
 
175
180
  return a - b;
176
- }
181
+ },
177
182
  );
178
183
  }
179
184
 
180
- function encodeSnapshots(snapshotData) {
181
- const encoded = cbor.encodeOne(snapshotData, {
185
+ async function encodeSnapshots(snapshotData) {
186
+ const encoded = await cbor.encodeAsync(snapshotData, {
182
187
  omitUndefinedProperties: true,
183
- canonical: true
188
+ canonical: true,
184
189
  });
185
190
  const compressed = zlib.gzipSync(encoded);
186
191
  compressed[9] = 0x03; // Override the GZip header containing the OS to always be Linux
@@ -189,7 +194,7 @@ function encodeSnapshots(snapshotData) {
189
194
  READABLE_PREFIX,
190
195
  VERSION_HEADER,
191
196
  sha256sum,
192
- compressed
197
+ compressed,
193
198
  ], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength + SHA_256_HASH_LENGTH + compressed.byteLength);
194
199
  }
195
200
 
@@ -200,7 +205,12 @@ function decodeSnapshots(buffer, snapPath) {
200
205
 
201
206
  // The version starts after the readable prefix, which is ended by a newline
202
207
  // byte (0x0A).
203
- const versionOffset = buffer.indexOf(0x0A) + 1;
208
+ const newline = buffer.indexOf(0x0A);
209
+ if (newline === -1) {
210
+ throw new InvalidSnapshotError(snapPath);
211
+ }
212
+
213
+ const versionOffset = newline + 1;
204
214
  const version = buffer.readUInt16LE(versionOffset);
205
215
  if (version !== VERSION) {
206
216
  throw new VersionMismatchError(snapPath, version);
@@ -341,14 +351,14 @@ class Manager {
341
351
  this.recordSerialized({belongsTo, index, ...snapshot});
342
352
  }
343
353
 
344
- save() {
354
+ async save() {
345
355
  const {dir, relFile, snapFile, snapPath, reportPath} = this;
346
356
 
347
357
  if (this.updating && this.newBlocksByTitle.size === 0) {
348
- return [
349
- ...cleanFile(snapPath),
350
- ...cleanFile(reportPath)
351
- ];
358
+ return {
359
+ changedFiles: [cleanFile(snapPath), cleanFile(reportPath)].flat(),
360
+ temporaryFiles: [],
361
+ };
352
362
  }
353
363
 
354
364
  if (!this.hasChanges) {
@@ -357,42 +367,45 @@ class Manager {
357
367
 
358
368
  const snapshots = {
359
369
  blocks: sortBlocks(this.newBlocksByTitle, this.blockIndices).map(
360
- ([title, block]) => ({title, ...block})
361
- )
370
+ ([title, block]) => ({title, ...block}),
371
+ ),
362
372
  };
363
373
 
364
- const buffer = encodeSnapshots(snapshots);
374
+ const buffer = await encodeSnapshots(snapshots);
365
375
  const reportBuffer = generateReport(relFile, snapFile, snapshots);
366
376
 
367
- fs.mkdirSync(dir, {recursive: true});
368
-
369
- const paths = [snapPath, reportPath];
370
- const tmpfileCreated = tmpfile => paths.push(tmpfile);
371
- writeFileAtomic.sync(snapPath, buffer, {tmpfileCreated});
372
- writeFileAtomic.sync(reportPath, reportBuffer, {tmpfileCreated});
373
- return paths;
377
+ await fs.promises.mkdir(dir, {recursive: true});
378
+
379
+ const temporaryFiles = [];
380
+ const tmpfileCreated = file => temporaryFiles.push(file);
381
+ await Promise.all([
382
+ writeFileAtomic(snapPath, buffer, {tmpfileCreated}),
383
+ writeFileAtomic(reportPath, reportBuffer, {tmpfileCreated}),
384
+ ]);
385
+ return {
386
+ changedFiles: [snapPath, reportPath],
387
+ temporaryFiles,
388
+ };
374
389
  }
375
390
  }
376
391
 
377
392
  const resolveSourceFile = mem(file => {
378
- const testDir = path.dirname(file);
379
- const buffer = tryRead(file);
380
- if (!buffer) {
381
- return file; // Assume the file is stubbed in our test suite.
393
+ const sourceMap = findSourceMap(file);
394
+ if (sourceMap === undefined) {
395
+ return file;
382
396
  }
383
397
 
384
- const source = buffer.toString();
385
- const converter = convertSourceMap.fromSource(source) || convertSourceMap.fromMapFileSource(source, testDir);
386
- if (converter) {
387
- const map = converter.toObject();
388
- const firstSource = `${map.sourceRoot || ''}${map.sources[0]}`;
389
- return path.resolve(testDir, firstSource);
398
+ const {payload} = sourceMap;
399
+ if (payload.sources.length === 0) { // Hypothetical?
400
+ return file;
390
401
  }
391
402
 
392
- return file;
403
+ return payload.sources[0].startsWith('file://')
404
+ ? fileURLToPath(payload.sources[0])
405
+ : payload.sources[0];
393
406
  });
394
407
 
395
- const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
408
+ export const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
396
409
  const testDir = path.dirname(resolveSourceFile(file));
397
410
  if (fixedLocation) {
398
411
  const relativeTestLocation = path.relative(projectDir, testDir);
@@ -411,8 +424,6 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
411
424
  return testDir;
412
425
  }, {cacheKey: ([{file}]) => file});
413
426
 
414
- exports.determineSnapshotDir = determineSnapshotDir;
415
-
416
427
  function determineSnapshotPaths({file, fixedLocation, projectDir}) {
417
428
  const dir = determineSnapshotDir({file, fixedLocation, projectDir});
418
429
  const relFile = path.relative(projectDir, resolveSourceFile(file));
@@ -426,7 +437,7 @@ function determineSnapshotPaths({file, fixedLocation, projectDir}) {
426
437
  snapFile,
427
438
  reportFile,
428
439
  snapPath: path.join(dir, snapFile),
429
- reportPath: path.join(dir, reportFile)
440
+ reportPath: path.join(dir, reportFile),
430
441
  };
431
442
  }
432
443
 
@@ -443,14 +454,14 @@ function cleanFile(file) {
443
454
  }
444
455
  }
445
456
 
446
- function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
457
+ export function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
447
458
  // Keep runner unit tests that use `new Runner()` happy
448
459
  if (file === undefined || projectDir === undefined) {
449
460
  return new Manager({
450
461
  recordNewSnapshots,
451
462
  updating,
452
463
  oldBlocksByTitle: new Map(),
453
- newBlocksByTitle: new Map()
464
+ newBlocksByTitle: new Map(),
454
465
  });
455
466
  }
456
467
 
@@ -463,7 +474,7 @@ function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
463
474
  updating,
464
475
  ...paths,
465
476
  oldBlocksByTitle: new Map(),
466
- newBlocksByTitle: new Map()
477
+ newBlocksByTitle: new Map(),
467
478
  });
468
479
  }
469
480
 
@@ -477,11 +488,9 @@ function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
477
488
  blocksByTitle = new Map();
478
489
 
479
490
  if (!updating) { // Discard all decoding errors when updating snapshots
480
- if (error instanceof SnapshotError) {
481
- snapshotError = error;
482
- } else {
483
- throw error;
484
- }
491
+ snapshotError = error instanceof SnapshotError
492
+ ? error
493
+ : new InvalidSnapshotError(paths.snapPath);
485
494
  }
486
495
  }
487
496
 
@@ -491,8 +500,6 @@ function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
491
500
  ...paths,
492
501
  oldBlocksByTitle: blocksByTitle,
493
502
  newBlocksByTitle: updating ? new Map() : blocksByTitle,
494
- error: snapshotError
503
+ error: snapshotError,
495
504
  });
496
505
  }
497
-
498
- exports.load = load;