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
package/lib/assert.js CHANGED
@@ -1,5 +1,6 @@
1
+ import {isNativeError} from 'node:util/types';
2
+
1
3
  import concordance from 'concordance';
2
- import isError from 'is-error';
3
4
  import isPromise from 'is-promise';
4
5
 
5
6
  import concordanceOptions from './concordance-options.js';
@@ -27,61 +28,55 @@ function formatWithLabel(label, value) {
27
28
  return formatDescriptorWithLabel(label, concordance.describe(value, concordanceOptions));
28
29
  }
29
30
 
30
- const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
31
31
  const noop = () => {};
32
32
  const notImplemented = () => {
33
33
  throw new Error('not implemented');
34
34
  };
35
35
 
36
36
  export class AssertionError extends Error {
37
- constructor(options) {
38
- super(options.message || '');
37
+ constructor(message = '', {
38
+ assertion,
39
+ assertionStack = getAssertionStack(AssertionError),
40
+ formattedDetails = [],
41
+ improperUsage = null,
42
+ cause,
43
+ } = {}) {
44
+ super(message, {cause});
39
45
  this.name = 'AssertionError';
40
46
 
41
- this.assertion = options.assertion;
42
- this.fixedSource = options.fixedSource;
43
- this.improperUsage = options.improperUsage || false;
44
- this.actualStack = options.actualStack;
45
- this.operator = options.operator;
46
- this.values = options.values || [];
47
-
48
- // Raw expected and actual objects are stored for custom reporters
49
- // (such as wallaby.js), that manage worker processes directly and
50
- // use the values for custom diff views
51
- this.raw = options.raw;
52
-
53
- this.savedError = options.savedError || getErrorWithLongStackTrace();
47
+ this.assertion = assertion;
48
+ this.assertionStack = assertionStack;
49
+ this.improperUsage = improperUsage;
50
+ this.formattedDetails = formattedDetails;
54
51
  }
55
52
  }
56
53
 
57
- export function checkAssertionMessage(assertion, message) {
54
+ export function checkAssertionMessage(message, assertion) {
58
55
  if (message === undefined || typeof message === 'string') {
59
56
  return true;
60
57
  }
61
58
 
62
- return new AssertionError({
59
+ return new AssertionError('The assertion message must be a string', {
63
60
  assertion,
64
- improperUsage: true,
65
- message: 'The assertion message must be a string',
66
- values: [formatWithLabel('Called with:', message)],
61
+ formattedDetails: [formatWithLabel('Called with:', message)],
67
62
  });
68
63
  }
69
64
 
70
- function getErrorWithLongStackTrace() {
71
- const limitBefore = Error.stackTraceLimit;
65
+ export function getAssertionStack(constructorOpt = getAssertionStack) {
66
+ const {stackTraceLimit: limitBefore} = Error;
72
67
  Error.stackTraceLimit = Number.POSITIVE_INFINITY;
73
- const error = new Error(); // eslint-disable-line unicorn/error-message
68
+ const temporary = {};
69
+ Error.captureStackTrace(temporary, constructorOpt);
74
70
  Error.stackTraceLimit = limitBefore;
75
- return error;
71
+ return temporary.stack;
76
72
  }
77
73
 
78
74
  function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity
79
75
  if (numberArgs === 1 || expectations === null || expectations === undefined) {
80
76
  if (expectations === null) {
81
- throw new AssertionError({
77
+ throw new AssertionError(`The second argument to \`${assertion}\` must be an expectation object or \`undefined\``, {
82
78
  assertion,
83
- message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``,
84
- values: [formatWithLabel('Called with:', expectations)],
79
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
85
80
  });
86
81
  }
87
82
 
@@ -94,46 +89,48 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d
94
89
  || Array.isArray(expectations)
95
90
  || Object.keys(expectations).length === 0
96
91
  ) {
97
- throw new AssertionError({
92
+ throw new AssertionError(`The second argument to \`${assertion}\` must be an expectation object, \`null\` or \`undefined\``, {
98
93
  assertion,
99
- message: `The second argument to \`t.${assertion}()\` must be an expectation object, \`null\` or \`undefined\``,
100
- values: [formatWithLabel('Called with:', expectations)],
94
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
101
95
  });
102
96
  } else {
103
- if (hasOwnProperty(expectations, 'instanceOf') && typeof expectations.instanceOf !== 'function') {
104
- throw new AssertionError({
97
+ if (Object.hasOwn(expectations, 'instanceOf') && typeof expectations.instanceOf !== 'function') {
98
+ throw new AssertionError(`The \`instanceOf\` property of the second argument to \`${assertion}\` must be a function`, {
105
99
  assertion,
106
- message: `The \`instanceOf\` property of the second argument to \`t.${assertion}()\` must be a function`,
107
- values: [formatWithLabel('Called with:', expectations)],
100
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
108
101
  });
109
102
  }
110
103
 
111
104
  if (
112
- hasOwnProperty(expectations, 'message')
105
+ Object.hasOwn(expectations, 'message')
113
106
  && typeof expectations.message !== 'string'
114
107
  && !(expectations.message instanceof RegExp)
115
108
  && !(typeof expectations.message === 'function')
116
109
  ) {
117
- throw new AssertionError({
110
+ throw new AssertionError(`The \`message\` property of the second argument to \`${assertion}\` must be a string, regular expression or a function`, {
118
111
  assertion,
119
- message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`,
120
- values: [formatWithLabel('Called with:', expectations)],
112
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
121
113
  });
122
114
  }
123
115
 
124
- if (hasOwnProperty(expectations, 'name') && typeof expectations.name !== 'string') {
125
- throw new AssertionError({
116
+ if (Object.hasOwn(expectations, 'name') && typeof expectations.name !== 'string') {
117
+ throw new AssertionError(`The \`name\` property of the second argument to \`${assertion}\` must be a string`, {
126
118
  assertion,
127
- message: `The \`name\` property of the second argument to \`t.${assertion}()\` must be a string`,
128
- values: [formatWithLabel('Called with:', expectations)],
119
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
129
120
  });
130
121
  }
131
122
 
132
- if (hasOwnProperty(expectations, 'code') && typeof expectations.code !== 'string' && typeof expectations.code !== 'number') {
133
- throw new AssertionError({
123
+ if (Object.hasOwn(expectations, 'code') && typeof expectations.code !== 'string' && typeof expectations.code !== 'number') {
124
+ throw new AssertionError(`The \`code\` property of the second argument to \`${assertion}\` must be a string or number`, {
134
125
  assertion,
135
- message: `The \`code\` property of the second argument to \`t.${assertion}()\` must be a string or number`,
136
- values: [formatWithLabel('Called with:', expectations)],
126
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
127
+ });
128
+ }
129
+
130
+ if (Object.hasOwn(expectations, 'any') && typeof expectations.any !== 'boolean') {
131
+ throw new AssertionError(`The \`any\` property of the second argument to \`${assertion}\` must be a boolean`, {
132
+ assertion,
133
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
137
134
  });
138
135
  }
139
136
 
@@ -143,15 +140,15 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d
143
140
  case 'is':
144
141
  case 'message':
145
142
  case 'name':
146
- case 'code': {
143
+ case 'code':
144
+ case 'any': {
147
145
  continue;
148
146
  }
149
147
 
150
148
  default: {
151
- throw new AssertionError({
149
+ throw new AssertionError(`The second argument to \`${assertion}\` contains unexpected properties`, {
152
150
  assertion,
153
- message: `The second argument to \`t.${assertion}()\` contains unexpected properties`,
154
- values: [formatWithLabel('Called with:', expectations)],
151
+ formattedDetails: [formatWithLabel('Called with:', expectations)],
155
152
  });
156
153
  }
157
154
  }
@@ -163,25 +160,23 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d
163
160
 
164
161
  // Note: this function *must* throw exceptions, since it can be used
165
162
  // as part of a pending assertion for promises.
166
- function assertExpectations({assertion, actual, expectations, message, prefix, savedError}) {
167
- if (!isError(actual)) {
168
- throw new AssertionError({
163
+ function assertExpectations({actual, expectations, message, prefix, assertion, assertionStack}) {
164
+ const allowThrowAnything = Object.hasOwn(expectations, 'any') && expectations.any;
165
+ if (!isNativeError(actual) && !allowThrowAnything) {
166
+ throw new AssertionError(message, {
169
167
  assertion,
170
- message,
171
- savedError,
172
- values: [formatWithLabel(`${prefix} exception that is not an error:`, actual)],
168
+ assertionStack,
169
+ cause: actual,
170
+ formattedDetails: [formatWithLabel(`${prefix} exception that is not an error:`, actual)],
173
171
  });
174
172
  }
175
173
 
176
- const actualStack = actual.stack;
177
-
178
- if (hasOwnProperty(expectations, 'is') && actual !== expectations.is) {
179
- throw new AssertionError({
174
+ if (Object.hasOwn(expectations, 'is') && actual !== expectations.is) {
175
+ throw new AssertionError(message, {
180
176
  assertion,
181
- message,
182
- savedError,
183
- actualStack,
184
- values: [
177
+ assertionStack,
178
+ cause: actual,
179
+ formattedDetails: [
185
180
  formatWithLabel(`${prefix} unexpected exception:`, actual),
186
181
  formatWithLabel('Expected to be strictly equal to:', expectations.is),
187
182
  ],
@@ -189,12 +184,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
189
184
  }
190
185
 
191
186
  if (expectations.instanceOf && !(actual instanceof expectations.instanceOf)) {
192
- throw new AssertionError({
187
+ throw new AssertionError(message, {
193
188
  assertion,
194
- message,
195
- savedError,
196
- actualStack,
197
- values: [
189
+ assertionStack,
190
+ cause: actual,
191
+ formattedDetails: [
198
192
  formatWithLabel(`${prefix} unexpected exception:`, actual),
199
193
  formatWithLabel('Expected instance of:', expectations.instanceOf),
200
194
  ],
@@ -202,12 +196,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
202
196
  }
203
197
 
204
198
  if (typeof expectations.name === 'string' && actual.name !== expectations.name) {
205
- throw new AssertionError({
199
+ throw new AssertionError(message, {
206
200
  assertion,
207
- message,
208
- savedError,
209
- actualStack,
210
- values: [
201
+ assertionStack,
202
+ cause: actual,
203
+ formattedDetails: [
211
204
  formatWithLabel(`${prefix} unexpected exception:`, actual),
212
205
  formatWithLabel('Expected name to equal:', expectations.name),
213
206
  ],
@@ -215,12 +208,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
215
208
  }
216
209
 
217
210
  if (typeof expectations.message === 'string' && actual.message !== expectations.message) {
218
- throw new AssertionError({
211
+ throw new AssertionError(message, {
219
212
  assertion,
220
- message,
221
- savedError,
222
- actualStack,
223
- values: [
213
+ assertionStack,
214
+ cause: actual,
215
+ formattedDetails: [
224
216
  formatWithLabel(`${prefix} unexpected exception:`, actual),
225
217
  formatWithLabel('Expected message to equal:', expectations.message),
226
218
  ],
@@ -228,12 +220,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
228
220
  }
229
221
 
230
222
  if (expectations.message instanceof RegExp && !expectations.message.test(actual.message)) {
231
- throw new AssertionError({
223
+ throw new AssertionError(message, {
232
224
  assertion,
233
- message,
234
- savedError,
235
- actualStack,
236
- values: [
225
+ assertionStack,
226
+ cause: actual,
227
+ formattedDetails: [
237
228
  formatWithLabel(`${prefix} unexpected exception:`, actual),
238
229
  formatWithLabel('Expected message to match:', expectations.message),
239
230
  ],
@@ -241,12 +232,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
241
232
  }
242
233
 
243
234
  if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) {
244
- throw new AssertionError({
235
+ throw new AssertionError(message, {
245
236
  assertion,
246
- message,
247
- savedError,
248
- actualStack,
249
- values: [
237
+ assertionStack,
238
+ cause: actual,
239
+ formattedDetails: [
250
240
  formatWithLabel(`${prefix} unexpected exception:`, actual),
251
241
  formatWithLabel('Expected message to return true:', expectations.message),
252
242
  ],
@@ -254,12 +244,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s
254
244
  }
255
245
 
256
246
  if (expectations.code !== undefined && actual.code !== expectations.code) {
257
- throw new AssertionError({
247
+ throw new AssertionError(message, {
258
248
  assertion,
259
- message,
260
- savedError,
261
- actualStack,
262
- values: [
249
+ assertionStack,
250
+ cause: actual,
251
+ formattedDetails: [
263
252
  formatWithLabel(`${prefix} unexpected exception:`, actual),
264
253
  formatWithLabel('Expected code to equal:', expectations.code),
265
254
  ],
@@ -272,6 +261,7 @@ export class Assertions {
272
261
  pass = notImplemented,
273
262
  pending = notImplemented,
274
263
  fail = notImplemented,
264
+ failPending = notImplemented,
275
265
  skip = notImplemented,
276
266
  compareWithSnapshot = notImplemented,
277
267
  experiments = {},
@@ -282,142 +272,99 @@ export class Assertions {
282
272
  return assertionFn;
283
273
  };
284
274
 
285
- const checkMessage = (assertion, message) => {
286
- const result = checkAssertionMessage(assertion, message);
287
- if (result === true) {
288
- return true;
275
+ const assertMessage = (message, assertion) => {
276
+ const result = checkAssertionMessage(message, assertion);
277
+ if (result !== true) {
278
+ throw fail(result);
289
279
  }
290
-
291
- fail(result);
292
- return false;
293
280
  };
294
281
 
295
- this.pass = withSkip(() => {
296
- pass();
297
- return true;
298
- });
282
+ this.pass = withSkip(() => pass());
299
283
 
300
284
  this.fail = withSkip(message => {
301
- if (!checkMessage('fail', message)) {
302
- return false;
303
- }
285
+ assertMessage(message, 't.fail()');
304
286
 
305
- fail(new AssertionError({
306
- assertion: 'fail',
307
- message: message || 'Test failed via `t.fail()`',
287
+ throw fail(new AssertionError(message ?? 'Test failed via `t.fail()`', {
288
+ assertion: 't.fail()',
308
289
  }));
309
-
310
- return false;
311
290
  });
312
291
 
313
292
  this.is = withSkip((actual, expected, message) => {
314
- if (!checkMessage('is', message)) {
315
- return false;
316
- }
293
+ assertMessage(message, 't.is()');
317
294
 
318
295
  if (Object.is(actual, expected)) {
319
- pass();
320
- return true;
296
+ return pass();
321
297
  }
322
298
 
323
299
  const result = concordance.compare(actual, expected, concordanceOptions);
324
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
325
- const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
300
+ const actualDescriptor = result.actual ?? concordance.describe(actual, concordanceOptions);
301
+ const expectedDescriptor = result.expected ?? concordance.describe(expected, concordanceOptions);
326
302
 
327
303
  if (result.pass) {
328
- fail(new AssertionError({
329
- assertion: 'is',
330
- message,
331
- raw: {actual, expected},
332
- values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)],
304
+ throw fail(new AssertionError(message, {
305
+ assertion: 't.is()',
306
+ formattedDetails: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)],
333
307
  }));
334
308
  } else {
335
- fail(new AssertionError({
336
- assertion: 'is',
337
- message,
338
- raw: {actual, expected},
339
- values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
309
+ throw fail(new AssertionError(message, {
310
+ assertion: 't.is()',
311
+ formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
340
312
  }));
341
313
  }
342
-
343
- return false;
344
314
  });
345
315
 
346
316
  this.not = withSkip((actual, expected, message) => {
347
- if (!checkMessage('not', message)) {
348
- return false;
349
- }
317
+ assertMessage(message, 't.not()');
350
318
 
351
319
  if (Object.is(actual, expected)) {
352
- fail(new AssertionError({
353
- assertion: 'not',
354
- message,
355
- raw: {actual, expected},
356
- values: [formatWithLabel('Value is the same as:', actual)],
320
+ throw fail(new AssertionError(message, {
321
+ assertion: 't.not()',
322
+ formattedDetails: [formatWithLabel('Value is the same as:', actual)],
357
323
  }));
358
- return false;
359
324
  }
360
325
 
361
- pass();
362
- return true;
326
+ return pass();
363
327
  });
364
328
 
365
329
  this.deepEqual = withSkip((actual, expected, message) => {
366
- if (!checkMessage('deepEqual', message)) {
367
- return false;
368
- }
330
+ assertMessage(message, 't.deepEqual()');
369
331
 
370
332
  const result = concordance.compare(actual, expected, concordanceOptions);
371
333
  if (result.pass) {
372
- pass();
373
- return true;
334
+ return pass();
374
335
  }
375
336
 
376
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
377
- const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
378
- fail(new AssertionError({
379
- assertion: 'deepEqual',
380
- message,
381
- raw: {actual, expected},
382
- values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
337
+ const actualDescriptor = result.actual ?? concordance.describe(actual, concordanceOptions);
338
+ const expectedDescriptor = result.expected ?? concordance.describe(expected, concordanceOptions);
339
+ throw fail(new AssertionError(message, {
340
+ assertion: 't.deepEqual()',
341
+ formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
383
342
  }));
384
- return false;
385
343
  });
386
344
 
387
345
  this.notDeepEqual = withSkip((actual, expected, message) => {
388
- if (!checkMessage('notDeepEqual', message)) {
389
- return false;
390
- }
346
+ assertMessage(message, 't.notDeepEqual()');
391
347
 
392
348
  const result = concordance.compare(actual, expected, concordanceOptions);
393
349
  if (result.pass) {
394
- const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
395
- fail(new AssertionError({
396
- assertion: 'notDeepEqual',
397
- message,
398
- raw: {actual, expected},
399
- values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)],
350
+ const actualDescriptor = result.actual ?? concordance.describe(actual, concordanceOptions);
351
+ throw fail(new AssertionError(message, {
352
+ assertion: 't.notDeepEqual()',
353
+ formattedDetails: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)],
400
354
  }));
401
- return false;
402
355
  }
403
356
 
404
- pass();
405
- return true;
357
+ return pass();
406
358
  });
407
359
 
408
360
  this.like = withSkip((actual, selector, message) => {
409
- if (!checkMessage('like', message)) {
410
- return false;
411
- }
361
+ assertMessage(message, 't.like()');
412
362
 
413
363
  if (!isLikeSelector(selector)) {
414
- fail(new AssertionError({
415
- assertion: 'like',
416
- improperUsage: true,
417
- message: '`t.like()` selector must be a non-empty object',
418
- values: [formatWithLabel('Called with:', selector)],
364
+ throw fail(new AssertionError('`t.like()` selector must be a non-empty object', {
365
+ assertion: 't.like()',
366
+ formattedDetails: [formatWithLabel('Called with:', selector)],
419
367
  }));
420
- return false;
421
368
  }
422
369
 
423
370
  let comparable;
@@ -425,13 +372,10 @@ export class Assertions {
425
372
  comparable = selectComparable(actual, selector);
426
373
  } catch (error) {
427
374
  if (error === CIRCULAR_SELECTOR) {
428
- fail(new AssertionError({
429
- assertion: 'like',
430
- improperUsage: true,
431
- message: '`t.like()` selector must not contain circular references',
432
- values: [formatWithLabel('Called with:', selector)],
375
+ throw fail(new AssertionError('`t.like()` selector must not contain circular references', {
376
+ assertion: 't.like()',
377
+ formattedDetails: [formatWithLabel('Called with:', selector)],
433
378
  }));
434
- return false;
435
379
  }
436
380
 
437
381
  throw error;
@@ -439,19 +383,15 @@ export class Assertions {
439
383
 
440
384
  const result = concordance.compare(comparable, selector, concordanceOptions);
441
385
  if (result.pass) {
442
- pass();
443
- return true;
386
+ return pass();
444
387
  }
445
388
 
446
- const actualDescriptor = result.actual || concordance.describe(comparable, concordanceOptions);
447
- const expectedDescriptor = result.expected || concordance.describe(selector, concordanceOptions);
448
- fail(new AssertionError({
449
- assertion: 'like',
450
- message,
451
- values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
389
+ const actualDescriptor = result.actual ?? concordance.describe(comparable, concordanceOptions);
390
+ const expectedDescriptor = result.expected ?? concordance.describe(selector, concordanceOptions);
391
+ throw fail(new AssertionError(message, {
392
+ assertion: 't.like()',
393
+ formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
452
394
  }));
453
-
454
- return false;
455
395
  });
456
396
 
457
397
  this.throws = withSkip((...args) => {
@@ -460,25 +400,20 @@ export class Assertions {
460
400
  // to the function.
461
401
  let [fn, expectations, message] = args;
462
402
 
463
- if (!checkMessage('throws', message)) {
464
- return;
465
- }
403
+ assertMessage(message, 't.throws()');
466
404
 
467
405
  if (typeof fn !== 'function') {
468
- fail(new AssertionError({
469
- assertion: 'throws',
470
- improperUsage: true,
471
- message: '`t.throws()` must be called with a function',
472
- values: [formatWithLabel('Called with:', fn)],
406
+ throw fail(new AssertionError('`t.throws()` must be called with a function', {
407
+ assertion: 't.throws()',
408
+ improperUsage: {assertion: 'throws'},
409
+ formattedDetails: [formatWithLabel('Called with:', fn)],
473
410
  }));
474
- return;
475
411
  }
476
412
 
477
413
  try {
478
- expectations = validateExpectations('throws', expectations, args.length, experiments);
414
+ expectations = validateExpectations('t.throws()', expectations, args.length, experiments);
479
415
  } catch (error) {
480
- fail(error);
481
- return;
416
+ throw fail(error);
482
417
  }
483
418
 
484
419
  let retval;
@@ -488,29 +423,25 @@ export class Assertions {
488
423
  if (isPromise(retval)) {
489
424
  // Here isPromise() checks if something is "promise like". Cast to an actual promise.
490
425
  Promise.resolve(retval).catch(noop);
491
- fail(new AssertionError({
492
- assertion: 'throws',
493
- message,
494
- values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)],
426
+ throw fail(new AssertionError(message, {
427
+ assertion: 't.throws()',
428
+ formattedDetails: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)],
495
429
  }));
496
- return;
497
430
  }
498
431
  } catch (error) {
499
432
  actual = error;
500
433
  }
501
434
 
502
435
  if (!actual) {
503
- fail(new AssertionError({
504
- assertion: 'throws',
505
- message,
506
- values: [formatWithLabel('Function returned:', retval)],
436
+ throw fail(new AssertionError(message, {
437
+ assertion: 't.throws()',
438
+ formattedDetails: [formatWithLabel('Function returned:', retval)],
507
439
  }));
508
- return;
509
440
  }
510
441
 
511
442
  try {
512
443
  assertExpectations({
513
- assertion: 'throws',
444
+ assertion: 't.throws()',
514
445
  actual,
515
446
  expectations,
516
447
  message,
@@ -519,63 +450,62 @@ export class Assertions {
519
450
  pass();
520
451
  return actual;
521
452
  } catch (error) {
522
- fail(error);
453
+ throw fail(error);
523
454
  }
524
455
  });
525
456
 
526
457
  this.throwsAsync = withSkip(async (...args) => {
527
458
  let [thrower, expectations, message] = args;
528
459
 
529
- if (!checkMessage('throwsAsync', message)) {
530
- return;
460
+ try {
461
+ assertMessage(message, 't.throwsAsync()');
462
+ } catch (error) {
463
+ Promise.resolve(thrower).catch(noop);
464
+ throw error;
531
465
  }
532
466
 
533
467
  if (typeof thrower !== 'function' && !isPromise(thrower)) {
534
- fail(new AssertionError({
535
- assertion: 'throwsAsync',
536
- improperUsage: true,
537
- message: '`t.throwsAsync()` must be called with a function or promise',
538
- values: [formatWithLabel('Called with:', thrower)],
468
+ throw fail(new AssertionError('`t.throwsAsync()` must be called with a function or promise', {
469
+ assertion: 't.throwsAsync()',
470
+ formattedDetails: [formatWithLabel('Called with:', thrower)],
539
471
  }));
540
- return;
541
472
  }
542
473
 
543
474
  try {
544
- expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
475
+ expectations = validateExpectations('t.throwsAsync()', expectations, args.length, experiments);
545
476
  } catch (error) {
546
- fail(error);
547
- return;
477
+ Promise.resolve(thrower).catch(noop);
478
+ throw fail(error);
548
479
  }
549
480
 
550
481
  const handlePromise = async (promise, wasReturned) => {
551
- // Create an error object to record the stack before it gets lost in the promise chain.
552
- const savedError = getErrorWithLongStackTrace();
482
+ // Record the stack before it gets lost in the promise chain.
483
+ const assertionStack = getAssertionStack();
553
484
  // Handle "promise like" objects by casting to a real Promise.
554
485
  const intermediate = Promise.resolve(promise).then(value => {
555
- throw new AssertionError({
556
- assertion: 'throwsAsync',
557
- message,
558
- savedError,
559
- values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)],
560
- });
486
+ throw failPending(new AssertionError(message, {
487
+ assertion: 't.throwsAsync()',
488
+ assertionStack,
489
+ formattedDetails: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)],
490
+ }));
561
491
  }, error => {
562
- assertExpectations({
563
- assertion: 'throwsAsync',
564
- actual: error,
565
- expectations,
566
- message,
567
- prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`,
568
- savedError,
569
- });
570
- return error;
492
+ try {
493
+ assertExpectations({
494
+ assertion: 't.throwsAsync()',
495
+ actual: error,
496
+ expectations,
497
+ message,
498
+ prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`,
499
+ assertionStack,
500
+ });
501
+ return error;
502
+ } catch (error_) {
503
+ throw failPending(error_);
504
+ }
571
505
  });
572
506
 
573
507
  pending(intermediate);
574
- try {
575
- return await intermediate;
576
- } catch {
577
- // Don't reject the returned promise, even if the assertion fails.
578
- }
508
+ return intermediate;
579
509
  };
580
510
 
581
511
  if (isPromise(thrower)) {
@@ -591,90 +521,77 @@ export class Assertions {
591
521
  }
592
522
 
593
523
  if (actual) {
594
- fail(new AssertionError({
595
- assertion: 'throwsAsync',
596
- message,
597
- actualStack: actual.stack,
598
- values: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)],
524
+ throw fail(new AssertionError(message, {
525
+ assertion: 't.throwsAsync()',
526
+ cause: actual,
527
+ formattedDetails: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)],
599
528
  }));
600
- return;
601
529
  }
602
530
 
603
531
  if (isPromise(retval)) {
604
532
  return handlePromise(retval, true);
605
533
  }
606
534
 
607
- fail(new AssertionError({
608
- assertion: 'throwsAsync',
609
- message,
610
- values: [formatWithLabel('Function returned:', retval)],
535
+ throw fail(new AssertionError(message, {
536
+ assertion: 't.throwsAsync()',
537
+ formattedDetails: [formatWithLabel('Function returned:', retval)],
611
538
  }));
612
539
  });
613
540
 
614
541
  this.notThrows = withSkip((fn, message) => {
615
- if (!checkMessage('notThrows', message)) {
616
- return;
617
- }
542
+ assertMessage(message, 't.notThrows()');
618
543
 
619
544
  if (typeof fn !== 'function') {
620
- fail(new AssertionError({
621
- assertion: 'notThrows',
622
- improperUsage: true,
623
- message: '`t.notThrows()` must be called with a function',
624
- values: [formatWithLabel('Called with:', fn)],
545
+ throw fail(new AssertionError('`t.notThrows()` must be called with a function', {
546
+ assertion: 't.notThrows()',
547
+ improperUsage: {assertion: 'notThrows'},
548
+ formattedDetails: [formatWithLabel('Called with:', fn)],
625
549
  }));
626
- return;
627
550
  }
628
551
 
629
552
  try {
630
553
  fn();
631
554
  } catch (error) {
632
- fail(new AssertionError({
633
- assertion: 'notThrows',
634
- message,
635
- actualStack: error.stack,
636
- values: [formatWithLabel('Function threw:', error)],
555
+ throw fail(new AssertionError(message, {
556
+ assertion: 't.notThrows()',
557
+ cause: error,
558
+ formattedDetails: [formatWithLabel('Function threw:', error)],
637
559
  }));
638
- return;
639
560
  }
640
561
 
641
- pass();
562
+ return pass();
642
563
  });
643
564
 
644
- this.notThrowsAsync = withSkip((nonThrower, message) => {
645
- if (!checkMessage('notThrowsAsync', message)) {
646
- return Promise.resolve();
565
+ this.notThrowsAsync = withSkip(async (nonThrower, message) => {
566
+ try {
567
+ assertMessage(message, 't.notThrowsAsync()');
568
+ } catch (error) {
569
+ Promise.resolve(nonThrower).catch(noop);
570
+ throw error;
647
571
  }
648
572
 
649
573
  if (typeof nonThrower !== 'function' && !isPromise(nonThrower)) {
650
- fail(new AssertionError({
651
- assertion: 'notThrowsAsync',
652
- improperUsage: true,
653
- message: '`t.notThrowsAsync()` must be called with a function or promise',
654
- values: [formatWithLabel('Called with:', nonThrower)],
574
+ throw fail(new AssertionError('`t.notThrowsAsync()` must be called with a function or promise', {
575
+ assertion: 't.notThrowsAsync()',
576
+ formattedDetails: [formatWithLabel('Called with:', nonThrower)],
655
577
  }));
656
- return Promise.resolve();
657
578
  }
658
579
 
659
580
  const handlePromise = async (promise, wasReturned) => {
660
581
  // Create an error object to record the stack before it gets lost in the promise chain.
661
- const savedError = getErrorWithLongStackTrace();
582
+ const assertionStack = getAssertionStack();
662
583
  // Handle "promise like" objects by casting to a real Promise.
663
584
  const intermediate = Promise.resolve(promise).then(noop, error => {
664
- throw new AssertionError({
665
- assertion: 'notThrowsAsync',
666
- message,
667
- savedError,
668
- values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)],
669
- });
585
+ throw failPending(new AssertionError(message, {
586
+ assertion: 't.notThrowsAsync()',
587
+ assertionStack,
588
+ formattedDetails: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)],
589
+ }));
670
590
  });
671
591
  pending(intermediate);
672
592
 
673
- try {
674
- return await intermediate;
675
- } catch {
676
- // Don't reject the returned promise, even if the assertion fails.
677
- }
593
+ await intermediate;
594
+ return true;
678
595
  };
679
596
 
680
597
  if (isPromise(nonThrower)) {
@@ -685,22 +602,18 @@ export class Assertions {
685
602
  try {
686
603
  retval = nonThrower();
687
604
  } catch (error) {
688
- fail(new AssertionError({
689
- assertion: 'notThrowsAsync',
690
- message,
691
- actualStack: error.stack,
692
- values: [formatWithLabel('Function threw:', error)],
605
+ throw fail(new AssertionError(message, {
606
+ assertion: 't.notThrowsAsync()',
607
+ cause: error,
608
+ formattedDetails: [formatWithLabel('Function threw:', error)],
693
609
  }));
694
- return Promise.resolve();
695
610
  }
696
611
 
697
612
  if (!isPromise(retval)) {
698
- fail(new AssertionError({
699
- assertion: 'notThrowsAsync',
700
- message,
701
- values: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)],
613
+ throw fail(new AssertionError(message, {
614
+ assertion: 't.notThrowsAsync()',
615
+ formattedDetails: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)],
702
616
  }));
703
- return Promise.resolve();
704
617
  }
705
618
 
706
619
  return handlePromise(retval, true);
@@ -708,36 +621,25 @@ export class Assertions {
708
621
 
709
622
  this.snapshot = withSkip((expected, message) => {
710
623
  if (disableSnapshots) {
711
- fail(new AssertionError({
712
- assertion: 'snapshot',
713
- message: '`t.snapshot()` can only be used in tests',
714
- improperUsage: true,
624
+ throw fail(new AssertionError('`t.snapshot()` can only be used in tests', {
625
+ assertion: 't.snapshot()',
715
626
  }));
716
- return false;
717
627
  }
718
628
 
719
- if (message && message.id !== undefined) {
720
- fail(new AssertionError({
721
- assertion: 'snapshot',
722
- message: 'AVA 4 no longer supports snapshot IDs',
723
- improperUsage: true,
724
- values: [formatWithLabel('Called with id:', message.id)],
629
+ if (message?.id !== undefined) {
630
+ throw fail(new AssertionError('Since AVA 4, snapshot IDs are no longer supported', {
631
+ assertion: 't.snapshot()',
632
+ formattedDetails: [formatWithLabel('Called with id:', message.id)],
725
633
  }));
726
- return false;
727
634
  }
728
635
 
729
- if (!checkMessage('snapshot', message)) {
730
- return false;
731
- }
636
+ assertMessage(message, 't.snapshot()');
732
637
 
733
638
  if (message === '') {
734
- fail(new AssertionError({
735
- assertion: 'snapshot',
736
- improperUsage: true,
737
- message: 'The snapshot assertion message must be a non-empty string',
738
- values: [formatWithLabel('Called with:', message)],
639
+ throw fail(new AssertionError('The snapshot assertion message must be a non-empty string', {
640
+ assertion: 't.snapshot()',
641
+ formattedDetails: [formatWithLabel('Called with:', message)],
739
642
  }));
740
- return false;
741
643
  }
742
644
 
743
645
  let result;
@@ -748,215 +650,158 @@ export class Assertions {
748
650
  throw error;
749
651
  }
750
652
 
751
- const improperUsage = {name: error.name, snapPath: error.snapPath};
653
+ const improperUsage = {assertion: 'snapshot', name: error.name, snapPath: error.snapPath};
752
654
  if (error instanceof VersionMismatchError) {
753
655
  improperUsage.snapVersion = error.snapVersion;
754
656
  improperUsage.expectedVersion = error.expectedVersion;
755
657
  }
756
658
 
757
- fail(new AssertionError({
758
- assertion: 'snapshot',
759
- message: message || 'Could not compare snapshot',
659
+ throw fail(new AssertionError(message ?? 'Could not compare snapshot', {
660
+ asssertion: 't.snapshot()',
760
661
  improperUsage,
761
662
  }));
762
- return false;
763
663
  }
764
664
 
765
665
  if (result.pass) {
766
- pass();
767
- return true;
666
+ return pass();
768
667
  }
769
668
 
770
669
  if (result.actual) {
771
- fail(new AssertionError({
772
- assertion: 'snapshot',
773
- message: message || 'Did not match snapshot',
774
- values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})],
670
+ throw fail(new AssertionError(message ?? 'Did not match snapshot', {
671
+ assertion: 't.snapshot()',
672
+ formattedDetails: [formatDescriptorDiff(result.actual, result.expected, {invert: true})],
775
673
  }));
776
674
  } else {
777
675
  // This can only occur in CI environments.
778
- fail(new AssertionError({
779
- assertion: 'snapshot',
780
- message: message || 'No snapshot available — new snapshots are not created in CI environments',
676
+ throw fail(new AssertionError(message ?? 'No snapshot available — new snapshots are not created in CI environments', {
677
+ assertion: 't.snapshot()',
781
678
  }));
782
679
  }
783
-
784
- return false;
785
680
  });
786
681
 
787
682
  this.truthy = withSkip((actual, message) => {
788
- if (!checkMessage('truthy', message)) {
789
- return false;
790
- }
683
+ assertMessage(message, 't.truthy()');
791
684
 
792
685
  if (actual) {
793
- pass();
794
- return true;
686
+ return pass();
795
687
  }
796
688
 
797
- fail(new AssertionError({
798
- assertion: 'truthy',
799
- message,
800
- operator: '!!',
801
- values: [formatWithLabel('Value is not truthy:', actual)],
689
+ throw fail(new AssertionError(message, {
690
+ assertion: 't.truthy()',
691
+ formattedDetails: [formatWithLabel('Value is not truthy:', actual)],
802
692
  }));
803
- return false;
804
693
  });
805
694
 
806
695
  this.falsy = withSkip((actual, message) => {
807
- if (!checkMessage('falsy', message)) {
808
- return false;
809
- }
696
+ assertMessage(message, 't.falsy()');
810
697
 
811
698
  if (actual) {
812
- fail(new AssertionError({
813
- assertion: 'falsy',
814
- message,
815
- operator: '!',
816
- values: [formatWithLabel('Value is not falsy:', actual)],
699
+ throw fail(new AssertionError(message, {
700
+ assertion: 't.falsy()',
701
+ formattedDetails: [formatWithLabel('Value is not falsy:', actual)],
817
702
  }));
818
- return false;
819
703
  }
820
704
 
821
- pass();
822
- return true;
705
+ return pass();
823
706
  });
824
707
 
825
708
  this.true = withSkip((actual, message) => {
826
- if (!checkMessage('true', message)) {
827
- return false;
828
- }
709
+ assertMessage(message, 't.true()');
829
710
 
830
711
  if (actual === true) {
831
- pass();
832
- return true;
712
+ return pass();
833
713
  }
834
714
 
835
- fail(new AssertionError({
836
- assertion: 'true',
837
- message,
838
- values: [formatWithLabel('Value is not `true`:', actual)],
715
+ throw fail(new AssertionError(message, {
716
+ assertion: 't.true()',
717
+ formattedDetails: [formatWithLabel('Value is not `true`:', actual)],
839
718
  }));
840
- return false;
841
719
  });
842
720
 
843
721
  this.false = withSkip((actual, message) => {
844
- if (!checkMessage('false', message)) {
845
- return false;
846
- }
722
+ assertMessage(message, 't.false()');
847
723
 
848
724
  if (actual === false) {
849
- pass();
850
- return true;
725
+ return pass();
851
726
  }
852
727
 
853
- fail(new AssertionError({
854
- assertion: 'false',
855
- message,
856
- values: [formatWithLabel('Value is not `false`:', actual)],
728
+ throw fail(new AssertionError(message, {
729
+ assertion: 't.false()',
730
+ formattedDetails: [formatWithLabel('Value is not `false`:', actual)],
857
731
  }));
858
- return false;
859
732
  });
860
733
 
861
734
  this.regex = withSkip((string, regex, message) => {
862
- if (!checkMessage('regex', message)) {
863
- return false;
864
- }
735
+ assertMessage(message, 't.regex()');
865
736
 
866
737
  if (typeof string !== 'string') {
867
- fail(new AssertionError({
868
- assertion: 'regex',
869
- improperUsage: true,
870
- message: '`t.regex()` must be called with a string',
871
- values: [formatWithLabel('Called with:', string)],
738
+ throw fail(new AssertionError('`t.regex()` must be called with a string', {
739
+ assertion: 't.regex()',
740
+ formattedDetails: [formatWithLabel('Called with:', string)],
872
741
  }));
873
- return false;
874
742
  }
875
743
 
876
744
  if (!(regex instanceof RegExp)) {
877
- fail(new AssertionError({
878
- assertion: 'regex',
879
- improperUsage: true,
880
- message: '`t.regex()` must be called with a regular expression',
881
- values: [formatWithLabel('Called with:', regex)],
745
+ throw fail(new AssertionError('`t.regex()` must be called with a regular expression', {
746
+ assertion: 't.regex()',
747
+ formattedDetails: [formatWithLabel('Called with:', regex)],
882
748
  }));
883
- return false;
884
749
  }
885
750
 
886
751
  if (!regex.test(string)) {
887
- fail(new AssertionError({
888
- assertion: 'regex',
889
- message,
890
- values: [
752
+ throw fail(new AssertionError(message, {
753
+ assertion: 't.regex()',
754
+ formattedDetails: [
891
755
  formatWithLabel('Value must match expression:', string),
892
756
  formatWithLabel('Regular expression:', regex),
893
757
  ],
894
758
  }));
895
- return false;
896
759
  }
897
760
 
898
- pass();
899
- return true;
761
+ return pass();
900
762
  });
901
763
 
902
764
  this.notRegex = withSkip((string, regex, message) => {
903
- if (!checkMessage('notRegex', message)) {
904
- return false;
905
- }
765
+ assertMessage(message, 't.notRegex()');
906
766
 
907
767
  if (typeof string !== 'string') {
908
- fail(new AssertionError({
909
- assertion: 'notRegex',
910
- improperUsage: true,
911
- message: '`t.notRegex()` must be called with a string',
912
- values: [formatWithLabel('Called with:', string)],
768
+ throw fail(new AssertionError('`t.notRegex()` must be called with a string', {
769
+ assertion: 't.notRegex()',
770
+ formattedDetails: [formatWithLabel('Called with:', string)],
913
771
  }));
914
- return false;
915
772
  }
916
773
 
917
774
  if (!(regex instanceof RegExp)) {
918
- fail(new AssertionError({
919
- assertion: 'notRegex',
920
- improperUsage: true,
921
- message: '`t.notRegex()` must be called with a regular expression',
922
- values: [formatWithLabel('Called with:', regex)],
775
+ throw fail(new AssertionError('`t.notRegex()` must be called with a regular expression', {
776
+ assertion: 't.notRegex()',
777
+ formattedDetails: [formatWithLabel('Called with:', regex)],
923
778
  }));
924
- return false;
925
779
  }
926
780
 
927
781
  if (regex.test(string)) {
928
- fail(new AssertionError({
929
- assertion: 'notRegex',
930
- message,
931
- values: [
782
+ throw fail(new AssertionError(message, {
783
+ assertion: 't.notRegex()',
784
+ formattedDetails: [
932
785
  formatWithLabel('Value must not match expression:', string),
933
786
  formatWithLabel('Regular expression:', regex),
934
787
  ],
935
788
  }));
936
- return false;
937
789
  }
938
790
 
939
- pass();
940
- return true;
791
+ return pass();
941
792
  });
942
793
 
943
794
  this.assert = withSkip((actual, message) => {
944
- if (!checkMessage('assert', message)) {
945
- return false;
946
- }
795
+ assertMessage(message, 't.assert()');
947
796
 
948
797
  if (!actual) {
949
- fail(new AssertionError({
950
- assertion: 'assert',
951
- message,
952
- operator: '!!',
953
- values: [formatWithLabel('Value is not truthy:', actual)],
798
+ throw fail(new AssertionError(message, {
799
+ assertion: 't.assert()',
800
+ formattedDetails: [formatWithLabel('Value is not truthy:', actual)],
954
801
  }));
955
- return false;
956
802
  }
957
803
 
958
- pass();
959
- return true;
804
+ return pass();
960
805
  });
961
806
  }
962
807
  }