ava 3.14.0 → 3.15.0

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.
@@ -1,26 +1,25 @@
1
1
  'use strict';
2
- const normalizeExtensions = require('./lib/extensions');
2
+ let isMainThread = true;
3
+ let supportsWorkers = false;
4
+ try {
5
+ ({isMainThread} = require('worker_threads'));
6
+ supportsWorkers = true;
7
+ } catch {}
8
+
3
9
  const {classify, hasExtension, isHelperish, matches, normalizeFileForMatching, normalizeGlobs, normalizePatterns} = require('./lib/globs');
4
- const loadConfig = require('./lib/load-config');
5
- const providerManager = require('./lib/provider-manager');
6
10
 
7
- const configCache = new Map();
8
- const helperCache = new Map();
11
+ let resolveGlobs;
12
+ let resolveGlobsSync;
9
13
 
10
- function load(projectDir, overrides) {
11
- const cacheKey = `${JSON.stringify(overrides)}\n${projectDir}`;
12
- if (helperCache.has(cacheKey)) {
13
- return helperCache.get(cacheKey);
14
- }
14
+ if (!supportsWorkers || !isMainThread) {
15
+ const normalizeExtensions = require('./lib/extensions');
16
+ const {loadConfig, loadConfigSync} = require('./lib/load-config');
17
+ const providerManager = require('./lib/provider-manager');
15
18
 
16
- let conf;
17
- let providers;
18
- if (configCache.has(projectDir)) {
19
- ({conf, providers} = configCache.get(projectDir));
20
- } else {
21
- conf = loadConfig({resolveFrom: projectDir});
19
+ const configCache = new Map();
22
20
 
23
- providers = [];
21
+ const collectProviders = ({conf, projectDir}) => {
22
+ const providers = [];
24
23
  if (Reflect.has(conf, 'babel')) {
25
24
  const {level, main} = providerManager.babel(projectDir);
26
25
  providers.push({
@@ -39,12 +38,125 @@ function load(projectDir, overrides) {
39
38
  });
40
39
  }
41
40
 
42
- configCache.set(projectDir, {conf, providers});
41
+ return providers;
42
+ };
43
+
44
+ const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
45
+ const extensions = overrideExtensions ?
46
+ normalizeExtensions(overrideExtensions) :
47
+ normalizeExtensions(conf.extensions, providers);
48
+
49
+ return {
50
+ cwd: projectDir,
51
+ ...normalizeGlobs({
52
+ extensions,
53
+ files: overrideFiles ? overrideFiles : conf.files,
54
+ providers
55
+ })
56
+ };
57
+ };
58
+
59
+ resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
60
+ if (!configCache.has(projectDir)) {
61
+ const conf = loadConfigSync({resolveFrom: projectDir});
62
+ const providers = collectProviders({conf, projectDir});
63
+ configCache.set(projectDir, {conf, providers});
64
+ }
65
+
66
+ const {conf, providers} = configCache.get(projectDir);
67
+ return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
68
+ };
69
+
70
+ resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
71
+ if (!configCache.has(projectDir)) {
72
+ configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(conf => { // eslint-disable-line promise/prefer-await-to-then
73
+ const providers = collectProviders({conf, projectDir});
74
+ return {conf, providers};
75
+ }));
76
+ }
77
+
78
+ const {conf, providers} = await configCache.get(projectDir);
79
+ return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
80
+ };
81
+ }
82
+
83
+ if (supportsWorkers) {
84
+ const v8 = require('v8');
85
+
86
+ const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.
87
+
88
+ if (isMainThread) {
89
+ const {Worker} = require('worker_threads');
90
+ let data;
91
+ let sync;
92
+ let worker;
93
+
94
+ resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
95
+ if (worker === undefined) {
96
+ const dataBuffer = new SharedArrayBuffer(MAX_DATA_LENGTH_EXCLUSIVE);
97
+ data = new Uint8Array(dataBuffer);
98
+
99
+ const syncBuffer = new SharedArrayBuffer(4);
100
+ sync = new Int32Array(syncBuffer);
101
+
102
+ worker = new Worker(__filename, {
103
+ workerData: {
104
+ dataBuffer,
105
+ syncBuffer,
106
+ firstMessage: {projectDir, overrideExtensions, overrideFiles}
107
+ }
108
+ });
109
+ worker.unref();
110
+ } else {
111
+ worker.postMessage({projectDir, overrideExtensions, overrideFiles});
112
+ }
113
+
114
+ Atomics.wait(sync, 0, 0);
115
+
116
+ const byteLength = Atomics.exchange(sync, 0, 0);
117
+ if (byteLength === MAX_DATA_LENGTH_EXCLUSIVE) {
118
+ throw new Error('Globs are over 100 KiB and cannot be resolved');
119
+ }
120
+
121
+ const globsOrError = v8.deserialize(data.slice(0, byteLength));
122
+ if (globsOrError instanceof Error) {
123
+ throw globsOrError;
124
+ }
125
+
126
+ return globsOrError;
127
+ };
128
+ } else {
129
+ const {parentPort, workerData} = require('worker_threads');
130
+ const data = new Uint8Array(workerData.dataBuffer);
131
+ const sync = new Int32Array(workerData.syncBuffer);
132
+
133
+ const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => {
134
+ let encoded;
135
+ try {
136
+ const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles);
137
+ encoded = v8.serialize(globs);
138
+ } catch (error) {
139
+ encoded = v8.serialize(error);
140
+ }
141
+
142
+ const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE;
143
+ Atomics.store(sync, 0, byteLength);
144
+ Atomics.notify(sync, 0);
145
+ };
146
+
147
+ parentPort.on('message', handleMessage);
148
+ handleMessage(workerData.firstMessage);
149
+ delete workerData.firstMessage;
43
150
  }
151
+ }
152
+
153
+ const helperCache = new Map();
44
154
 
45
- const extensions = overrides && overrides.extensions ?
46
- normalizeExtensions(overrides.extensions) :
47
- normalizeExtensions(conf.extensions, providers);
155
+ function load(projectDir, overrides) {
156
+ const cacheKey = `${JSON.stringify(overrides)}\n${projectDir}`;
157
+ if (helperCache.has(cacheKey)) {
158
+ return helperCache.get(cacheKey);
159
+ }
48
160
 
49
161
  let helperPatterns = [];
50
162
  if (overrides && overrides.helpers !== undefined) {
@@ -55,14 +167,7 @@ function load(projectDir, overrides) {
55
167
  helperPatterns = normalizePatterns(overrides.helpers);
56
168
  }
57
169
 
58
- const globs = {
59
- cwd: projectDir,
60
- ...normalizeGlobs({
61
- extensions,
62
- files: overrides && overrides.files ? overrides.files : conf.files,
63
- providers
64
- })
65
- };
170
+ const globs = resolveGlobsSync(projectDir, overrides && overrides.extensions, overrides && overrides.files);
66
171
 
67
172
  const classifyForESLint = file => {
68
173
  const {isTest} = classify(file, globs);
package/lib/api.js CHANGED
@@ -111,10 +111,8 @@ class Api extends Emittery {
111
111
  }
112
112
  };
113
113
 
114
- let cacheDir;
115
114
  let testFiles;
116
115
  try {
117
- cacheDir = this._createCacheDir();
118
116
  testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
119
117
  if (selectedFiles.length === 0) {
120
118
  selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
@@ -189,7 +187,7 @@ class Api extends Emittery {
189
187
 
190
188
  const {providers = []} = this.options;
191
189
  const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
192
- const state = await main.compile({cacheDir, files: testFiles});
190
+ const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
193
191
  return state === null ? null : {type, state};
194
192
  }))).filter(state => state !== null);
195
193
 
package/lib/cli.js CHANGED
@@ -7,7 +7,7 @@ const arrify = require('arrify');
7
7
  const yargs = require('yargs');
8
8
  const readPkg = require('read-pkg');
9
9
  const isCi = require('./is-ci');
10
- const loadConfig = require('./load-config');
10
+ const {loadConfig} = require('./load-config');
11
11
 
12
12
  function exit(message) {
13
13
  console.error(`\n ${require('./chalk').get().red(figures.cross)} ${message}`);
@@ -83,7 +83,7 @@ exports.run = async () => { // eslint-disable-line complexity
83
83
  let confError = null;
84
84
  try {
85
85
  const {argv: {config: configFile}} = yargs.help(false);
86
- conf = loadConfig({configFile});
86
+ conf = await loadConfig({configFile});
87
87
  } catch (error) {
88
88
  confError = error;
89
89
  }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
+ const url = require('url');
4
5
  const vm = require('vm');
5
6
  const {isPlainObject} = require('is-plain-object');
6
7
  const pkgConf = require('pkg-conf');
@@ -11,23 +12,37 @@ const EXPERIMENTS = new Set([
11
12
  'configurableModuleFormat',
12
13
  'disableNullExpectations',
13
14
  'disableSnapshotsInHooks',
15
+ 'nextGenConfig',
14
16
  'reverseTeardowns',
15
17
  'sharedWorkers'
16
18
  ]);
17
19
 
18
20
  // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
19
- const evaluateJsConfig = configFile => {
20
- const contents = fs.readFileSync(configFile, 'utf8');
21
- const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.replace(/export default/g, '__export__ =')};return __export__;})()`, {
21
+ const evaluateJsConfig = (contents, configFile) => {
22
+ const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.toString('utf8').replace(/export default/g, '__export__ =')};return __export__;})()`, {
22
23
  filename: configFile,
23
24
  lineOffset: -1
24
25
  });
25
- return {
26
- default: script.runInThisContext()
27
- };
26
+ return script.runInThisContext();
28
27
  };
29
28
 
30
- const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}) => {
29
+ const importConfig = async ({configFile, fileForErrorMessage}) => {
30
+ let module;
31
+ try {
32
+ module = await import(url.pathToFileURL(configFile)); // eslint-disable-line node/no-unsupported-features/es-syntax
33
+ } catch (error) {
34
+ throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error});
35
+ }
36
+
37
+ const {default: config = MISSING_DEFAULT_EXPORT} = module;
38
+ if (config === MISSING_DEFAULT_EXPORT) {
39
+ throw new Error(`${fileForErrorMessage} must have a default export`);
40
+ }
41
+
42
+ return config;
43
+ };
44
+
45
+ const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.js')}, useImport = false) => {
31
46
  if (!configFile.endsWith('.js')) {
32
47
  return null;
33
48
  }
@@ -36,7 +51,10 @@ const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.confi
36
51
 
37
52
  let config;
38
53
  try {
39
- ({default: config = MISSING_DEFAULT_EXPORT} = evaluateJsConfig(configFile));
54
+ const contents = fs.readFileSync(configFile);
55
+ config = useImport && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig') ?
56
+ importConfig({configFile, fileForErrorMessage}) :
57
+ evaluateJsConfig(contents, configFile) || MISSING_DEFAULT_EXPORT;
40
58
  } catch (error) {
41
59
  if (error.code === 'ENOENT') {
42
60
  return null;
@@ -69,14 +87,17 @@ const loadCjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf
69
87
  }
70
88
  };
71
89
 
72
- const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}) => {
90
+ const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.config.mjs')}, experimentally = false) => {
73
91
  if (!configFile.endsWith('.mjs')) {
74
92
  return null;
75
93
  }
76
94
 
77
95
  const fileForErrorMessage = path.relative(projectDir, configFile);
78
96
  try {
79
- fs.readFileSync(configFile);
97
+ const contents = fs.readFileSync(configFile);
98
+ if (experimentally && contents.includes('nonSemVerExperiments') && contents.includes('nextGenConfig')) {
99
+ return {config: importConfig({configFile, fileForErrorMessage}), fileForErrorMessage};
100
+ }
80
101
  } catch (error) {
81
102
  if (error.code === 'ENOENT') {
82
103
  return null;
@@ -88,11 +109,7 @@ const loadMjsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.conf
88
109
  throw new Error(`AVA cannot yet load ${fileForErrorMessage} files`);
89
110
  };
90
111
 
91
- function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity
92
- let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
93
- const filepath = pkgConf.filepath(packageConf);
94
- const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
95
-
112
+ function resolveConfigFile(projectDir, configFile) {
96
113
  if (configFile) {
97
114
  configFile = path.resolve(configFile); // Relative to CWD
98
115
  if (path.basename(configFile) !== path.relative(projectDir, configFile)) {
@@ -104,6 +121,15 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {
104
121
  }
105
122
  }
106
123
 
124
+ return configFile;
125
+ }
126
+
127
+ function loadConfigSync({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
128
+ let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
129
+ const filepath = pkgConf.filepath(packageConf);
130
+ const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
131
+
132
+ configFile = resolveConfigFile(projectDir, configFile);
107
133
  const allowConflictWithPackageJson = Boolean(configFile);
108
134
 
109
135
  let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
@@ -163,4 +189,79 @@ function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {
163
189
  return config;
164
190
  }
165
191
 
166
- module.exports = loadConfig;
192
+ exports.loadConfigSync = loadConfigSync;
193
+
194
+ async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) {
195
+ let packageConf = await pkgConf('ava', {cwd: resolveFrom});
196
+ const filepath = pkgConf.filepath(packageConf);
197
+ const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
198
+
199
+ configFile = resolveConfigFile(projectDir, configFile);
200
+ const allowConflictWithPackageJson = Boolean(configFile);
201
+
202
+ // TODO: Refactor resolution logic to implement https://github.com/avajs/ava/issues/2285.
203
+ let [{config: fileConf, fileForErrorMessage} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = [
204
+ loadJsConfig({projectDir, configFile}, true),
205
+ loadCjsConfig({projectDir, configFile}),
206
+ loadMjsConfig({projectDir, configFile}, true)
207
+ ].filter(result => result !== null);
208
+
209
+ if (conflicting.length > 0) {
210
+ throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
211
+ }
212
+
213
+ let sawPromise = false;
214
+ if (fileConf !== NO_SUCH_FILE) {
215
+ if (allowConflictWithPackageJson) {
216
+ packageConf = {};
217
+ } else if (Object.keys(packageConf).length > 0) {
218
+ throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
219
+ }
220
+
221
+ if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
222
+ sawPromise = true;
223
+ fileConf = await fileConf;
224
+ }
225
+
226
+ if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
227
+ throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
228
+ }
229
+
230
+ if (typeof fileConf === 'function') {
231
+ fileConf = fileConf({projectDir});
232
+ if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
233
+ sawPromise = true;
234
+ fileConf = await fileConf;
235
+ }
236
+
237
+ if (!isPlainObject(fileConf)) {
238
+ throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
239
+ }
240
+ }
241
+
242
+ if ('ava' in fileConf) {
243
+ throw new Error(`Encountered ’ava’ property in ${fileForErrorMessage}; avoid wrapping the configuration`);
244
+ }
245
+ }
246
+
247
+ const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir};
248
+
249
+ const {nonSemVerExperiments: experiments} = config;
250
+ if (!isPlainObject(experiments)) {
251
+ throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`);
252
+ }
253
+
254
+ for (const key of Object.keys(experiments)) {
255
+ if (!EXPERIMENTS.has(key)) {
256
+ throw new Error(`nonSemVerExperiments.${key} from ${fileForErrorMessage} is not a supported experiment`);
257
+ }
258
+ }
259
+
260
+ if (sawPromise && experiments.nextGenConfig !== true) {
261
+ throw new Error(`${fileForErrorMessage} exported a promise or an asynchronous factory function. You must enable the ’asyncConfigurationLoading’ experiment for this to work.`);
262
+ }
263
+
264
+ return config;
265
+ }
266
+
267
+ exports.loadConfig = loadConfig;
@@ -125,12 +125,22 @@ class TapReporter {
125
125
  this.reportStream.write(`# ${stripAnsi(title)}${os.EOL}`);
126
126
  if (evt.logs) {
127
127
  for (const log of evt.logs) {
128
- const logLines = indentString(log, 4).replace(/^ {4}/, ' # ');
128
+ const logLines = indentString(log, 4).replace(/^ {4}/gm, '# ');
129
129
  this.reportStream.write(`${logLines}${os.EOL}`);
130
130
  }
131
131
  }
132
132
  }
133
133
 
134
+ writeTimeout(evt) {
135
+ const err = new Error(`Exited because no new tests completed within the last ${evt.period}ms of inactivity`);
136
+
137
+ for (const [testFile, tests] of evt.pendingTests) {
138
+ for (const title of tests) {
139
+ this.writeTest({testFile, title, err}, {passed: false, todo: false, skip: false});
140
+ }
141
+ }
142
+ }
143
+
134
144
  consumeStateChange(evt) { // eslint-disable-line complexity
135
145
  const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
136
146
 
@@ -172,7 +182,7 @@ class TapReporter {
172
182
  this.writeTest(evt, {passed: true, todo: false, skip: false});
173
183
  break;
174
184
  case 'timeout':
175
- this.writeCrash(evt, `Exited because no new tests completed within the last ${evt.period}ms of inactivity`);
185
+ this.writeTimeout(evt);
176
186
  break;
177
187
  case 'uncaught-exception':
178
188
  this.writeCrash(evt);
package/lib/runner.js CHANGED
@@ -29,6 +29,8 @@ class Runner extends Emittery {
29
29
 
30
30
  this.activeRunnables = new Set();
31
31
  this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
32
+ this.skippedSnapshots = false;
33
+ this.boundSkipSnapshot = this.skipSnapshot.bind(this);
32
34
  this.interrupted = false;
33
35
  this.snapshots = null;
34
36
  this.nextTaskIndex = 0;
@@ -199,8 +201,19 @@ class Runner extends Emittery {
199
201
  return this.snapshots.compare(options);
200
202
  }
201
203
 
204
+ skipSnapshot() {
205
+ this.skippedSnapshots = true;
206
+ }
207
+
202
208
  saveSnapshotState() {
203
- if (this.updateSnapshots && (this.runOnlyExclusive || this.skippingTests)) {
209
+ if (
210
+ this.updateSnapshots &&
211
+ (
212
+ this.runOnlyExclusive ||
213
+ this.skippingTests ||
214
+ this.skippedSnapshots
215
+ )
216
+ ) {
204
217
  return {cannotSave: true};
205
218
  }
206
219
 
@@ -209,9 +222,11 @@ class Runner extends Emittery {
209
222
  }
210
223
 
211
224
  if (this.updateSnapshots) {
212
- // TODO: There may be unused snapshot files if no test caused the
213
- // snapshots to be loaded. Prune them. But not if tests (including hooks!)
214
- // were skipped. Perhaps emit a warning if this occurs?
225
+ return {touchedFiles: snapshotManager.cleanSnapshots({
226
+ file: this.file,
227
+ fixedLocation: this.snapshotDir,
228
+ projectDir: this.projectDir
229
+ })};
215
230
  }
216
231
 
217
232
  return {};
@@ -297,6 +312,7 @@ class Runner extends Emittery {
297
312
  task.implementation :
298
313
  t => task.implementation.apply(null, [t].concat(task.args)),
299
314
  compareTestSnapshot: this.boundCompareTestSnapshot,
315
+ skipSnapshot: this.boundSkipSnapshot,
300
316
  updateSnapshots: this.updateSnapshots,
301
317
  metadata: {...task.metadata, associatedTaskIndex},
302
318
  powerAssert: this.powerAssert,
@@ -349,6 +365,7 @@ class Runner extends Emittery {
349
365
  task.implementation :
350
366
  t => task.implementation.apply(null, [t].concat(task.args)),
351
367
  compareTestSnapshot: this.boundCompareTestSnapshot,
368
+ skipSnapshot: this.boundSkipSnapshot,
352
369
  updateSnapshots: this.updateSnapshots,
353
370
  metadata: task.metadata,
354
371
  powerAssert: this.powerAssert,
@@ -449,12 +449,49 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
449
449
 
450
450
  exports.determineSnapshotDir = determineSnapshotDir;
451
451
 
452
- function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
452
+ function determineSnapshotPaths({file, fixedLocation, projectDir}) {
453
453
  const dir = determineSnapshotDir({file, fixedLocation, projectDir});
454
454
  const relFile = path.relative(projectDir, resolveSourceFile(file));
455
455
  const name = path.basename(relFile);
456
456
  const reportFile = `${name}.md`;
457
457
  const snapFile = `${name}.snap`;
458
+
459
+ return {
460
+ dir,
461
+ relFile,
462
+ snapFile,
463
+ reportFile
464
+ };
465
+ }
466
+
467
+ function cleanFile(file) {
468
+ try {
469
+ fs.unlinkSync(file);
470
+ return [file];
471
+ } catch (error) {
472
+ if (error.code === 'ENOENT') {
473
+ return [];
474
+ }
475
+
476
+ throw error;
477
+ }
478
+ }
479
+
480
+ // Remove snapshot and report if they exist. Returns an array containing the
481
+ // paths of the touched files.
482
+ function cleanSnapshots({file, fixedLocation, projectDir}) {
483
+ const {dir, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});
484
+
485
+ return [
486
+ ...cleanFile(path.join(dir, snapFile)),
487
+ ...cleanFile(path.join(dir, reportFile))
488
+ ];
489
+ }
490
+
491
+ exports.cleanSnapshots = cleanSnapshots;
492
+
493
+ function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
494
+ const {dir, relFile, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});
458
495
  const snapPath = path.join(dir, snapFile);
459
496
 
460
497
  let appendOnly = !updating;
package/lib/test.js CHANGED
@@ -249,6 +249,10 @@ class Test {
249
249
  };
250
250
 
251
251
  this.skipSnapshot = () => {
252
+ if (typeof options.skipSnapshot === 'function') {
253
+ options.skipSnapshot();
254
+ }
255
+
252
256
  if (options.updateSnapshots) {
253
257
  this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots'));
254
258
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ava",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Node.js test runner that lets you develop with confidence.",
5
5
  "license": "MIT",
6
6
  "repository": "avajs/ava",
@@ -10,7 +10,8 @@
10
10
  "node": ">=10.18.0 <11 || >=12.14.0 <12.17.0 || >=12.17.0 <13 || >=14.0.0 <15 || >=15"
11
11
  },
12
12
  "scripts": {
13
- "test": "xo && tsd && c8 --report=none tap && c8 --report=none --no-clean test-ava && c8 report"
13
+ "cover": "c8 --report=none tap && c8 --report=none --no-clean test-ava && c8 report",
14
+ "test": "xo && tsd && npm run -s cover"
14
15
  },
15
16
  "files": [
16
17
  "lib",
@@ -78,7 +79,7 @@
78
79
  "currently-unhandled": "^0.4.1",
79
80
  "debug": "^4.3.1",
80
81
  "del": "^6.0.0",
81
- "emittery": "^0.7.2",
82
+ "emittery": "^0.8.0",
82
83
  "equal-length": "^1.0.0",
83
84
  "figures": "^3.2.0",
84
85
  "globby": "^11.0.1",
@@ -92,8 +93,8 @@
92
93
  "matcher": "^3.0.0",
93
94
  "md5-hex": "^3.0.1",
94
95
  "mem": "^8.0.0",
95
- "ms": "^2.1.2",
96
- "ora": "^5.1.0",
96
+ "ms": "^2.1.3",
97
+ "ora": "^5.2.0",
97
98
  "p-event": "^4.2.0",
98
99
  "p-map": "^4.0.0",
99
100
  "picomatch": "^2.2.2",
@@ -106,7 +107,7 @@
106
107
  "source-map-support": "^0.5.19",
107
108
  "stack-utils": "^2.0.3",
108
109
  "strip-ansi": "^6.0.0",
109
- "supertap": "^1.0.0",
110
+ "supertap": "^2.0.0",
110
111
  "temp-dir": "^2.0.0",
111
112
  "trim-off-newlines": "^1.0.1",
112
113
  "update-notifier": "^5.0.1",
@@ -119,25 +120,26 @@
119
120
  "@babel/plugin-proposal-do-expressions": "^7.12.1",
120
121
  "@sinonjs/fake-timers": "^6.0.1",
121
122
  "ansi-escapes": "^4.3.1",
122
- "c8": "^7.3.5",
123
+ "c8": "^7.4.0",
123
124
  "delay": "^4.4.0",
124
125
  "esm": "^3.2.25",
125
126
  "execa": "^5.0.0",
127
+ "fs-extra": "^9.0.1",
126
128
  "get-stream": "^6.0.0",
127
129
  "it-first": "^1.0.4",
128
130
  "proxyquire": "^2.1.3",
129
131
  "react": "^16.14.0",
130
132
  "react-test-renderer": "^16.14.0",
131
133
  "replace-string": "^3.1.0",
132
- "sinon": "^9.2.1",
134
+ "sinon": "^9.2.2",
133
135
  "source-map-fixtures": "^2.1.0",
134
136
  "tap": "^14.11.0",
135
137
  "temp-write": "^4.0.0",
136
138
  "tempy": "^1.0.0",
137
139
  "touch": "^3.1.0",
138
140
  "tsd": "^0.14.0",
139
- "typescript": "^4.1.2",
140
- "xo": "^0.35.0",
141
+ "typescript": "^4.1.3",
142
+ "xo": "^0.36.1",
141
143
  "zen-observable": "^0.8.15"
142
144
  }
143
145
  }
package/readme.md CHANGED
@@ -1,8 +1,5 @@
1
1
  # <img src="media/header.png" title="AVA" alt="AVA logo" width="530">
2
2
 
3
- [![Build Status](https://travis-ci.org/avajs/ava.svg?branch=master)](https://travis-ci.org/avajs/ava) [![Coverage Status](https://codecov.io/gh/avajs/ava/branch/master/graph/badge.svg)](https://codecov.io/gh/avajs/ava/branch/master) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/ava)
4
- [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs)
5
-
6
3
  AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and process isolation that lets you develop with confidence 🚀
7
4
 
8
5
  Follow the [AVA Twitter account](https://twitter.com/ava__js) for updates.
@@ -188,9 +185,7 @@ It's the [Andromeda galaxy](https://simple.wikipedia.org/wiki/Andromeda_galaxy).
188
185
 
189
186
  ## Support
190
187
 
191
- - [Stack Overflow](https://stackoverflow.com/questions/tagged/ava)
192
- - [Spectrum](https://spectrum.chat/ava)
193
- - [Twitter](https://twitter.com/ava__js)
188
+ - [GitHub Discussions](https://github.com/avajs/ava/discussions)
194
189
 
195
190
  ## Related
196
191