ava 3.8.1 → 3.10.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.
package/index.d.ts CHANGED
@@ -45,6 +45,9 @@ export interface Assertions {
45
45
  /** Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
46
46
  deepEqual: DeepEqualAssertion;
47
47
 
48
+ /** Assert that `actual` is like `expected`. */
49
+ like: LikeAssertion;
50
+
48
51
  /** Fail the test. */
49
52
  fail: FailAssertion;
50
53
 
@@ -125,6 +128,14 @@ export interface DeepEqualAssertion {
125
128
  skip(actual: any, expected: any, message?: string): void;
126
129
  }
127
130
 
131
+ export interface LikeAssertion {
132
+ /** Assert that `value` is like `selector`. */
133
+ (value: any, selector: Record<string, any>, message?: string): void;
134
+
135
+ /** Skip this assertion. */
136
+ skip(value: any, selector: any, message?: string): void;
137
+ }
138
+
128
139
  export interface FailAssertion {
129
140
  /** Fail the test. */
130
141
  (message?: string): void;
@@ -342,7 +353,7 @@ export interface TimeoutFn {
342
353
  * Set a timeout for the test, in milliseconds. The test will fail if the timeout is exceeded.
343
354
  * The timeout is reset each time an assertion is made.
344
355
  */
345
- (ms: number): void;
356
+ (ms: number, message?: string): void;
346
357
  }
347
358
 
348
359
  export interface TeardownFn {
@@ -422,7 +433,7 @@ export interface CbExecutionContext<Context = unknown> extends ExecutionContext<
422
433
  end(error?: any): void;
423
434
  }
424
435
 
425
- export type ImplementationResult = PromiseLike<void> | Subscribable | void;
436
+ export type ImplementationResult = PromiseLike<void> | Subscribable | void; // eslint-disable-line @typescript-eslint/no-invalid-void-type
426
437
  export type Implementation<Context = unknown> = (t: ExecutionContext<Context>) => ImplementationResult;
427
438
  export type CbImplementation<Context = unknown> = (t: CbExecutionContext<Context>) => ImplementationResult;
428
439
 
package/lib/assert.js CHANGED
@@ -3,11 +3,11 @@ const concordance = require('concordance');
3
3
  const isError = require('is-error');
4
4
  const isPromise = require('is-promise');
5
5
  const concordanceOptions = require('./concordance-options').default;
6
- const concordanceDiffOptions = require('./concordance-options').diff;
6
+ const {CIRCULAR_SELECTOR, isLikeSelector, selectComparable} = require('./like-selector');
7
7
  const snapshotManager = require('./snapshot-manager');
8
8
 
9
9
  function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
10
- options = {...options, ...concordanceDiffOptions};
10
+ options = {...options, ...concordanceOptions};
11
11
  return {
12
12
  label: 'Difference:',
13
13
  formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options)
@@ -64,6 +64,21 @@ class AssertionError extends Error {
64
64
  }
65
65
  exports.AssertionError = AssertionError;
66
66
 
67
+ function checkAssertionMessage(assertion, message) {
68
+ if (typeof message === 'undefined' || typeof message === 'string') {
69
+ return true;
70
+ }
71
+
72
+ return new AssertionError({
73
+ assertion,
74
+ improperUsage: true,
75
+ message: 'The assertion message must be a string',
76
+ values: [formatWithLabel('Called with:', message)]
77
+ });
78
+ }
79
+
80
+ exports.checkAssertionMessage = checkAssertionMessage;
81
+
67
82
  function getErrorWithLongStackTrace() {
68
83
  const limitBefore = Error.stackTraceLimit;
69
84
  Error.stackTraceLimit = Infinity;
@@ -242,7 +257,9 @@ class Assertions {
242
257
  fail = notImplemented,
243
258
  skip = notImplemented,
244
259
  compareWithSnapshot = notImplemented,
245
- powerAssert
260
+ powerAssert,
261
+ experiments = {},
262
+ disableSnapshots = false
246
263
  } = {}) {
247
264
  const withSkip = assertionFn => {
248
265
  assertionFn.skip = skip;
@@ -267,22 +284,16 @@ class Assertions {
267
284
  });
268
285
 
269
286
  const checkMessage = (assertion, message, powerAssert = false) => {
270
- if (typeof message === 'undefined' || typeof message === 'string') {
271
- return true;
287
+ const result = checkAssertionMessage(assertion, message);
288
+ if (result === true) {
289
+ return this.true;
272
290
  }
273
291
 
274
- const error = new AssertionError({
275
- assertion,
276
- improperUsage: true,
277
- message: 'The assertion message must be a string',
278
- values: [formatWithLabel('Called with:', message)]
279
- });
280
-
281
292
  if (powerAssert) {
282
- throw error;
293
+ throw result;
283
294
  }
284
295
 
285
- fail(error);
296
+ fail(result);
286
297
  return false;
287
298
  };
288
299
 
@@ -387,6 +398,61 @@ class Assertions {
387
398
  }
388
399
  });
389
400
 
401
+ this.like = withSkip((actual, selector, message) => {
402
+ if (!experiments.likeAssertion) {
403
+ fail(new AssertionError({
404
+ assertion: 'like',
405
+ improperUsage: true,
406
+ message: 'You must enable the `likeAssertion` experiment in order to use `t.like()`'
407
+ }));
408
+ return;
409
+ }
410
+
411
+ if (!checkMessage('like', message)) {
412
+ return;
413
+ }
414
+
415
+ if (!isLikeSelector(selector)) {
416
+ fail(new AssertionError({
417
+ assertion: 'like',
418
+ improperUsage: true,
419
+ message: '`t.like()` selector must be a non-empty object',
420
+ values: [formatWithLabel('Called with:', selector)]
421
+ }));
422
+ return;
423
+ }
424
+
425
+ let comparable;
426
+ try {
427
+ comparable = selectComparable(actual, selector);
428
+ } catch (error) {
429
+ if (error === CIRCULAR_SELECTOR) {
430
+ fail(new AssertionError({
431
+ assertion: 'like',
432
+ improperUsage: true,
433
+ message: '`t.like()` selector must not contain circular references',
434
+ values: [formatWithLabel('Called with:', selector)]
435
+ }));
436
+ return;
437
+ }
438
+
439
+ throw error;
440
+ }
441
+
442
+ const result = concordance.compare(comparable, selector, concordanceOptions);
443
+ if (result.pass) {
444
+ pass();
445
+ } else {
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)]
452
+ }));
453
+ }
454
+ });
455
+
390
456
  this.throws = withSkip((...args) => {
391
457
  // Since arrow functions do not support 'arguments', we are using rest
392
458
  // operator, so we can determine the total number of arguments passed
@@ -634,6 +700,15 @@ class Assertions {
634
700
  });
635
701
 
636
702
  this.snapshot = withSkip((expected, ...rest) => {
703
+ if (disableSnapshots && experiments.disableSnapshotsInHooks) {
704
+ fail(new AssertionError({
705
+ assertion: 'snapshot',
706
+ message: '`t.snapshot()` can only be used in tests',
707
+ improperUsage: true
708
+ }));
709
+ return;
710
+ }
711
+
637
712
  let message;
638
713
  let snapshotOptions;
639
714
  if (rest.length > 1) {
package/lib/cli.js CHANGED
@@ -182,6 +182,10 @@ exports.run = async () => { // eslint-disable-line complexity
182
182
  const chalkOptions = {level: combined.color === false ? 0 : require('chalk').level};
183
183
  const chalk = require('./chalk').set(chalkOptions);
184
184
 
185
+ if (combined.updateSnapshots && combined.match) {
186
+ exit('Snapshots cannot be updated when matching specific tests.');
187
+ }
188
+
185
189
  if (confError) {
186
190
  if (confError.parent) {
187
191
  exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
@@ -259,8 +263,7 @@ exports.run = async () => { // eslint-disable-line complexity
259
263
 
260
264
  const ciParallelVars = require('ci-parallel-vars');
261
265
  const Api = require('./api');
262
- const VerboseReporter = require('./reporters/verbose');
263
- const MiniReporter = require('./reporters/mini');
266
+ const DefaultReporter = require('./reporters/default');
264
267
  const TapReporter = require('./reporters/tap');
265
268
  const Watcher = require('./watcher');
266
269
  const normalizeExtensions = require('./extensions');
@@ -357,6 +360,9 @@ exports.run = async () => { // eslint-disable-line complexity
357
360
  pattern: normalizePattern(path.relative(projectDir, path.resolve(process.cwd(), pattern))),
358
361
  ...rest
359
362
  }));
363
+ if (combined.updateSnapshots && filter.some(condition => condition.lineNumbers !== null)) {
364
+ exit('Snapshots cannot be updated when selecting specific tests by their line number.');
365
+ }
360
366
 
361
367
  const api = new Api({
362
368
  cacheEnabled: combined.cache !== false,
@@ -391,25 +397,32 @@ exports.run = async () => { // eslint-disable-line complexity
391
397
  reportStream: process.stdout,
392
398
  stdStream: process.stderr
393
399
  });
394
- } else if (debug !== null || combined.verbose || isCi || !process.stdout.isTTY) {
395
- reporter = new VerboseReporter({
396
- projectDir,
397
- reportStream: process.stdout,
398
- stdStream: process.stderr,
399
- watching: combined.watch
400
- });
401
400
  } else {
402
- reporter = new MiniReporter({
401
+ reporter = new DefaultReporter({
403
402
  projectDir,
404
403
  reportStream: process.stdout,
405
404
  stdStream: process.stderr,
406
- watching: combined.watch
405
+ watching: combined.watch,
406
+ verbose: debug !== null || combined.verbose || isCi || !process.stdout.isTTY
407
407
  });
408
408
  }
409
409
 
410
410
  api.on('run', plan => {
411
411
  reporter.startRun(plan);
412
412
 
413
+ if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
414
+ if (process.versions.node >= '12.16.0') {
415
+ plan.status.on('stateChange', evt => {
416
+ process.send(evt);
417
+ });
418
+ } else {
419
+ const v8 = require('v8');
420
+ plan.status.on('stateChange', evt => {
421
+ process.send([...v8.serialize(evt)]);
422
+ });
423
+ }
424
+ }
425
+
413
426
  plan.status.on('stateChange', evt => {
414
427
  if (evt.type === 'interrupt') {
415
428
  reporter.endRun();
@@ -19,7 +19,7 @@ module.exports = (source, options = {}) => {
19
19
  let contents;
20
20
  try {
21
21
  contents = fs.readFileSync(file, 'utf8');
22
- } catch (_) {
22
+ } catch {
23
23
  return null;
24
24
  }
25
25
 
@@ -135,5 +135,4 @@ exports.default = {
135
135
  theme
136
136
  };
137
137
 
138
- exports.diff = {maxDepth: 1, plugins, theme};
139
138
  exports.snapshotManager = {plugins, theme: plainTheme};
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+ function isLikeSelector(selector) {
3
+ return selector !== null &&
4
+ typeof selector === 'object' &&
5
+ Reflect.getPrototypeOf(selector) === Object.prototype &&
6
+ Reflect.ownKeys(selector).length > 0;
7
+ }
8
+
9
+ exports.isLikeSelector = isLikeSelector;
10
+
11
+ const CIRCULAR_SELECTOR = new Error('Encountered a circular selector');
12
+ exports.CIRCULAR_SELECTOR = CIRCULAR_SELECTOR;
13
+
14
+ function selectComparable(lhs, selector, circular = new Set()) {
15
+ if (circular.has(selector)) {
16
+ throw CIRCULAR_SELECTOR;
17
+ }
18
+
19
+ circular.add(selector);
20
+
21
+ if (lhs === null || typeof lhs !== 'object') {
22
+ return lhs;
23
+ }
24
+
25
+ const comparable = {};
26
+ for (const [key, rhs] of Object.entries(selector)) {
27
+ if (isLikeSelector(rhs)) {
28
+ comparable[key] = selectComparable(Reflect.get(lhs, key), rhs, circular);
29
+ } else {
30
+ comparable[key] = Reflect.get(lhs, key);
31
+ }
32
+ }
33
+
34
+ return comparable;
35
+ }
36
+
37
+ exports.selectComparable = selectComparable;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const micromatch = require('micromatch');
3
+ const picomatch = require('picomatch');
4
4
  const flatten = require('lodash/flatten');
5
5
 
6
6
  const NUMBER_REGEX = /^\d+$/;
@@ -56,7 +56,7 @@ exports.splitPatternAndLineNumbers = splitPatternAndLineNumbers;
56
56
  function getApplicableLineNumbers(normalizedFilePath, filter) {
57
57
  return sortNumbersAscending(distinctArray(flatten(
58
58
  filter
59
- .filter(({pattern, lineNumbers}) => lineNumbers && micromatch.isMatch(normalizedFilePath, pattern))
59
+ .filter(({pattern, lineNumbers}) => lineNumbers && picomatch.isMatch(normalizedFilePath, pattern))
60
60
  .map(({lineNumbers}) => lineNumbers)
61
61
  )));
62
62
  }
@@ -7,7 +7,7 @@ const pkgConf = require('pkg-conf');
7
7
 
8
8
  const NO_SUCH_FILE = Symbol('no ava.config.js file');
9
9
  const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
10
- const EXPERIMENTS = new Set();
10
+ const EXPERIMENTS = new Set(['disableSnapshotsInHooks', 'likeAssertion', 'reverseTeardowns']);
11
11
 
12
12
  // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
13
13
  const evaluateJsConfig = configFile => {