aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ava/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/ava/lib')
-rw-r--r--node_modules/ava/lib/assert.js378
-rw-r--r--node_modules/ava/lib/ava-error.js10
-rw-r--r--node_modules/ava/lib/ava-files.js282
-rw-r--r--node_modules/ava/lib/babel-config.js148
-rw-r--r--node_modules/ava/lib/beautify-stack.js37
-rw-r--r--node_modules/ava/lib/caching-precompiler.js103
-rw-r--r--node_modules/ava/lib/cli.js200
-rw-r--r--node_modules/ava/lib/code-excerpt.js57
-rw-r--r--node_modules/ava/lib/colors.js15
-rw-r--r--node_modules/ava/lib/concurrent.js64
-rw-r--r--node_modules/ava/lib/enhance-assert.js63
-rw-r--r--node_modules/ava/lib/extract-stack.js10
-rw-r--r--node_modules/ava/lib/fork.js178
-rw-r--r--node_modules/ava/lib/format-assert-error.js121
-rw-r--r--node_modules/ava/lib/globals.js10
-rw-r--r--node_modules/ava/lib/logger.js81
-rw-r--r--node_modules/ava/lib/main.js103
-rw-r--r--node_modules/ava/lib/prefix-title.js21
-rw-r--r--node_modules/ava/lib/process-adapter.js103
-rw-r--r--node_modules/ava/lib/reporters/improper-usage-messages.js21
-rw-r--r--node_modules/ava/lib/reporters/mini.js318
-rw-r--r--node_modules/ava/lib/reporters/tap.js119
-rw-r--r--node_modules/ava/lib/reporters/verbose.js165
-rw-r--r--node_modules/ava/lib/run-status.js125
-rw-r--r--node_modules/ava/lib/runner.js221
-rw-r--r--node_modules/ava/lib/sequence.js94
-rw-r--r--node_modules/ava/lib/serialize-error.js94
-rw-r--r--node_modules/ava/lib/test-collection.js206
-rw-r--r--node_modules/ava/lib/test-worker.js130
-rw-r--r--node_modules/ava/lib/test.js416
-rw-r--r--node_modules/ava/lib/validate-test.js48
-rw-r--r--node_modules/ava/lib/watcher.js322
32 files changed, 4263 insertions, 0 deletions
diff --git a/node_modules/ava/lib/assert.js b/node_modules/ava/lib/assert.js
new file mode 100644
index 000000000..c16e11a1a
--- /dev/null
+++ b/node_modules/ava/lib/assert.js
@@ -0,0 +1,378 @@
+'use strict';
+const coreAssert = require('core-assert');
+const deepEqual = require('lodash.isequal');
+const observableToPromise = require('observable-to-promise');
+const isObservable = require('is-observable');
+const isPromise = require('is-promise');
+const jestDiff = require('jest-diff');
+const enhanceAssert = require('./enhance-assert');
+const formatAssertError = require('./format-assert-error');
+
+class AssertionError extends Error {
+ constructor(opts) {
+ super(opts.message || '');
+ this.name = 'AssertionError';
+
+ this.assertion = opts.assertion;
+ this.fixedSource = opts.fixedSource;
+ this.improperUsage = opts.improperUsage || false;
+ this.operator = opts.operator;
+ this.values = opts.values || [];
+
+ // Reserved for power-assert statements
+ this.statements = [];
+
+ if (opts.stack) {
+ this.stack = opts.stack;
+ }
+ }
+}
+exports.AssertionError = AssertionError;
+
+function getStack() {
+ const obj = {};
+ Error.captureStackTrace(obj, getStack);
+ return obj.stack;
+}
+
+function wrapAssertions(callbacks) {
+ const pass = callbacks.pass;
+ const pending = callbacks.pending;
+ const fail = callbacks.fail;
+
+ const noop = () => {};
+ const makeNoop = () => noop;
+ const makeRethrow = reason => () => {
+ throw reason;
+ };
+
+ const assertions = {
+ pass() {
+ pass(this);
+ },
+
+ fail(message) {
+ fail(this, new AssertionError({
+ assertion: 'fail',
+ message: message || 'Test failed via `t.fail()`'
+ }));
+ },
+
+ is(actual, expected, message) {
+ if (actual === expected) {
+ pass(this);
+ } else {
+ const diff = formatAssertError.formatDiff(actual, expected);
+ const values = diff ? [diff] : [
+ formatAssertError.formatWithLabel('Actual:', actual),
+ formatAssertError.formatWithLabel('Must be strictly equal to:', expected)
+ ];
+
+ fail(this, new AssertionError({
+ assertion: 'is',
+ message,
+ operator: '===',
+ values
+ }));
+ }
+ },
+
+ not(actual, expected, message) {
+ if (actual === expected) {
+ fail(this, new AssertionError({
+ assertion: 'not',
+ message,
+ operator: '!==',
+ values: [formatAssertError.formatWithLabel('Value is strictly equal:', actual)]
+ }));
+ } else {
+ pass(this);
+ }
+ },
+
+ deepEqual(actual, expected, message) {
+ if (deepEqual(actual, expected)) {
+ pass(this);
+ } else {
+ const diff = formatAssertError.formatDiff(actual, expected);
+ const values = diff ? [diff] : [
+ formatAssertError.formatWithLabel('Actual:', actual),
+ formatAssertError.formatWithLabel('Must be deeply equal to:', expected)
+ ];
+
+ fail(this, new AssertionError({
+ assertion: 'deepEqual',
+ message,
+ values
+ }));
+ }
+ },
+
+ notDeepEqual(actual, expected, message) {
+ if (deepEqual(actual, expected)) {
+ fail(this, new AssertionError({
+ assertion: 'notDeepEqual',
+ message,
+ values: [formatAssertError.formatWithLabel('Value is deeply equal:', actual)]
+ }));
+ } else {
+ pass(this);
+ }
+ },
+
+ throws(fn, err, message) {
+ let promise;
+ if (isPromise(fn)) {
+ promise = fn;
+ } else if (isObservable(fn)) {
+ promise = observableToPromise(fn);
+ } else if (typeof fn !== 'function') {
+ fail(this, new AssertionError({
+ assertion: 'throws',
+ improperUsage: true,
+ message: '`t.throws()` must be called with a function, Promise, or Observable',
+ values: [formatAssertError.formatWithLabel('Called with:', fn)]
+ }));
+ return;
+ }
+
+ let coreAssertThrowsErrorArg;
+ if (typeof err === 'string') {
+ const expectedMessage = err;
+ coreAssertThrowsErrorArg = error => error.message === expectedMessage;
+ } else {
+ // Assume it's a constructor function or regular expression
+ coreAssertThrowsErrorArg = err;
+ }
+
+ const test = (fn, stack) => {
+ let actual;
+ let threw = false;
+ try {
+ coreAssert.throws(() => {
+ try {
+ fn();
+ } catch (err) {
+ actual = err;
+ threw = true;
+ throw err;
+ }
+ }, coreAssertThrowsErrorArg);
+ return actual;
+ } catch (err) {
+ const values = threw ?
+ [formatAssertError.formatWithLabel('Threw unexpected exception:', actual)] :
+ null;
+
+ throw new AssertionError({
+ assertion: 'throws',
+ message,
+ stack,
+ values
+ });
+ }
+ };
+
+ if (promise) {
+ // Record stack before it gets lost in the promise chain.
+ const stack = getStack();
+ const intermediate = promise.then(makeNoop, makeRethrow).then(fn => test(fn, stack));
+ pending(this, intermediate);
+ // Don't reject the returned promise, even if the assertion fails.
+ return intermediate.catch(noop);
+ }
+
+ try {
+ const retval = test(fn);
+ pass(this);
+ return retval;
+ } catch (err) {
+ fail(this, err);
+ }
+ },
+
+ notThrows(fn, message) {
+ let promise;
+ if (isPromise(fn)) {
+ promise = fn;
+ } else if (isObservable(fn)) {
+ promise = observableToPromise(fn);
+ } else if (typeof fn !== 'function') {
+ fail(this, new AssertionError({
+ assertion: 'notThrows',
+ improperUsage: true,
+ message: '`t.notThrows()` must be called with a function, Promise, or Observable',
+ values: [formatAssertError.formatWithLabel('Called with:', fn)]
+ }));
+ return;
+ }
+
+ const test = (fn, stack) => {
+ try {
+ coreAssert.doesNotThrow(fn);
+ } catch (err) {
+ throw new AssertionError({
+ assertion: 'notThrows',
+ message,
+ stack,
+ values: [formatAssertError.formatWithLabel('Threw:', err.actual)]
+ });
+ }
+ };
+
+ if (promise) {
+ // Record stack before it gets lost in the promise chain.
+ const stack = getStack();
+ const intermediate = promise.then(noop, reason => test(makeRethrow(reason), stack));
+ pending(this, intermediate);
+ // Don't reject the returned promise, even if the assertion fails.
+ return intermediate.catch(noop);
+ }
+
+ try {
+ test(fn);
+ pass(this);
+ } catch (err) {
+ fail(this, err);
+ }
+ },
+
+ ifError(actual, message) {
+ if (actual) {
+ fail(this, new AssertionError({
+ assertion: 'ifError',
+ message,
+ values: [formatAssertError.formatWithLabel('Error:', actual)]
+ }));
+ } else {
+ pass(this);
+ }
+ },
+
+ snapshot(actual, message) {
+ const state = this._test.getSnapshotState();
+ const result = state.match(this.title, actual);
+ if (result.pass) {
+ pass(this);
+ } else {
+ const diff = jestDiff(result.expected.trim(), result.actual.trim(), {expand: true})
+ // Remove annotation
+ .split('\n')
+ .slice(3)
+ .join('\n');
+ fail(this, new AssertionError({
+ assertion: 'snapshot',
+ message: message || 'Did not match snapshot',
+ values: [{label: 'Difference:', formatted: diff}]
+ }));
+ }
+ }
+ };
+
+ const enhancedAssertions = enhanceAssert(pass, fail, {
+ truthy(actual, message) {
+ if (!actual) {
+ throw new AssertionError({
+ assertion: 'truthy',
+ message,
+ operator: '!!',
+ values: [formatAssertError.formatWithLabel('Value is not truthy:', actual)]
+ });
+ }
+ },
+
+ falsy(actual, message) {
+ if (actual) {
+ throw new AssertionError({
+ assertion: 'falsy',
+ message,
+ operator: '!',
+ values: [formatAssertError.formatWithLabel('Value is not falsy:', actual)]
+ });
+ }
+ },
+
+ true(actual, message) {
+ if (actual !== true) {
+ throw new AssertionError({
+ assertion: 'true',
+ message,
+ values: [formatAssertError.formatWithLabel('Value is not `true`:', actual)]
+ });
+ }
+ },
+
+ false(actual, message) {
+ if (actual !== false) {
+ throw new AssertionError({
+ assertion: 'false',
+ message,
+ values: [formatAssertError.formatWithLabel('Value is not `false`:', actual)]
+ });
+ }
+ },
+
+ regex(string, regex, message) {
+ if (typeof string !== 'string') {
+ throw new AssertionError({
+ assertion: 'regex',
+ improperUsage: true,
+ message: '`t.regex()` must be called with a string',
+ values: [formatAssertError.formatWithLabel('Called with:', string)]
+ });
+ }
+ if (!(regex instanceof RegExp)) {
+ throw new AssertionError({
+ assertion: 'regex',
+ improperUsage: true,
+ message: '`t.regex()` must be called with a regular expression',
+ values: [formatAssertError.formatWithLabel('Called with:', regex)]
+ });
+ }
+
+ if (!regex.test(string)) {
+ throw new AssertionError({
+ assertion: 'regex',
+ message,
+ values: [
+ formatAssertError.formatWithLabel('Value must match expression:', string),
+ formatAssertError.formatWithLabel('Regular expression:', regex)
+ ]
+ });
+ }
+ },
+
+ notRegex(string, regex, message) {
+ if (typeof string !== 'string') {
+ throw new AssertionError({
+ assertion: 'notRegex',
+ improperUsage: true,
+ message: '`t.notRegex()` must be called with a string',
+ values: [formatAssertError.formatWithLabel('Called with:', string)]
+ });
+ }
+ if (!(regex instanceof RegExp)) {
+ throw new AssertionError({
+ assertion: 'notRegex',
+ improperUsage: true,
+ message: '`t.notRegex()` must be called with a regular expression',
+ values: [formatAssertError.formatWithLabel('Called with:', regex)]
+ });
+ }
+
+ if (regex.test(string)) {
+ throw new AssertionError({
+ assertion: 'notRegex',
+ message,
+ values: [
+ formatAssertError.formatWithLabel('Value must not match expression:', string),
+ formatAssertError.formatWithLabel('Regular expression:', regex)
+ ]
+ });
+ }
+ }
+ });
+
+ return Object.assign(assertions, enhancedAssertions);
+}
+exports.wrapAssertions = wrapAssertions;
diff --git a/node_modules/ava/lib/ava-error.js b/node_modules/ava/lib/ava-error.js
new file mode 100644
index 000000000..05df6b349
--- /dev/null
+++ b/node_modules/ava/lib/ava-error.js
@@ -0,0 +1,10 @@
+'use strict';
+
+class AvaError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = 'AvaError';
+ }
+}
+
+module.exports = AvaError;
diff --git a/node_modules/ava/lib/ava-files.js b/node_modules/ava/lib/ava-files.js
new file mode 100644
index 000000000..dd9a2ee6d
--- /dev/null
+++ b/node_modules/ava/lib/ava-files.js
@@ -0,0 +1,282 @@
+'use strict';
+const fs = require('fs');
+const path = require('path');
+const Promise = require('bluebird');
+const slash = require('slash');
+const globby = require('globby');
+const flatten = require('lodash.flatten');
+const autoBind = require('auto-bind');
+const defaultIgnore = require('ignore-by-default').directories();
+const multimatch = require('multimatch');
+
+function handlePaths(files, excludePatterns, globOptions) {
+ // Convert Promise to Bluebird
+ files = Promise.resolve(globby(files.concat(excludePatterns), globOptions));
+
+ const searchedParents = new Set();
+ const foundFiles = new Set();
+
+ function alreadySearchingParent(dir) {
+ if (searchedParents.has(dir)) {
+ return true;
+ }
+
+ const parentDir = path.dirname(dir);
+
+ if (parentDir === dir) {
+ // We have reached the root path
+ return false;
+ }
+
+ return alreadySearchingParent(parentDir);
+ }
+
+ return files
+ .map(file => {
+ file = path.resolve(globOptions.cwd, file);
+
+ if (fs.statSync(file).isDirectory()) {
+ if (alreadySearchingParent(file)) {
+ return null;
+ }
+
+ searchedParents.add(file);
+
+ let pattern = path.join(file, '**', '*.js');
+
+ if (process.platform === 'win32') {
+ // Always use `/` in patterns, harmonizing matching across platforms
+ pattern = slash(pattern);
+ }
+
+ return handlePaths([pattern], excludePatterns, globOptions);
+ }
+
+ // `globby` returns slashes even on Windows. Normalize here so the file
+ // paths are consistently platform-accurate as tests are run.
+ return path.normalize(file);
+ })
+ .then(flatten)
+ .filter(file => file && path.extname(file) === '.js')
+ .filter(file => {
+ if (path.basename(file)[0] === '_' && globOptions.includeUnderscoredFiles !== true) {
+ return false;
+ }
+
+ return true;
+ })
+ .map(file => path.resolve(file))
+ .filter(file => {
+ const alreadyFound = foundFiles.has(file);
+ foundFiles.add(file);
+ return !alreadyFound;
+ });
+}
+
+const defaultExcludePatterns = () => [
+ '!**/node_modules/**',
+ '!**/fixtures/**',
+ '!**/helpers/**'
+];
+
+const defaultIncludePatterns = () => [
+ 'test.js',
+ 'test-*.js',
+ 'test',
+ '**/__tests__',
+ '**/*.test.js'
+];
+
+const defaultHelperPatterns = () => [
+ '**/__tests__/helpers/**/*.js',
+ '**/__tests__/**/_*.js',
+ '**/test/helpers/**/*.js',
+ '**/test/**/_*.js'
+];
+
+const getDefaultIgnorePatterns = () => defaultIgnore.map(dir => `${dir}/**/*`);
+
+// Used on paths before they're passed to multimatch to harmonize matching
+// across platforms
+const matchable = process.platform === 'win32' ? slash : (path => path);
+
+class AvaFiles {
+ constructor(options) {
+ options = options || {};
+
+ let files = (options.files || []).map(file => {
+ // `./` should be removed from the beginning of patterns because
+ // otherwise they won't match change events from Chokidar
+ if (file.slice(0, 2) === './') {
+ return file.slice(2);
+ }
+
+ return file;
+ });
+
+ if (files.length === 0) {
+ files = defaultIncludePatterns();
+ }
+
+ this.excludePatterns = defaultExcludePatterns();
+ this.files = files;
+ this.sources = options.sources || [];
+ this.cwd = options.cwd || process.cwd();
+
+ autoBind(this);
+ }
+ findTestFiles() {
+ return handlePaths(this.files, this.excludePatterns, {
+ cwd: this.cwd,
+ cache: Object.create(null),
+ statCache: Object.create(null),
+ realpathCache: Object.create(null),
+ symlinks: Object.create(null)
+ });
+ }
+ findTestHelpers() {
+ return handlePaths(defaultHelperPatterns(), ['!**/node_modules/**'], {
+ cwd: this.cwd,
+ includeUnderscoredFiles: true,
+ cache: Object.create(null),
+ statCache: Object.create(null),
+ realpathCache: Object.create(null),
+ symlinks: Object.create(null)
+ });
+ }
+ isSource(filePath) {
+ let mixedPatterns = [];
+ const defaultIgnorePatterns = getDefaultIgnorePatterns();
+ const overrideDefaultIgnorePatterns = [];
+
+ let hasPositivePattern = false;
+ this.sources.forEach(pattern => {
+ mixedPatterns.push(pattern);
+
+ // TODO: Why not just `pattern[0] !== '!'`?
+ if (!hasPositivePattern && pattern[0] !== '!') {
+ hasPositivePattern = true;
+ }
+
+ // Extract patterns that start with an ignored directory. These need to be
+ // rematched separately.
+ if (defaultIgnore.indexOf(pattern.split('/')[0]) >= 0) {
+ overrideDefaultIgnorePatterns.push(pattern);
+ }
+ });
+
+ // Same defaults as used for Chokidar
+ if (!hasPositivePattern) {
+ mixedPatterns = ['package.json', '**/*.js'].concat(mixedPatterns);
+ }
+
+ filePath = matchable(filePath);
+
+ // Ignore paths outside the current working directory.
+ // They can't be matched to a pattern.
+ if (/^\.\.\//.test(filePath)) {
+ return false;
+ }
+
+ const isSource = multimatch(filePath, mixedPatterns).length === 1;
+ if (!isSource) {
+ return false;
+ }
+
+ const isIgnored = multimatch(filePath, defaultIgnorePatterns).length === 1;
+ if (!isIgnored) {
+ return true;
+ }
+
+ const isErroneouslyIgnored = multimatch(filePath, overrideDefaultIgnorePatterns).length === 1;
+ if (isErroneouslyIgnored) {
+ return true;
+ }
+
+ return false;
+ }
+ isTest(filePath) {
+ const excludePatterns = this.excludePatterns;
+ const initialPatterns = this.files.concat(excludePatterns);
+
+ // Like in `api.js`, tests must be `.js` files and not start with `_`
+ if (path.extname(filePath) !== '.js' || path.basename(filePath)[0] === '_') {
+ return false;
+ }
+
+ // Check if the entire path matches a pattern
+ if (multimatch(matchable(filePath), initialPatterns).length === 1) {
+ return true;
+ }
+
+ // Check if the path contains any directory components
+ const dirname = path.dirname(filePath);
+ if (dirname === '.') {
+ return false;
+ }
+
+ // Compute all possible subpaths. Note that the dirname is assumed to be
+ // relative to the working directory, without a leading `./`.
+ const subpaths = dirname.split(/[\\/]/).reduce((subpaths, component) => {
+ const parent = subpaths[subpaths.length - 1];
+
+ if (parent) {
+ // Always use `/`` to makes multimatch consistent across platforms
+ subpaths.push(`${parent}/${component}`);
+ } else {
+ subpaths.push(component);
+ }
+
+ return subpaths;
+ }, []);
+
+ // Check if any of the possible subpaths match a pattern. If so, generate a
+ // new pattern with **/*.js.
+ const recursivePatterns = subpaths
+ .filter(subpath => multimatch(subpath, initialPatterns).length === 1)
+ // Always use `/` to makes multimatch consistent across platforms
+ .map(subpath => `${subpath}/**/*.js`);
+
+ // See if the entire path matches any of the subpaths patterns, taking the
+ // excludePatterns into account. This mimicks the behavior in api.js
+ return multimatch(matchable(filePath), recursivePatterns.concat(excludePatterns)).length === 1;
+ }
+ getChokidarPatterns() {
+ let paths = [];
+ let ignored = [];
+
+ this.sources.forEach(pattern => {
+ if (pattern[0] === '!') {
+ ignored.push(pattern.slice(1));
+ } else {
+ paths.push(pattern);
+ }
+ });
+
+ // Allow source patterns to override the default ignore patterns. Chokidar
+ // ignores paths that match the list of ignored patterns. It uses anymatch
+ // under the hood, which supports negation patterns. For any source pattern
+ // that starts with an ignored directory, ensure the corresponding negation
+ // pattern is added to the ignored paths.
+ const overrideDefaultIgnorePatterns = paths
+ .filter(pattern => defaultIgnore.indexOf(pattern.split('/')[0]) >= 0)
+ .map(pattern => `!${pattern}`);
+
+ ignored = getDefaultIgnorePatterns().concat(ignored, overrideDefaultIgnorePatterns);
+
+ if (paths.length === 0) {
+ paths = ['package.json', '**/*.js'];
+ }
+
+ paths = paths.concat(this.files);
+
+ return {
+ paths,
+ ignored
+ };
+ }
+}
+
+module.exports = AvaFiles;
+module.exports.defaultIncludePatterns = defaultIncludePatterns;
+module.exports.defaultExcludePatterns = defaultExcludePatterns;
diff --git a/node_modules/ava/lib/babel-config.js b/node_modules/ava/lib/babel-config.js
new file mode 100644
index 000000000..c3be0dcfb
--- /dev/null
+++ b/node_modules/ava/lib/babel-config.js
@@ -0,0 +1,148 @@
+'use strict';
+const fs = require('fs');
+const path = require('path');
+const chalk = require('chalk');
+const figures = require('figures');
+const configManager = require('hullabaloo-config-manager');
+const md5Hex = require('md5-hex');
+const mkdirp = require('mkdirp');
+const colors = require('./colors');
+
+function validate(conf) {
+ if (conf === undefined || conf === null) {
+ conf = 'default';
+ }
+
+ // Check for valid babel config shortcuts (can be either `default` or `inherit`)
+ const isValidShortcut = conf === 'default' || conf === 'inherit';
+
+ if (!conf || (typeof conf === 'string' && !isValidShortcut)) {
+ let message = colors.error(figures.cross);
+ message += ' Unexpected Babel configuration for AVA. ';
+ message += 'See ' + chalk.underline('https://github.com/avajs/ava#es2015-support') + ' for allowed values.';
+
+ throw new Error(message);
+ }
+
+ return conf;
+}
+
+const SOURCE = '(AVA) Base Babel config';
+const AVA_DIR = path.join(__dirname, '..');
+
+function verifyExistingOptions(verifierFile, baseConfig, cache) {
+ return new Promise((resolve, reject) => {
+ try {
+ resolve(fs.readFileSync(verifierFile));
+ } catch (err) {
+ if (err && err.code === 'ENOENT') {
+ resolve(null);
+ } else {
+ reject(err);
+ }
+ }
+ })
+ .then(buffer => {
+ if (!buffer) {
+ return null;
+ }
+
+ const verifier = configManager.restoreVerifier(buffer);
+ const fixedSourceHashes = new Map();
+ fixedSourceHashes.set(baseConfig.source, baseConfig.hash);
+ if (baseConfig.extends) {
+ fixedSourceHashes.set(baseConfig.extends.source, baseConfig.extends.hash);
+ }
+ return verifier.verifyCurrentEnv({sources: fixedSourceHashes}, cache)
+ .then(result => {
+ if (!result.cacheKeys) {
+ return null;
+ }
+
+ if (result.dependenciesChanged) {
+ fs.writeFileSync(verifierFile, result.verifier.toBuffer());
+ }
+
+ return result.cacheKeys;
+ });
+ });
+}
+
+function resolveOptions(baseConfig, cache, optionsFile, verifierFile) {
+ return configManager.fromConfig(baseConfig, {cache})
+ .then(result => {
+ fs.writeFileSync(optionsFile, result.generateModule());
+
+ return result.createVerifier()
+ .then(verifier => {
+ fs.writeFileSync(verifierFile, verifier.toBuffer());
+ return verifier.cacheKeysForCurrentEnv();
+ });
+ });
+}
+
+function build(projectDir, cacheDir, userOptions, powerAssert) {
+ // Compute a seed based on the Node.js version and the project directory.
+ // Dependency hashes may vary based on the Node.js version, e.g. with the
+ // @ava/stage-4 Babel preset. Sources and dependencies paths are absolute in
+ // the generated module and verifier state. Those paths wouldn't necessarily
+ // be valid if the project directory changes.
+ const seed = md5Hex([process.versions.node, projectDir]);
+
+ // Ensure cacheDir exists
+ mkdirp.sync(cacheDir);
+
+ // The file names predict where valid options may be cached, and thus should
+ // include the seed.
+ const optionsFile = path.join(cacheDir, `${seed}.babel-options.js`);
+ const verifierFile = path.join(cacheDir, `${seed}.verifier.bin`);
+
+ const baseOptions = {
+ babelrc: false,
+ presets: [
+ ['@ava/transform-test-files', {powerAssert}]
+ ]
+ };
+ if (userOptions === 'default') {
+ baseOptions.presets.unshift('@ava/stage-4');
+ }
+
+ const baseConfig = configManager.createConfig({
+ dir: AVA_DIR, // Presets are resolved relative to this directory
+ hash: md5Hex(JSON.stringify(baseOptions)),
+ json5: false,
+ options: baseOptions,
+ source: SOURCE
+ });
+
+ if (userOptions !== 'default') {
+ baseConfig.extend(configManager.createConfig({
+ dir: projectDir,
+ options: userOptions === 'inherit' ?
+ {babelrc: true} :
+ userOptions,
+ source: path.join(projectDir, 'package.json') + '#ava.babel',
+ hash: md5Hex(JSON.stringify(userOptions))
+ }));
+ }
+
+ const cache = configManager.prepareCache();
+ return verifyExistingOptions(verifierFile, baseConfig, cache)
+ .then(cacheKeys => {
+ if (cacheKeys) {
+ return cacheKeys;
+ }
+
+ return resolveOptions(baseConfig, cache, optionsFile, verifierFile);
+ })
+ .then(cacheKeys => ({
+ getOptions: require(optionsFile).getOptions, // eslint-disable-line import/no-dynamic-require
+ // Include the seed in the cache keys used to store compilation results.
+ cacheKeys: Object.assign({seed}, cacheKeys)
+ }));
+}
+
+module.exports = {
+ validate,
+ build
+};
diff --git a/node_modules/ava/lib/beautify-stack.js b/node_modules/ava/lib/beautify-stack.js
new file mode 100644
index 000000000..189ed0714
--- /dev/null
+++ b/node_modules/ava/lib/beautify-stack.js
@@ -0,0 +1,37 @@
+'use strict';
+const StackUtils = require('stack-utils');
+const cleanStack = require('clean-stack');
+const debug = require('debug')('ava');
+
+// Ignore unimportant stack trace lines
+let ignoreStackLines = [];
+
+const avaInternals = /\/ava\/(?:lib\/)?[\w-]+\.js:\d+:\d+\)?$/;
+const avaDependencies = /\/node_modules\/(?:bluebird|empower-core|(?:ava\/node_modules\/)?(?:babel-runtime|core-js))\//;
+
+if (!debug.enabled) {
+ ignoreStackLines = StackUtils.nodeInternals();
+ ignoreStackLines.push(avaInternals);
+ ignoreStackLines.push(avaDependencies);
+}
+
+const stackUtils = new StackUtils({internals: ignoreStackLines});
+
+module.exports = stack => {
+ if (!stack) {
+ return '';
+ }
+
+ // Workaround for https://github.com/tapjs/stack-utils/issues/14
+ // TODO: fix it in `stack-utils`
+ stack = cleanStack(stack);
+
+ const title = stack.split('\n')[0];
+ const lines = stackUtils
+ .clean(stack)
+ .split('\n')
+ .map(x => ` ${x}`)
+ .join('\n');
+
+ return `${title}\n${lines}`;
+};
diff --git a/node_modules/ava/lib/caching-precompiler.js b/node_modules/ava/lib/caching-precompiler.js
new file mode 100644
index 000000000..937309bf0
--- /dev/null
+++ b/node_modules/ava/lib/caching-precompiler.js
@@ -0,0 +1,103 @@
+'use strict';
+const path = require('path');
+const fs = require('fs');
+const convertSourceMap = require('convert-source-map');
+const cachingTransform = require('caching-transform');
+const packageHash = require('package-hash');
+const stripBomBuf = require('strip-bom-buf');
+const autoBind = require('auto-bind');
+const md5Hex = require('md5-hex');
+
+function getSourceMap(filePath, code) {
+ let sourceMap = convertSourceMap.fromSource(code);
+
+ if (!sourceMap) {
+ const dirPath = path.dirname(filePath);
+ sourceMap = convertSourceMap.fromMapFileSource(code, dirPath);
+ }
+
+ if (sourceMap) {
+ sourceMap = sourceMap.toObject();
+ }
+
+ return sourceMap;
+}
+
+class CachingPrecompiler {
+ constructor(options) {
+ autoBind(this);
+
+ this.getBabelOptions = options.getBabelOptions;
+ this.babelCacheKeys = options.babelCacheKeys;
+ this.cacheDirPath = options.path;
+ this.fileHashes = {};
+ this.transform = this._createTransform();
+ }
+ precompileFile(filePath) {
+ if (!this.fileHashes[filePath]) {
+ const source = stripBomBuf(fs.readFileSync(filePath));
+ this.transform(source, filePath);
+ }
+
+ return this.fileHashes[filePath];
+ }
+ // Conditionally called by caching-transform when precompiling is required
+ _init() {
+ this.babel = require('babel-core');
+ return this._transform;
+ }
+ _transform(code, filePath, hash) {
+ code = code.toString();
+
+ let result;
+ const originalBabelDisableCache = process.env.BABEL_DISABLE_CACHE;
+ try {
+ // Disable Babel's cache. AVA has good cache management already.
+ process.env.BABEL_DISABLE_CACHE = '1';
+
+ result = this.babel.transform(code, Object.assign(this.getBabelOptions(), {
+ inputSourceMap: getSourceMap(filePath, code),
+ filename: filePath,
+ sourceMaps: true,
+ ast: false
+ }));
+ } finally {
+ // Restore the original value. It is passed to workers, where users may
+ // not want Babel's cache to be disabled.
+ process.env.BABEL_DISABLE_CACHE = originalBabelDisableCache;
+ }
+
+ // Save source map
+ const mapPath = path.join(this.cacheDirPath, `${hash}.js.map`);
+ fs.writeFileSync(mapPath, JSON.stringify(result.map));
+
+ // Append source map comment to transformed code
+ // So that other libraries (like nyc) can find the source map
+ const dirPath = path.dirname(filePath);
+ const relativeMapPath = path.relative(dirPath, mapPath);
+ const comment = convertSourceMap.generateMapFileComment(relativeMapPath);
+
+ return `${result.code}\n${comment}`;
+ }
+ _createTransform() {
+ const salt = packageHash.sync([
+ require.resolve('../package.json'),
+ require.resolve('babel-core/package.json')
+ ], this.babelCacheKeys);
+
+ return cachingTransform({
+ factory: this._init,
+ cacheDir: this.cacheDirPath,
+ hash: this._generateHash,
+ salt,
+ ext: '.js'
+ });
+ }
+ _generateHash(code, filePath, salt) {
+ const hash = md5Hex([code, filePath, salt]);
+ this.fileHashes[filePath] = hash;
+ return hash;
+ }
+}
+
+module.exports = CachingPrecompiler;
diff --git a/node_modules/ava/lib/cli.js b/node_modules/ava/lib/cli.js
new file mode 100644
index 000000000..f6213f107
--- /dev/null
+++ b/node_modules/ava/lib/cli.js
@@ -0,0 +1,200 @@
+'use strict';
+const path = require('path');
+const updateNotifier = require('update-notifier');
+const figures = require('figures');
+const arrify = require('arrify');
+const meow = require('meow');
+const Promise = require('bluebird');
+const pkgConf = require('pkg-conf');
+const isCi = require('is-ci');
+const hasFlag = require('has-flag');
+const Api = require('../api');
+const colors = require('./colors');
+const VerboseReporter = require('./reporters/verbose');
+const MiniReporter = require('./reporters/mini');
+const TapReporter = require('./reporters/tap');
+const Logger = require('./logger');
+const Watcher = require('./watcher');
+const babelConfigHelper = require('./babel-config');
+
+// Bluebird specific
+Promise.longStackTraces();
+
+exports.run = () => {
+ const conf = pkgConf.sync('ava');
+
+ const filepath = pkgConf.filepath(conf);
+ const projectDir = filepath === null ? process.cwd() : path.dirname(filepath);
+
+ const cli = meow(`
+ Usage
+ ava [<file|directory|glob> ...]
+
+ Options
+ --init Add AVA to your project
+ --fail-fast Stop after first test failure
+ --serial, -s Run tests serially
+ --tap, -t Generate TAP output
+ --verbose, -v Enable verbose output
+ --no-cache Disable the transpiler cache
+ --no-power-assert Disable Power Assert
+ --color Force color output
+ --no-color Disable color output
+ --match, -m Only run tests with matching title (Can be repeated)
+ --watch, -w Re-run tests when tests and source files change
+ --timeout, -T Set global timeout
+ --concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
+ --update-snapshots, -u Update snapshots
+
+ Examples
+ ava
+ ava test.js test2.js
+ ava test-*.js
+ ava test
+ ava --init
+ ava --init foo.js
+
+ Default patterns when no arguments:
+ test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js
+ `, {
+ string: [
+ '_',
+ 'match',
+ 'timeout',
+ 'concurrency'
+ ],
+ boolean: [
+ 'init',
+ 'fail-fast',
+ 'serial',
+ 'tap',
+ 'verbose',
+ 'watch',
+ 'update-snapshots',
+ 'color'
+ ],
+ default: {
+ cache: conf.cache,
+ color: 'color' in conf ? conf.color : require('supports-color') !== false,
+ concurrency: conf.concurrency,
+ failFast: conf.failFast,
+ init: conf.init,
+ match: conf.match,
+ powerAssert: conf.powerAssert,
+ serial: conf.serial,
+ tap: conf.tap,
+ timeout: conf.timeout,
+ updateSnapshots: conf.updateSnapshots,
+ verbose: conf.verbose,
+ watch: conf.watch
+ },
+ alias: {
+ t: 'tap',
+ v: 'verbose',
+ s: 'serial',
+ m: 'match',
+ w: 'watch',
+ T: 'timeout',
+ c: 'concurrency',
+ u: 'update-snapshots'
+ }
+ });
+
+ updateNotifier({pkg: cli.pkg}).notify();
+
+ if (cli.flags.init) {
+ require('ava-init')();
+ return;
+ }
+
+ if (
+ ((hasFlag('--watch') || hasFlag('-w')) && (hasFlag('--tap') || hasFlag('-t'))) ||
+ (conf.watch && conf.tap)
+ ) {
+ throw new Error(colors.error(figures.cross) + ' The TAP reporter is not available when using watch mode.');
+ }
+
+ if ((hasFlag('--watch') || hasFlag('-w')) && isCi) {
+ throw new Error(colors.error(figures.cross) + ' Watch mode is not available in CI, as it prevents AVA from terminating.');
+ }
+
+ if (hasFlag('--require') || hasFlag('-r')) {
+ throw new Error(colors.error(figures.cross) + ' The --require and -r flags are deprecated. Requirements should be configured in package.json - see documentation.');
+ }
+
+ // Copy resultant cli.flags into conf for use with Api and elsewhere
+ Object.assign(conf, cli.flags);
+
+ const api = new Api({
+ failFast: conf.failFast,
+ failWithoutAssertions: conf.failWithoutAssertions !== false,
+ serial: conf.serial,
+ require: arrify(conf.require),
+ cacheEnabled: conf.cache !== false,
+ powerAssert: conf.powerAssert !== false,
+ explicitTitles: conf.watch,
+ match: arrify(conf.match),
+ babelConfig: babelConfigHelper.validate(conf.babel),
+ resolveTestsFrom: cli.input.length === 0 ? projectDir : process.cwd(),
+ projectDir,
+ timeout: conf.timeout,
+ concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
+ updateSnapshots: conf.updateSnapshots,
+ color: conf.color
+ });
+
+ let reporter;
+
+ if (conf.tap && !conf.watch) {
+ reporter = new TapReporter();
+ } else if (conf.verbose || isCi) {
+ reporter = new VerboseReporter({color: conf.color});
+ } else {
+ reporter = new MiniReporter({color: conf.color, watching: conf.watch});
+ }
+
+ reporter.api = api;
+ const logger = new Logger(reporter);
+
+ logger.start();
+
+ api.on('test-run', runStatus => {
+ reporter.api = runStatus;
+ runStatus.on('test', logger.test);
+ runStatus.on('error', logger.unhandledError);
+
+ runStatus.on('stdout', logger.stdout);
+ runStatus.on('stderr', logger.stderr);
+ });
+
+ const files = cli.input.length ? cli.input : arrify(conf.files);
+
+ if (conf.watch) {
+ try {
+ const watcher = new Watcher(logger, api, files, arrify(conf.source));
+ watcher.observeStdin(process.stdin);
+ } catch (err) {
+ if (err.name === 'AvaError') {
+ // An AvaError may be thrown if `chokidar` is not installed. Log it nicely.
+ console.error(` ${colors.error(figures.cross)} ${err.message}`);
+ logger.exit(1);
+ } else {
+ // Rethrow so it becomes an uncaught exception
+ throw err;
+ }
+ }
+ } else {
+ api.run(files)
+ .then(runStatus => {
+ logger.finish(runStatus);
+ logger.exit(runStatus.failCount > 0 || runStatus.rejectionCount > 0 || runStatus.exceptionCount > 0 ? 1 : 0);
+ })
+ .catch(err => {
+ // Don't swallow exceptions. Note that any expected error should already
+ // have been logged.
+ setImmediate(() => {
+ throw err;
+ });
+ });
+ }
+};
diff --git a/node_modules/ava/lib/code-excerpt.js b/node_modules/ava/lib/code-excerpt.js
new file mode 100644
index 000000000..aa619a0b2
--- /dev/null
+++ b/node_modules/ava/lib/code-excerpt.js
@@ -0,0 +1,57 @@
+'use strict';
+const fs = require('fs');
+const equalLength = require('equal-length');
+const codeExcerpt = require('code-excerpt');
+const truncate = require('cli-truncate');
+const chalk = require('chalk');
+
+const formatLineNumber = (lineNumber, maxLineNumber) =>
+ ' '.repeat(Math.max(0, String(maxLineNumber).length - String(lineNumber).length)) + lineNumber;
+
+module.exports = (source, options) => {
+ if (!source.isWithinProject || source.isDependency) {
+ return null;
+ }
+
+ const file = source.file;
+ const line = source.line;
+
+ options = options || {};
+ const maxWidth = options.maxWidth || 80;
+
+ let contents;
+ try {
+ contents = fs.readFileSync(file, 'utf8');
+ } catch (err) {
+ return null;
+ }
+
+ const excerpt = codeExcerpt(contents, line, {around: 1});
+ if (!excerpt) {
+ return null;
+ }
+
+ const lines = excerpt.map(item => ({
+ line: item.line,
+ value: truncate(item.value, maxWidth - String(line).length - 5)
+ }));
+
+ const joinedLines = lines.map(line => line.value).join('\n');
+ const extendedLines = equalLength(joinedLines).split('\n');
+
+ return lines
+ .map((item, index) => ({
+ line: item.line,
+ value: extendedLines[index]
+ }))
+ .map(item => {
+ const isErrorSource = item.line === line;
+
+ const lineNumber = formatLineNumber(item.line, line) + ':';
+ const coloredLineNumber = isErrorSource ? lineNumber : chalk.grey(lineNumber);
+ const result = ` ${coloredLineNumber} ${item.value}`;
+
+ return isErrorSource ? chalk.bgRed(result) : result;
+ })
+ .join('\n');
+};
diff --git a/node_modules/ava/lib/colors.js b/node_modules/ava/lib/colors.js
new file mode 100644
index 000000000..74be14bb1
--- /dev/null
+++ b/node_modules/ava/lib/colors.js
@@ -0,0 +1,15 @@
+'use strict';
+const chalk = require('chalk');
+
+module.exports = {
+ title: chalk.bold.white,
+ error: chalk.red,
+ skip: chalk.yellow,
+ todo: chalk.blue,
+ pass: chalk.green,
+ duration: chalk.gray.dim,
+ errorSource: chalk.gray,
+ errorStack: chalk.gray,
+ stack: chalk.red,
+ information: chalk.magenta
+};
diff --git a/node_modules/ava/lib/concurrent.js b/node_modules/ava/lib/concurrent.js
new file mode 100644
index 000000000..3cdbb41c3
--- /dev/null
+++ b/node_modules/ava/lib/concurrent.js
@@ -0,0 +1,64 @@
+'use strict';
+
+class Concurrent {
+ constructor(runnables, bail) {
+ if (!Array.isArray(runnables)) {
+ throw new TypeError('Expected an array of runnables');
+ }
+
+ this.runnables = runnables;
+ this.bail = bail || false;
+ }
+
+ run() {
+ let allPassed = true;
+
+ let pending;
+ let rejectPending;
+ let resolvePending;
+ const allPromises = [];
+ const handlePromise = promise => {
+ if (!pending) {
+ pending = new Promise((resolve, reject) => {
+ rejectPending = reject;
+ resolvePending = resolve;
+ });
+ }
+
+ allPromises.push(promise.then(passed => {
+ if (!passed) {
+ allPassed = false;
+
+ if (this.bail) {
+ // Stop if the test failed and bail mode is on.
+ resolvePending();
+ }
+ }
+ }, rejectPending));
+ };
+
+ for (const runnable of this.runnables) {
+ const passedOrPromise = runnable.run();
+
+ if (!passedOrPromise) {
+ if (this.bail) {
+ // Stop if the test failed and bail mode is on.
+ return false;
+ }
+
+ allPassed = false;
+ } else if (passedOrPromise !== true) {
+ handlePromise(passedOrPromise);
+ }
+ }
+
+ if (pending) {
+ Promise.all(allPromises).then(resolvePending);
+ return pending.then(() => allPassed);
+ }
+
+ return allPassed;
+ }
+}
+
+module.exports = Concurrent;
diff --git a/node_modules/ava/lib/enhance-assert.js b/node_modules/ava/lib/enhance-assert.js
new file mode 100644
index 000000000..7808765b7
--- /dev/null
+++ b/node_modules/ava/lib/enhance-assert.js
@@ -0,0 +1,63 @@
+'use strict';
+const dotProp = require('dot-prop');
+const formatValue = require('./format-assert-error').formatValue;
+
+// When adding patterns, don't forget to add to
+// https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json
+// Then release a new version of that preset and bump the SemVer range here.
+const PATTERNS = [
+ 't.truthy(value, [message])',
+ 't.falsy(value, [message])',
+ 't.true(value, [message])',
+ 't.false(value, [message])',
+ 't.regex(contents, regex, [message])',
+ 't.notRegex(contents, regex, [message])'
+];
+
+const isRangeMatch = (a, b) => {
+ return (a[0] === b[0] && a[1] === b[1]) ||
+ (a[0] > b[0] && a[0] < b[1]) ||
+ (a[1] > b[0] && a[1] < b[1]);
+};
+
+const computeStatement = (tokens, range) => {
+ return tokens
+ .filter(token => isRangeMatch(token.range, range))
+ .map(token => token.value === undefined ? token.type.label : token.value)
+ .join('');
+};
+
+const getNode = (ast, path) => dotProp.get(ast, path.replace(/\//g, '.'));
+
+const formatter = context => {
+ const ast = JSON.parse(context.source.ast);
+ const tokens = JSON.parse(context.source.tokens);
+ const args = context.args[0].events;
+
+ return args
+ .map(arg => {
+ const range = getNode(ast, arg.espath).range;
+ return [computeStatement(tokens, range), formatValue(arg.value, {maxDepth: 1})];
+ })
+ .reverse();
+};
+
+const enhanceAssert = (pass, fail, assertions) => {
+ const empower = require('empower-core');
+ return empower(assertions, {
+ destructive: true,
+ onError(event) {
+ const error = event.error;
+ if (event.powerAssertContext) { // Context may be missing in internal tests.
+ error.statements = formatter(event.powerAssertContext);
+ }
+ fail(this, error);
+ },
+ onSuccess() {
+ pass(this);
+ },
+ patterns: PATTERNS,
+ bindReceiver: false
+ });
+};
+module.exports = enhanceAssert;
diff --git a/node_modules/ava/lib/extract-stack.js b/node_modules/ava/lib/extract-stack.js
new file mode 100644
index 000000000..64f63db1c
--- /dev/null
+++ b/node_modules/ava/lib/extract-stack.js
@@ -0,0 +1,10 @@
+'use strict';
+const stackLineRegex = /^.+ \(.+:[0-9]+:[0-9]+\)$/;
+
+module.exports = stack => {
+ return stack
+ .split('\n')
+ .filter(line => stackLineRegex.test(line))
+ .map(line => line.trim())
+ .join('\n');
+};
diff --git a/node_modules/ava/lib/fork.js b/node_modules/ava/lib/fork.js
new file mode 100644
index 000000000..bf918d391
--- /dev/null
+++ b/node_modules/ava/lib/fork.js
@@ -0,0 +1,178 @@
+'use strict';
+const childProcess = require('child_process');
+const path = require('path');
+const fs = require('fs');
+const Promise = require('bluebird');
+const debug = require('debug')('ava');
+const AvaError = require('./ava-error');
+
+if (fs.realpathSync(__filename) !== __filename) {
+ console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
+}
+
+let env = process.env;
+
+// Ensure NODE_PATH paths are absolute
+if (env.NODE_PATH) {
+ env = Object.assign({}, env);
+
+ env.NODE_PATH = env.NODE_PATH
+ .split(path.delimiter)
+ .map(x => path.resolve(x))
+ .join(path.delimiter);
+}
+
+// In case the test file imports a different AVA install,
+// the presence of this variable allows it to require this one instead
+env.AVA_PATH = path.resolve(__dirname, '..');
+
+module.exports = (file, opts, execArgv) => {
+ opts = Object.assign({
+ file,
+ baseDir: process.cwd(),
+ tty: process.stdout.isTTY ? {
+ columns: process.stdout.columns,
+ rows: process.stdout.rows
+ } : false
+ }, opts);
+
+ const args = [JSON.stringify(opts), opts.color ? '--color' : '--no-color'];
+
+ const ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), args, {
+ cwd: opts.projectDir,
+ silent: true,
+ env,
+ execArgv: execArgv || process.execArgv
+ });
+
+ const relFile = path.relative('.', file);
+
+ let exiting = false;
+ const send = (name, data) => {
+ if (!exiting) {
+ // This seems to trigger a Node bug which kills the AVA master process, at
+ // least while running AVA's tests. See
+ // <https://github.com/novemberborn/_ava-tap-crash> for more details.
+ ps.send({
+ name: `ava-${name}`,
+ data,
+ ava: true
+ });
+ }
+ };
+
+ const testResults = [];
+ let results;
+
+ const promise = new Promise((resolve, reject) => {
+ ps.on('error', reject);
+
+ // Emit `test` and `stats` events
+ ps.on('message', event => {
+ if (!event.ava) {
+ return;
+ }
+
+ event.name = event.name.replace(/^ava-/, '');
+ event.data.file = relFile;
+
+ debug('ipc %s:\n%o', event.name, event.data);
+
+ ps.emit(event.name, event.data);
+ });
+
+ ps.on('test', props => {
+ testResults.push(props);
+ });
+
+ ps.on('results', data => {
+ results = data;
+ data.tests = testResults;
+ send('teardown');
+ });
+
+ ps.on('exit', (code, signal) => {
+ if (code > 0) {
+ return reject(new AvaError(`${relFile} exited with a non-zero exit code: ${code}`));
+ }
+
+ if (code === null && signal) {
+ return reject(new AvaError(`${relFile} exited due to ${signal}`));
+ }
+
+ if (results) {
+ resolve(results);
+ } else {
+ reject(new AvaError(`Test results were not received from ${relFile}`));
+ }
+ });
+
+ ps.on('no-tests', data => {
+ send('teardown');
+
+ let message = `No tests found in ${relFile}`;
+
+ if (!data.avaRequired) {
+ message += ', make sure to import "ava" at the top of your test file';
+ }
+
+ reject(new AvaError(message));
+ });
+ });
+
+ // Teardown finished, now exit
+ ps.on('teardown', () => {
+ send('exit');
+ exiting = true;
+ });
+
+ // Uncaught exception in fork, need to exit
+ ps.on('uncaughtException', () => {
+ send('teardown');
+ });
+
+ ps.stdout.on('data', data => {
+ ps.emit('stdout', data);
+ });
+
+ ps.stderr.on('data', data => {
+ ps.emit('stderr', data);
+ });
+
+ promise.on = function () {
+ ps.on.apply(ps, arguments);
+ return promise;
+ };
+
+ promise.send = (name, data) => {
+ send(name, data);
+ return promise;
+ };
+
+ promise.exit = () => {
+ send('init-exit');
+ return promise;
+ };
+
+ // Send 'run' event only when fork is listening for it
+ let isReady = false;
+
+ ps.on('stats', () => {
+ isReady = true;
+ });
+
+ promise.run = options => {
+ if (isReady) {
+ send('run', options);
+ return promise;
+ }
+
+ ps.on('stats', () => {
+ send('run', options);
+ });
+
+ return promise;
+ };
+
+ return promise;
+};
diff --git a/node_modules/ava/lib/format-assert-error.js b/node_modules/ava/lib/format-assert-error.js
new file mode 100644
index 000000000..a899af463
--- /dev/null
+++ b/node_modules/ava/lib/format-assert-error.js
@@ -0,0 +1,121 @@
+'use strict';
+const prettyFormat = require('@ava/pretty-format');
+const reactTestPlugin = require('@ava/pretty-format/plugins/ReactTestComponent');
+const chalk = require('chalk');
+const diff = require('diff');
+const DiffMatchPatch = require('diff-match-patch');
+const indentString = require('indent-string');
+const globals = require('./globals');
+
+function formatValue(value, options) {
+ return prettyFormat(value, Object.assign({
+ callToJSON: false,
+ plugins: [reactTestPlugin],
+ highlight: globals.options.color !== false
+ }, options));
+}
+exports.formatValue = formatValue;
+
+const cleanUp = line => {
+ if (line[0] === '+') {
+ return `${chalk.green('+')} ${line.slice(1)}`;
+ }
+
+ if (line[0] === '-') {
+ return `${chalk.red('-')} ${line.slice(1)}`;
+ }
+
+ if (line.match(/@@/)) {
+ return null;
+ }
+
+ if (line.match(/\\ No newline/)) {
+ return null;
+ }
+
+ return ` ${line}`;
+};
+
+const getType = value => {
+ const type = typeof value;
+ if (type === 'object') {
+ if (type === null) {
+ return 'null';
+ }
+ if (Array.isArray(value)) {
+ return 'array';
+ }
+ }
+ return type;
+};
+
+function formatDiff(actual, expected) {
+ const actualType = getType(actual);
+ const expectedType = getType(expected);
+ if (actualType !== expectedType) {
+ return null;
+ }
+
+ if (actualType === 'array' || actualType === 'object') {
+ const formatted = diff.createPatch('string', formatValue(actual), formatValue(expected))
+ .split('\n')
+ .slice(4)
+ .map(cleanUp)
+ .filter(Boolean)
+ .join('\n')
+ .trimRight();
+
+ return {label: 'Difference:', formatted};
+ }
+
+ if (actualType === 'string') {
+ const formatted = new DiffMatchPatch()
+ .diff_main(formatValue(actual, {highlight: false}), formatValue(expected, {highlight: false}))
+ .map(part => {
+ if (part[0] === 1) {
+ return chalk.bgGreen.black(part[1]);
+ }
+
+ if (part[0] === -1) {
+ return chalk.bgRed.black(part[1]);
+ }
+
+ return chalk.red(part[1]);
+ })
+ .join('')
+ .trimRight();
+
+ return {label: 'Difference:', formatted};
+ }
+
+ return null;
+}
+exports.formatDiff = formatDiff;
+
+function formatWithLabel(label, value) {
+ return {label, formatted: formatValue(value)};
+}
+exports.formatWithLabel = formatWithLabel;
+
+function formatSerializedError(error) {
+ if (error.statements.length === 0 && error.values.length === 0) {
+ return null;
+ }
+
+ let result = error.values
+ .map(value => `${value.label}\n\n${indentString(value.formatted, 2).trimRight()}\n`)
+ .join('\n');
+
+ if (error.statements.length > 0) {
+ if (error.values.length > 0) {
+ result += '\n';
+ }
+
+ result += error.statements
+ .map(statement => `${statement[0]}\n${chalk.grey('=>')} ${statement[1]}\n`)
+ .join('\n');
+ }
+
+ return result;
+}
+exports.formatSerializedError = formatSerializedError;
diff --git a/node_modules/ava/lib/globals.js b/node_modules/ava/lib/globals.js
new file mode 100644
index 000000000..51176c113
--- /dev/null
+++ b/node_modules/ava/lib/globals.js
@@ -0,0 +1,10 @@
+'use strict';
+
+// Global objects / functions to be bound before requiring test file, so tests do not interfere
+
+const x = module.exports;
+x.now = Date.now;
+x.setTimeout = setTimeout;
+x.clearTimeout = clearTimeout;
+x.setImmediate = setImmediate;
+x.options = {};
diff --git a/node_modules/ava/lib/logger.js b/node_modules/ava/lib/logger.js
new file mode 100644
index 000000000..54bd23c94
--- /dev/null
+++ b/node_modules/ava/lib/logger.js
@@ -0,0 +1,81 @@
+'use strict';
+const autoBind = require('auto-bind');
+
+class Logger {
+ constructor(reporter) {
+ this.reporter = reporter;
+ autoBind(this);
+ }
+ start(runStatus) {
+ if (!this.reporter.start) {
+ return;
+ }
+
+ this.write(this.reporter.start(runStatus), runStatus);
+ }
+ reset(runStatus) {
+ if (!this.reporter.reset) {
+ return;
+ }
+
+ this.write(this.reporter.reset(runStatus), runStatus);
+ }
+ test(test, runStatus) {
+ this.write(this.reporter.test(test, runStatus), runStatus);
+ }
+ unhandledError(err, runStatus) {
+ if (!this.reporter.unhandledError) {
+ return;
+ }
+
+ this.write(this.reporter.unhandledError(err, runStatus), runStatus);
+ }
+ finish(runStatus) {
+ if (!this.reporter.finish) {
+ return;
+ }
+
+ this.write(this.reporter.finish(runStatus), runStatus);
+ }
+ section() {
+ if (!this.reporter.section) {
+ return;
+ }
+
+ this.write(this.reporter.section());
+ }
+ clear() {
+ if (!this.reporter.clear) {
+ return false;
+ }
+
+ this.write(this.reporter.clear());
+ return true;
+ }
+ write(str, runStatus) {
+ if (typeof str === 'undefined') {
+ return;
+ }
+
+ this.reporter.write(str, runStatus);
+ }
+ stdout(data, runStatus) {
+ if (!this.reporter.stdout) {
+ return;
+ }
+
+ this.reporter.stdout(data, runStatus);
+ }
+ stderr(data, runStatus) {
+ if (!this.reporter.stderr) {
+ return;
+ }
+
+ this.reporter.stderr(data, runStatus);
+ }
+ exit(code) {
+ process.exit(code); // eslint-disable-line unicorn/no-process-exit
+ }
+}
+
+module.exports = Logger;
diff --git a/node_modules/ava/lib/main.js b/node_modules/ava/lib/main.js
new file mode 100644
index 000000000..52618e8b7
--- /dev/null
+++ b/node_modules/ava/lib/main.js
@@ -0,0 +1,103 @@
+'use strict';
+const worker = require('./test-worker');
+const adapter = require('./process-adapter');
+const serializeError = require('./serialize-error');
+const globals = require('./globals');
+const Runner = require('./runner');
+
+const opts = globals.options;
+const runner = new Runner({
+ bail: opts.failFast,
+ failWithoutAssertions: opts.failWithoutAssertions,
+ file: opts.file,
+ match: opts.match,
+ serial: opts.serial,
+ updateSnapshots: opts.updateSnapshots
+});
+
+worker.setRunner(runner);
+
+// If fail-fast is enabled, use this variable to detect
+// that no more tests should be logged
+let isFailed = false;
+
+Error.stackTraceLimit = Infinity;
+
+function test(props) {
+ if (isFailed) {
+ return;
+ }
+
+ const hasError = typeof props.error !== 'undefined';
+
+ // Don't display anything if it's a passed hook
+ if (!hasError && props.type !== 'test') {
+ return;
+ }
+
+ if (hasError) {
+ props.error = serializeError(props.error);
+ } else {
+ props.error = null;
+ }
+
+ adapter.send('test', props);
+
+ if (hasError && opts.failFast) {
+ isFailed = true;
+ exit();
+ }
+}
+
+function exit() {
+ // Reference the IPC channel now that tests have finished running.
+ adapter.ipcChannel.ref();
+
+ const stats = runner.buildStats();
+ adapter.send('results', {stats});
+}
+
+globals.setImmediate(() => {
+ const hasExclusive = runner.tests.hasExclusive;
+ const numberOfTests = runner.tests.testCount;
+
+ if (numberOfTests === 0) {
+ adapter.send('no-tests', {avaRequired: true});
+ return;
+ }
+
+ adapter.send('stats', {
+ testCount: numberOfTests,
+ hasExclusive
+ });
+
+ runner.on('test', test);
+
+ process.on('ava-run', options => {
+ // Unreference the IPC channel. This stops it from keeping the event loop
+ // busy, which means the `beforeExit` event can be used to detect when tests
+ // stall.
+ adapter.ipcChannel.unref();
+
+ runner.run(options)
+ .then(() => {
+ runner.saveSnapshotState();
+
+ return exit();
+ })
+ .catch(err => {
+ process.emit('uncaughtException', err);
+ });
+ });
+
+ process.on('ava-init-exit', () => {
+ exit();
+ });
+});
+
+module.exports = runner.chain;
+
+// TypeScript imports the `default` property for
+// an ES2015 default import (`import test from 'ava'`)
+// See: https://github.com/Microsoft/TypeScript/issues/2242#issuecomment-83694181
+module.exports.default = runner.chain;
diff --git a/node_modules/ava/lib/prefix-title.js b/node_modules/ava/lib/prefix-title.js
new file mode 100644
index 000000000..a1c7b4f3b
--- /dev/null
+++ b/node_modules/ava/lib/prefix-title.js
@@ -0,0 +1,21 @@
+'use strict';
+const path = require('path');
+
+module.exports = (file, base, separator) => {
+ let prefix = file
+ // Only replace this.base if it is found at the start of the path
+ .replace(base, (match, offset) => offset === 0 ? '' : match)
+ .replace(/\.spec/, '')
+ .replace(/\.test/, '')
+ .replace(/test-/g, '')
+ .replace(/\.js$/, '')
+ .split(path.sep)
+ .filter(p => p !== '__tests__')
+ .join(separator);
+
+ if (prefix.length > 0) {
+ prefix += separator;
+ }
+
+ return prefix;
+};
diff --git a/node_modules/ava/lib/process-adapter.js b/node_modules/ava/lib/process-adapter.js
new file mode 100644
index 000000000..b50f37398
--- /dev/null
+++ b/node_modules/ava/lib/process-adapter.js
@@ -0,0 +1,103 @@
+'use strict';
+const fs = require('fs');
+const path = require('path');
+const debug = require('debug')('ava');
+const sourceMapSupport = require('source-map-support');
+const installPrecompiler = require('require-precompiled');
+
+// Parse and re-emit AVA messages
+process.on('message', message => {
+ if (!message.ava) {
+ return;
+ }
+
+ process.emit(message.name, message.data);
+});
+
+exports.send = (name, data) => {
+ process.send({
+ name: `ava-${name}`,
+ data,
+ ava: true
+ });
+};
+
+// `process.channel` was added in Node.js 7.1.0, but the channel was available
+// through an undocumented API as `process._channel`.
+exports.ipcChannel = process.channel || process._channel;
+
+const opts = JSON.parse(process.argv[2]);
+exports.opts = opts;
+
+// Fake TTY support
+if (opts.tty) {
+ process.stdout.isTTY = true;
+ process.stdout.columns = opts.tty.columns || 80;
+ process.stdout.rows = opts.tty.rows;
+
+ const tty = require('tty');
+ const isatty = tty.isatty;
+
+ tty.isatty = function (fd) {
+ if (fd === 1 || fd === process.stdout) {
+ return true;
+ }
+
+ return isatty(fd);
+ };
+}
+
+if (debug.enabled) {
+ // Forward the `time-require` `--sorted` flag.
+ // Intended for internal optimization tests only.
+ if (opts._sorted) {
+ process.argv.push('--sorted');
+ }
+
+ require('time-require'); // eslint-disable-line import/no-unassigned-import
+}
+
+const sourceMapCache = new Map();
+const cacheDir = opts.cacheDir;
+
+exports.installSourceMapSupport = () => {
+ sourceMapSupport.install({
+ environment: 'node',
+ handleUncaughtExceptions: false,
+ retrieveSourceMap(source) {
+ if (sourceMapCache.has(source)) {
+ return {
+ url: source,
+ map: fs.readFileSync(sourceMapCache.get(source), 'utf8')
+ };
+ }
+ }
+ });
+};
+
+exports.installPrecompilerHook = () => {
+ installPrecompiler(filename => {
+ const precompiled = opts.precompiled[filename];
+
+ if (precompiled) {
+ sourceMapCache.set(filename, path.join(cacheDir, `${precompiled}.js.map`));
+ return fs.readFileSync(path.join(cacheDir, `${precompiled}.js`), 'utf8');
+ }
+
+ return null;
+ });
+};
+
+exports.installDependencyTracking = (dependencies, testPath) => {
+ Object.keys(require.extensions).forEach(ext => {
+ const wrappedHandler = require.extensions[ext];
+
+ require.extensions[ext] = (module, filename) => {
+ if (filename !== testPath) {
+ dependencies.push(filename);
+ }
+
+ wrappedHandler(module, filename);
+ };
+ });
+};
diff --git a/node_modules/ava/lib/reporters/improper-usage-messages.js b/node_modules/ava/lib/reporters/improper-usage-messages.js
new file mode 100644
index 000000000..0a2626638
--- /dev/null
+++ b/node_modules/ava/lib/reporters/improper-usage-messages.js
@@ -0,0 +1,21 @@
+'use strict';
+const chalk = require('chalk');
+
+exports.forError = error => {
+ if (!error.improperUsage) {
+ return null;
+ }
+
+ const assertion = error.assertion;
+ if (assertion !== 'throws' || !assertion === 'notThrows') {
+ return null;
+ }
+
+ return `Try wrapping the first argument to \`t.${assertion}()\` in a function:
+
+ ${chalk.cyan(`t.${assertion}(() => { `)}${chalk.grey('/* your code here */')}${chalk.cyan(' })')}
+
+Visit the following URL for more details:
+
+ ${chalk.blue.underline('https://github.com/avajs/ava#throwsfunctionpromise-error-message')}`;
+};
diff --git a/node_modules/ava/lib/reporters/mini.js b/node_modules/ava/lib/reporters/mini.js
new file mode 100644
index 000000000..df481a76a
--- /dev/null
+++ b/node_modules/ava/lib/reporters/mini.js
@@ -0,0 +1,318 @@
+'use strict';
+const StringDecoder = require('string_decoder').StringDecoder;
+const cliCursor = require('cli-cursor');
+const lastLineTracker = require('last-line-stream/tracker');
+const plur = require('plur');
+const spinners = require('cli-spinners');
+const chalk = require('chalk');
+const cliTruncate = require('cli-truncate');
+const cross = require('figures').cross;
+const indentString = require('indent-string');
+const formatAssertError = require('../format-assert-error');
+const extractStack = require('../extract-stack');
+const codeExcerpt = require('../code-excerpt');
+const colors = require('../colors');
+const improperUsageMessages = require('./improper-usage-messages');
+
+// TODO(@jamestalamge): This should be fixed in log-update and ansi-escapes once we are confident it's a good solution.
+const CSI = '\u001B[';
+const ERASE_LINE = CSI + '2K';
+const CURSOR_TO_COLUMN_0 = CSI + '0G';
+const CURSOR_UP = CSI + '1A';
+
+// Returns a string that will erase `count` lines from the end of the terminal.
+function eraseLines(count) {
+ let clear = '';
+
+ for (let i = 0; i < count; i++) {
+ clear += ERASE_LINE + (i < count - 1 ? CURSOR_UP : '');
+ }
+
+ if (count) {
+ clear += CURSOR_TO_COLUMN_0;
+ }
+
+ return clear;
+}
+
+class MiniReporter {
+ constructor(options) {
+ this.options = Object.assign({}, options);
+
+ chalk.enabled = this.options.color;
+ for (const key of Object.keys(colors)) {
+ colors[key].enabled = this.options.color;
+ }
+
+ const spinnerDef = spinners[process.platform === 'win32' ? 'line' : 'dots'];
+ this.spinnerFrames = spinnerDef.frames.map(c => chalk.gray.dim(c));
+ this.spinnerInterval = spinnerDef.interval;
+
+ this.reset();
+ this.stream = process.stderr;
+ this.stringDecoder = new StringDecoder();
+ }
+ start() {
+ this.interval = setInterval(() => {
+ this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
+ this.write(this.prefix());
+ }, this.spinnerInterval);
+
+ return this.prefix('');
+ }
+ reset() {
+ this.clearInterval();
+ this.passCount = 0;
+ this.knownFailureCount = 0;
+ this.failCount = 0;
+ this.skipCount = 0;
+ this.todoCount = 0;
+ this.rejectionCount = 0;
+ this.exceptionCount = 0;
+ this.currentStatus = '';
+ this.currentTest = '';
+ this.statusLineCount = 0;
+ this.spinnerIndex = 0;
+ this.lastLineTracker = lastLineTracker();
+ }
+ spinnerChar() {
+ return this.spinnerFrames[this.spinnerIndex];
+ }
+ clearInterval() {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ test(test) {
+ if (test.todo) {
+ this.todoCount++;
+ } else if (test.skip) {
+ this.skipCount++;
+ } else if (test.error) {
+ this.failCount++;
+ } else {
+ this.passCount++;
+ if (test.failing) {
+ this.knownFailureCount++;
+ }
+ }
+
+ if (test.todo || test.skip) {
+ return;
+ }
+
+ return this.prefix(this._test(test));
+ }
+ prefix(str) {
+ str = str || this.currentTest;
+ this.currentTest = str;
+
+ // The space before the newline is required for proper formatting
+ // TODO(jamestalmage): Figure out why it's needed and document it here
+ return ` \n ${this.spinnerChar()} ${str}`;
+ }
+ _test(test) {
+ const SPINNER_WIDTH = 3;
+ const PADDING = 1;
+ let title = cliTruncate(test.title, process.stdout.columns - SPINNER_WIDTH - PADDING);
+
+ if (test.error || test.failing) {
+ title = colors.error(test.title);
+ }
+
+ return title + '\n' + this.reportCounts();
+ }
+ unhandledError(err) {
+ if (err.type === 'exception') {
+ this.exceptionCount++;
+ } else {
+ this.rejectionCount++;
+ }
+ }
+ reportCounts(time) {
+ const lines = [
+ this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '',
+ this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '',
+ this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '',
+ this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '',
+ this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : ''
+ ].filter(Boolean);
+
+ if (time && lines.length > 0) {
+ lines[0] += ' ' + time;
+ }
+
+ return lines.join('');
+ }
+ finish(runStatus) {
+ this.clearInterval();
+ let time;
+
+ if (this.options.watching) {
+ time = chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']');
+ }
+
+ let status = this.reportCounts(time);
+
+ if (this.rejectionCount > 0) {
+ status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount));
+ }
+
+ if (this.exceptionCount > 0) {
+ status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount));
+ }
+
+ if (runStatus.previousFailCount > 0) {
+ status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun');
+ }
+
+ if (this.knownFailureCount > 0) {
+ for (const test of runStatus.knownFailures) {
+ const title = test.title;
+ status += '\n\n ' + colors.title(title);
+ // TODO: Output description with link
+ // status += colors.stack(description);
+ }
+ }
+
+ if (this.failCount > 0) {
+ runStatus.errors.forEach((test, index) => {
+ if (!test.error) {
+ return;
+ }
+
+ const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n';
+
+ status += beforeSpacing + ' ' + colors.title(test.title) + '\n';
+ if (test.error.source) {
+ status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n';
+
+ const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns});
+ if (excerpt) {
+ status += '\n' + indentString(excerpt, 2) + '\n';
+ }
+ }
+
+ if (test.error.message) {
+ status += '\n' + indentString(test.error.message, 2) + '\n';
+ }
+
+ if (test.error.avaAssertionError) {
+ const formatted = formatAssertError.formatSerializedError(test.error);
+ if (formatted) {
+ status += '\n' + indentString(formatted, 2);
+ }
+
+ const message = improperUsageMessages.forError(test.error);
+ if (message) {
+ status += '\n' + indentString(message, 2) + '\n';
+ }
+ }
+
+ if (test.error.stack) {
+ const extracted = extractStack(test.error.stack);
+ if (extracted.includes('\n')) {
+ status += '\n' + indentString(colors.errorStack(extracted), 2);
+ }
+ }
+ });
+ }
+
+ if (this.rejectionCount > 0 || this.exceptionCount > 0) {
+ // TODO(sindresorhus): Figure out why this causes a test failure when switched to a for-of loop
+ runStatus.errors.forEach(err => {
+ if (err.title) {
+ return;
+ }
+
+ if (err.type === 'exception' && err.name === 'AvaError') {
+ status += '\n\n ' + colors.error(cross + ' ' + err.message);
+ } else {
+ const title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception';
+ let description = err.stack ? err.stack.trimRight() : JSON.stringify(err);
+ description = description.split('\n');
+ const errorTitle = err.name ? description[0] : 'Threw non-error: ' + description[0];
+ const errorStack = description.slice(1).join('\n');
+
+ status += '\n\n ' + colors.title(title) + '\n';
+ status += ' ' + colors.stack(errorTitle) + '\n';
+ status += colors.errorStack(errorStack);
+ }
+ });
+ }
+
+ if (runStatus.failFastEnabled === true && runStatus.remainingCount > 0 && runStatus.failCount > 0) {
+ const remaining = 'At least ' + runStatus.remainingCount + ' ' + plur('test was', 'tests were', runStatus.remainingCount) + ' skipped.';
+ status += '\n\n ' + colors.information('`--fail-fast` is on. ' + remaining);
+ }
+
+ if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) {
+ status += '\n\n ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run');
+ }
+
+ return status + '\n\n';
+ }
+ section() {
+ return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80));
+ }
+ clear() {
+ return '';
+ }
+ write(str) {
+ cliCursor.hide();
+ this.currentStatus = str;
+ this._update();
+ this.statusLineCount = this.currentStatus.split('\n').length;
+ }
+ stdout(data) {
+ this._update(data);
+ }
+ stderr(data) {
+ this._update(data);
+ }
+ _update(data) {
+ let str = '';
+ let ct = this.statusLineCount;
+ const columns = process.stdout.columns;
+ let lastLine = this.lastLineTracker.lastLine();
+
+ // Terminals automatically wrap text. We only need the last log line as seen on the screen.
+ lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns));
+
+ // Don't delete the last log line if it's completely empty.
+ if (lastLine.length > 0) {
+ ct++;
+ }
+
+ // Erase the existing status message, plus the last log line.
+ str += eraseLines(ct);
+
+ // Rewrite the last log line.
+ str += lastLine;
+
+ if (str.length > 0) {
+ this.stream.write(str);
+ }
+
+ if (data) {
+ // Send new log data to the terminal, and update the last line status.
+ this.lastLineTracker.update(this.stringDecoder.write(data));
+ this.stream.write(data);
+ }
+
+ let currentStatus = this.currentStatus;
+
+ if (currentStatus.length > 0) {
+ lastLine = this.lastLineTracker.lastLine();
+ // We need a newline at the end of the last log line, before the status message.
+ // However, if the last log line is the exact width of the terminal a newline is implied,
+ // and adding a second will cause problems.
+ if (lastLine.length % columns) {
+ currentStatus = '\n' + currentStatus;
+ }
+ // Rewrite the status message.
+ this.stream.write(currentStatus);
+ }
+ }
+}
+
+module.exports = MiniReporter;
diff --git a/node_modules/ava/lib/reporters/tap.js b/node_modules/ava/lib/reporters/tap.js
new file mode 100644
index 000000000..37c2cfd95
--- /dev/null
+++ b/node_modules/ava/lib/reporters/tap.js
@@ -0,0 +1,119 @@
+'use strict';
+const format = require('util').format;
+const indentString = require('indent-string');
+const stripAnsi = require('strip-ansi');
+const yaml = require('js-yaml');
+const extractStack = require('../extract-stack');
+
+// Parses stack trace and extracts original function name, file name and line
+function getSourceFromStack(stack) {
+ return extractStack(stack).split('\n')[0];
+}
+
+function dumpError(error, includeMessage) {
+ const obj = Object.assign({}, error.object);
+ if (error.name) {
+ obj.name = error.name;
+ }
+ if (includeMessage && error.message) {
+ obj.message = error.message;
+ }
+
+ if (error.avaAssertionError) {
+ if (error.assertion) {
+ obj.assertion = error.assertion;
+ }
+ if (error.operator) {
+ obj.operator = error.operator;
+ }
+ if (error.values.length > 0) {
+ obj.values = error.values.reduce((acc, value) => {
+ acc[value.label] = stripAnsi(value.formatted);
+ return acc;
+ }, {});
+ }
+ }
+
+ if (error.stack) {
+ obj.at = getSourceFromStack(error.stack);
+ }
+
+ return ` ---\n${indentString(yaml.safeDump(obj).trim(), 4)}\n ...`;
+}
+
+class TapReporter {
+ constructor() {
+ this.i = 0;
+ }
+ start() {
+ return 'TAP version 13';
+ }
+ test(test) {
+ let output;
+
+ let directive = '';
+ const passed = test.todo ? 'not ok' : 'ok';
+
+ if (test.todo) {
+ directive = '# TODO';
+ } else if (test.skip) {
+ directive = '# SKIP';
+ }
+
+ const title = stripAnsi(test.title);
+
+ if (test.error) {
+ output = [
+ '# ' + title,
+ format('not ok %d - %s', ++this.i, title),
+ dumpError(test.error, true)
+ ];
+ } else {
+ output = [
+ `# ${title}`,
+ format('%s %d - %s %s', passed, ++this.i, title, directive).trim()
+ ];
+ }
+
+ return output.join('\n');
+ }
+ unhandledError(err) {
+ const output = [
+ `# ${err.message}`,
+ format('not ok %d - %s', ++this.i, err.message)
+ ];
+ // AvaErrors don't have stack traces
+ if (err.type !== 'exception' || err.name !== 'AvaError') {
+ output.push(dumpError(err, false));
+ }
+
+ return output.join('\n');
+ }
+ finish(runStatus) {
+ const output = [
+ '',
+ '1..' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount),
+ '# tests ' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount),
+ '# pass ' + runStatus.passCount
+ ];
+
+ if (runStatus.skipCount > 0) {
+ output.push(`# skip ${runStatus.skipCount}`);
+ }
+
+ output.push('# fail ' + (runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount), '');
+
+ return output.join('\n');
+ }
+ write(str) {
+ console.log(str);
+ }
+ stdout(data) {
+ process.stderr.write(data);
+ }
+ stderr(data) {
+ this.stdout(data);
+ }
+}
+
+module.exports = TapReporter;
diff --git a/node_modules/ava/lib/reporters/verbose.js b/node_modules/ava/lib/reporters/verbose.js
new file mode 100644
index 000000000..1be43ce5e
--- /dev/null
+++ b/node_modules/ava/lib/reporters/verbose.js
@@ -0,0 +1,165 @@
+'use strict';
+const indentString = require('indent-string');
+const prettyMs = require('pretty-ms');
+const figures = require('figures');
+const chalk = require('chalk');
+const plur = require('plur');
+const formatAssertError = require('../format-assert-error');
+const extractStack = require('../extract-stack');
+const codeExcerpt = require('../code-excerpt');
+const colors = require('../colors');
+const improperUsageMessages = require('./improper-usage-messages');
+
+class VerboseReporter {
+ constructor(options) {
+ this.options = Object.assign({}, options);
+
+ chalk.enabled = this.options.color;
+ for (const key of Object.keys(colors)) {
+ colors[key].enabled = this.options.color;
+ }
+ }
+ start() {
+ return '';
+ }
+ test(test, runStatus) {
+ if (test.error) {
+ return ' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message);
+ }
+
+ if (test.todo) {
+ return ' ' + colors.todo('- ' + test.title);
+ } else if (test.skip) {
+ return ' ' + colors.skip('- ' + test.title);
+ }
+
+ if (test.failing) {
+ return ' ' + colors.error(figures.tick) + ' ' + colors.error(test.title);
+ }
+
+ if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') {
+ return undefined;
+ }
+
+ // Display duration only over a threshold
+ const threshold = 100;
+ const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : '';
+
+ return ' ' + colors.pass(figures.tick) + ' ' + test.title + duration;
+ }
+ unhandledError(err) {
+ if (err.type === 'exception' && err.name === 'AvaError') {
+ return colors.error(' ' + figures.cross + ' ' + err.message);
+ }
+
+ const types = {
+ rejection: 'Unhandled Rejection',
+ exception: 'Uncaught Exception'
+ };
+
+ let output = colors.error(types[err.type] + ':', err.file) + '\n';
+
+ if (err.stack) {
+ output += ' ' + colors.stack(err.stack) + '\n';
+ } else {
+ output += ' ' + colors.stack(JSON.stringify(err)) + '\n';
+ }
+
+ output += '\n';
+
+ return output;
+ }
+ finish(runStatus) {
+ let output = '\n';
+
+ const lines = [
+ runStatus.failCount > 0 ?
+ ' ' + colors.error(runStatus.failCount, plur('test', runStatus.failCount), 'failed') :
+ ' ' + colors.pass(runStatus.passCount, plur('test', runStatus.passCount), 'passed'),
+ runStatus.knownFailureCount > 0 ? ' ' + colors.error(runStatus.knownFailureCount, plur('known failure', runStatus.knownFailureCount)) : '',
+ runStatus.skipCount > 0 ? ' ' + colors.skip(runStatus.skipCount, plur('test', runStatus.skipCount), 'skipped') : '',
+ runStatus.todoCount > 0 ? ' ' + colors.todo(runStatus.todoCount, plur('test', runStatus.todoCount), 'todo') : '',
+ runStatus.rejectionCount > 0 ? ' ' + colors.error(runStatus.rejectionCount, 'unhandled', plur('rejection', runStatus.rejectionCount)) : '',
+ runStatus.exceptionCount > 0 ? ' ' + colors.error(runStatus.exceptionCount, 'uncaught', plur('exception', runStatus.exceptionCount)) : '',
+ runStatus.previousFailCount > 0 ? ' ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun') : ''
+ ].filter(Boolean);
+
+ if (lines.length > 0) {
+ lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']');
+ output += lines.join('\n');
+ }
+
+ if (runStatus.knownFailureCount > 0) {
+ runStatus.knownFailures.forEach(test => {
+ output += '\n\n\n ' + colors.error(test.title);
+ });
+ }
+
+ if (runStatus.failCount > 0) {
+ runStatus.tests.forEach((test, index) => {
+ if (!test.error) {
+ return;
+ }
+
+ const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n';
+ output += beforeSpacing + ' ' + colors.title(test.title) + '\n';
+ if (test.error.source) {
+ output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n';
+
+ const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns});
+ if (excerpt) {
+ output += '\n' + indentString(excerpt, 2) + '\n';
+ }
+ }
+
+ if (test.error.message) {
+ output += '\n' + indentString(test.error.message, 2) + '\n';
+ }
+
+ if (test.error.avaAssertionError) {
+ const formatted = formatAssertError.formatSerializedError(test.error);
+ if (formatted) {
+ output += '\n' + indentString(formatted, 2);
+ }
+
+ const message = improperUsageMessages.forError(test.error);
+ if (message) {
+ output += '\n' + indentString(message, 2) + '\n';
+ }
+ }
+
+ if (test.error.stack) {
+ const extracted = extractStack(test.error.stack);
+ if (extracted.includes('\n')) {
+ output += '\n' + indentString(colors.errorStack(extracted), 2);
+ }
+ }
+ });
+ }
+
+ if (runStatus.failFastEnabled === true && runStatus.remainingCount > 0 && runStatus.failCount > 0) {
+ const remaining = 'At least ' + runStatus.remainingCount + ' ' + plur('test was', 'tests were', runStatus.remainingCount) + ' skipped.';
+ output += '\n\n\n ' + colors.information('`--fail-fast` is on. ' + remaining);
+ }
+
+ if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) {
+ output += '\n\n\n ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run');
+ }
+
+ return output + '\n';
+ }
+ section() {
+ return chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80));
+ }
+ write(str) {
+ console.error(str);
+ }
+ stdout(data) {
+ process.stderr.write(data);
+ }
+ stderr(data) {
+ process.stderr.write(data);
+ }
+}
+
+module.exports = VerboseReporter;
diff --git a/node_modules/ava/lib/run-status.js b/node_modules/ava/lib/run-status.js
new file mode 100644
index 000000000..6526f7bdc
--- /dev/null
+++ b/node_modules/ava/lib/run-status.js
@@ -0,0 +1,125 @@
+'use strict';
+const EventEmitter = require('events');
+const chalk = require('chalk');
+const flatten = require('arr-flatten');
+const figures = require('figures');
+const autoBind = require('auto-bind');
+const prefixTitle = require('./prefix-title');
+
+function sum(arr, key) {
+ let result = 0;
+
+ arr.forEach(item => {
+ result += item[key];
+ });
+
+ return result;
+}
+
+class RunStatus extends EventEmitter {
+ constructor(opts) {
+ super();
+
+ opts = opts || {};
+ this.prefixTitles = opts.prefixTitles !== false;
+ this.hasExclusive = Boolean(opts.runOnlyExclusive);
+ this.base = opts.base || '';
+ this.rejectionCount = 0;
+ this.exceptionCount = 0;
+ this.passCount = 0;
+ this.knownFailureCount = 0;
+ this.skipCount = 0;
+ this.todoCount = 0;
+ this.failCount = 0;
+ this.fileCount = 0;
+ this.testCount = 0;
+ this.remainingCount = 0;
+ this.previousFailCount = 0;
+ this.knownFailures = [];
+ this.errors = [];
+ this.stats = [];
+ this.tests = [];
+ this.failFastEnabled = opts.failFast || false;
+
+ autoBind(this);
+ }
+ observeFork(emitter) {
+ emitter
+ .on('teardown', this.handleTeardown)
+ .on('stats', this.handleStats)
+ .on('test', this.handleTest)
+ .on('unhandledRejections', this.handleRejections)
+ .on('uncaughtException', this.handleExceptions)
+ .on('stdout', this.handleOutput.bind(this, 'stdout'))
+ .on('stderr', this.handleOutput.bind(this, 'stderr'));
+ }
+ handleRejections(data) {
+ this.rejectionCount += data.rejections.length;
+
+ data.rejections.forEach(err => {
+ err.type = 'rejection';
+ err.file = data.file;
+ this.emit('error', err, this);
+ this.errors.push(err);
+ });
+ }
+ handleExceptions(data) {
+ this.exceptionCount++;
+ const err = data.exception;
+ err.type = 'exception';
+ err.file = data.file;
+ this.emit('error', err, this);
+ this.errors.push(err);
+ }
+ handleTeardown(data) {
+ this.emit('dependencies', data.file, data.dependencies, this);
+ }
+ handleStats(stats) {
+ this.emit('stats', stats, this);
+
+ if (stats.hasExclusive) {
+ this.hasExclusive = true;
+ }
+
+ this.testCount += stats.testCount;
+ }
+ handleTest(test) {
+ test.title = this.prefixTitle(test.file) + test.title;
+
+ if (test.error) {
+ this.errors.push(test);
+ }
+
+ if (test.failing && !test.error) {
+ this.knownFailures.push(test);
+ }
+
+ this.emit('test', test, this);
+ }
+ prefixTitle(file) {
+ if (!this.prefixTitles) {
+ return '';
+ }
+
+ const separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';
+
+ return prefixTitle(file, this.base, separator);
+ }
+ handleOutput(channel, data) {
+ this.emit(channel, data, this);
+ }
+ processResults(results) {
+ // Assemble stats from all tests
+ this.stats = results.map(result => result.stats);
+ this.tests = results.map(result => result.tests);
+ this.tests = flatten(this.tests);
+ this.passCount = sum(this.stats, 'passCount');
+ this.knownFailureCount = sum(this.stats, 'knownFailureCount');
+ this.skipCount = sum(this.stats, 'skipCount');
+ this.todoCount = sum(this.stats, 'todoCount');
+ this.failCount = sum(this.stats, 'failCount');
+ this.remainingCount = this.testCount - this.passCount - this.failCount - this.skipCount - this.todoCount - this.knownFailureCount;
+ }
+}
+
+module.exports = RunStatus;
diff --git a/node_modules/ava/lib/runner.js b/node_modules/ava/lib/runner.js
new file mode 100644
index 000000000..5f0edacb2
--- /dev/null
+++ b/node_modules/ava/lib/runner.js
@@ -0,0 +1,221 @@
+'use strict';
+const EventEmitter = require('events');
+const path = require('path');
+const Bluebird = require('bluebird');
+const jestSnapshot = require('jest-snapshot');
+const optionChain = require('option-chain');
+const matcher = require('matcher');
+const TestCollection = require('./test-collection');
+const validateTest = require('./validate-test');
+
+const chainableMethods = {
+ defaults: {
+ type: 'test',
+ serial: false,
+ exclusive: false,
+ skipped: false,
+ todo: false,
+ failing: false,
+ callback: false,
+ always: false
+ },
+ chainableMethods: {
+ test: {},
+ serial: {serial: true},
+ before: {type: 'before'},
+ after: {type: 'after'},
+ skip: {skipped: true},
+ todo: {todo: true},
+ failing: {failing: true},
+ only: {exclusive: true},
+ beforeEach: {type: 'beforeEach'},
+ afterEach: {type: 'afterEach'},
+ cb: {callback: true},
+ always: {always: true}
+ }
+};
+
+function wrapFunction(fn, args) {
+ return function (t) {
+ return fn.apply(this, [t].concat(args));
+ };
+}
+
+class Runner extends EventEmitter {
+ constructor(options) {
+ super();
+
+ options = options || {};
+
+ this.file = options.file;
+ this.match = options.match || [];
+ this.serial = options.serial;
+ this.updateSnapshots = options.updateSnapshots;
+
+ this.hasStarted = false;
+ this.results = [];
+ this.snapshotState = null;
+ this.tests = new TestCollection({
+ bail: options.bail,
+ failWithoutAssertions: options.failWithoutAssertions,
+ getSnapshotState: () => this.getSnapshotState()
+ });
+
+ this.chain = optionChain(chainableMethods, (opts, args) => {
+ let title;
+ let fn;
+ let macroArgIndex;
+
+ if (this.hasStarted) {
+ throw new Error('All tests and hooks must be declared synchronously in your ' +
+ 'test file, and cannot be nested within other tests or hooks.');
+ }
+
+ if (typeof args[0] === 'string') {
+ title = args[0];
+ fn = args[1];
+ macroArgIndex = 2;
+ } else {
+ fn = args[0];
+ title = null;
+ macroArgIndex = 1;
+ }
+
+ if (this.serial) {
+ opts.serial = true;
+ }
+
+ if (args.length > macroArgIndex) {
+ args = args.slice(macroArgIndex);
+ } else {
+ args = null;
+ }
+
+ if (Array.isArray(fn)) {
+ fn.forEach(fn => {
+ this.addTest(title, opts, fn, args);
+ });
+ } else {
+ this.addTest(title, opts, fn, args);
+ }
+ });
+ }
+
+ addTest(title, metadata, fn, args) {
+ if (args) {
+ if (fn.title) {
+ title = fn.title.apply(fn, [title || ''].concat(args));
+ }
+
+ fn = wrapFunction(fn, args);
+ }
+
+ if (metadata.type === 'test' && this.match.length > 0) {
+ metadata.exclusive = title !== null && matcher([title], this.match).length === 1;
+ }
+
+ const validationError = validateTest(title, fn, metadata);
+ if (validationError !== null) {
+ throw new TypeError(validationError);
+ }
+
+ this.tests.add({
+ metadata,
+ fn,
+ title
+ });
+ }
+
+ addTestResult(result) {
+ const test = result.result;
+ const props = {
+ duration: test.duration,
+ title: test.title,
+ error: result.reason,
+ type: test.metadata.type,
+ skip: test.metadata.skipped,
+ todo: test.metadata.todo,
+ failing: test.metadata.failing
+ };
+
+ this.results.push(result);
+ this.emit('test', props);
+ }
+
+ buildStats() {
+ const stats = {
+ failCount: 0,
+ knownFailureCount: 0,
+ passCount: 0,
+ skipCount: 0,
+ testCount: 0,
+ todoCount: 0
+ };
+
+ for (const result of this.results) {
+ if (!result.passed) {
+ // Includes hooks
+ stats.failCount++;
+ }
+
+ const metadata = result.result.metadata;
+ if (metadata.type === 'test') {
+ stats.testCount++;
+
+ if (metadata.skipped) {
+ stats.skipCount++;
+ } else if (metadata.todo) {
+ stats.todoCount++;
+ } else if (result.passed) {
+ if (metadata.failing) {
+ stats.knownFailureCount++;
+ } else {
+ stats.passCount++;
+ }
+ }
+ }
+ }
+
+ return stats;
+ }
+
+ getSnapshotState() {
+ if (this.snapshotState) {
+ return this.snapshotState;
+ }
+
+ const name = path.basename(this.file) + '.snap';
+ const dir = path.dirname(this.file);
+
+ const snapshotPath = path.join(dir, '__snapshots__', name);
+ const testPath = this.file;
+ const update = this.updateSnapshots;
+
+ const state = jestSnapshot.initializeSnapshotState(testPath, update, snapshotPath);
+ this.snapshotState = state;
+ return state;
+ }
+
+ saveSnapshotState() {
+ if (this.snapshotState) {
+ this.snapshotState.save(this.updateSnapshots);
+ }
+ }
+
+ run(options) {
+ if (options.runOnlyExclusive && !this.tests.hasExclusive) {
+ return Promise.resolve(null);
+ }
+
+ this.hasStarted = true;
+ this.tests.on('test', result => {
+ this.addTestResult(result);
+ });
+ return Bluebird.try(() => this.tests.build().run());
+ }
+ attributeLeakedError(err) {
+ return this.tests.attributeLeakedError(err);
+ }
+}
+
+module.exports = Runner;
diff --git a/node_modules/ava/lib/sequence.js b/node_modules/ava/lib/sequence.js
new file mode 100644
index 000000000..1e5960a98
--- /dev/null
+++ b/node_modules/ava/lib/sequence.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const beforeExitSubscribers = new Set();
+const beforeExitHandler = () => {
+ for (const subscriber of beforeExitSubscribers) {
+ subscriber();
+ }
+};
+const onBeforeExit = subscriber => {
+ if (beforeExitSubscribers.size === 0) {
+ // Only listen for the event once, no matter how many Sequences are run
+ // concurrently.
+ process.on('beforeExit', beforeExitHandler);
+ }
+
+ beforeExitSubscribers.add(subscriber);
+ return {
+ dispose() {
+ beforeExitSubscribers.delete(subscriber);
+ if (beforeExitSubscribers.size === 0) {
+ process.removeListener('beforeExit', beforeExitHandler);
+ }
+ }
+ };
+};
+
+class Sequence {
+ constructor(runnables, bail) {
+ if (!Array.isArray(runnables)) {
+ throw new TypeError('Expected an array of runnables');
+ }
+
+ this.runnables = runnables;
+ this.bail = bail || false;
+ }
+
+ run() {
+ const iterator = this.runnables[Symbol.iterator]();
+
+ let activeRunnable;
+ const beforeExit = onBeforeExit(() => {
+ if (activeRunnable.finishDueToInactivity) {
+ activeRunnable.finishDueToInactivity();
+ }
+ });
+
+ let allPassed = true;
+ const finish = () => {
+ beforeExit.dispose();
+ return allPassed;
+ };
+
+ const runNext = () => {
+ let promise;
+
+ for (let next = iterator.next(); !next.done; next = iterator.next()) {
+ activeRunnable = next.value;
+ const passedOrPromise = activeRunnable.run();
+ if (!passedOrPromise) {
+ allPassed = false;
+
+ if (this.bail) {
+ // Stop if the test failed and bail mode is on.
+ break;
+ }
+ } else if (passedOrPromise !== true) {
+ promise = passedOrPromise;
+ break;
+ }
+ }
+
+ if (!promise) {
+ return finish();
+ }
+
+ return promise.then(passed => {
+ if (!passed) {
+ allPassed = false;
+
+ if (this.bail) {
+ // Stop if the test failed and bail mode is on.
+ return finish();
+ }
+ }
+
+ return runNext();
+ });
+ };
+
+ return runNext();
+ }
+}
+
+module.exports = Sequence;
diff --git a/node_modules/ava/lib/serialize-error.js b/node_modules/ava/lib/serialize-error.js
new file mode 100644
index 000000000..55717e161
--- /dev/null
+++ b/node_modules/ava/lib/serialize-error.js
@@ -0,0 +1,94 @@
+'use strict';
+const path = require('path');
+const cleanYamlObject = require('clean-yaml-object');
+const StackUtils = require('stack-utils');
+const assert = require('./assert');
+const beautifyStack = require('./beautify-stack');
+const extractStack = require('./extract-stack');
+
+function isAvaAssertionError(source) {
+ return source instanceof assert.AssertionError;
+}
+
+function filter(propertyName, isRoot) {
+ return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
+}
+
+const stackUtils = new StackUtils();
+function extractSource(stack) {
+ if (!stack) {
+ return null;
+ }
+
+ const firstStackLine = extractStack(stack).split('\n')[0];
+ return stackUtils.parseLine(firstStackLine);
+}
+function buildSource(source) {
+ if (!source) {
+ return null;
+ }
+
+ // Assume the CWD is the project directory. This holds since this function
+ // is only called in test workers, which are created with their working
+ // directory set to the project directory.
+ const projectDir = process.cwd();
+
+ const file = path.resolve(projectDir, source.file.trim());
+ const rel = path.relative(projectDir, file);
+
+ const isWithinProject = rel.split(path.sep)[0] !== '..';
+ const isDependency = isWithinProject && path.dirname(rel).split(path.sep).indexOf('node_modules') > -1;
+
+ return {
+ isDependency,
+ isWithinProject,
+ file,
+ line: source.line
+ };
+}
+
+module.exports = error => {
+ const stack = typeof error.stack === 'string' ?
+ beautifyStack(error.stack) :
+ null;
+
+ const retval = {
+ avaAssertionError: isAvaAssertionError(error),
+ source: buildSource(extractSource(stack))
+ };
+ if (stack) {
+ retval.stack = stack;
+ }
+
+ if (retval.avaAssertionError) {
+ retval.improperUsage = error.improperUsage;
+ retval.message = error.message;
+ retval.name = error.name;
+ retval.statements = error.statements;
+ retval.values = error.values;
+
+ if (error.fixedSource) {
+ const source = buildSource(error.fixedSource);
+ if (source) {
+ retval.source = source;
+ }
+ }
+
+ if (error.assertion) {
+ retval.assertion = error.assertion;
+ }
+ if (error.operator) {
+ retval.operator = error.operator;
+ }
+ } else {
+ retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties
+ if (typeof error.message === 'string') {
+ retval.message = error.message;
+ }
+ if (typeof error.name === 'string') {
+ retval.name = error.name;
+ }
+ }
+
+ return retval;
+};
diff --git a/node_modules/ava/lib/test-collection.js b/node_modules/ava/lib/test-collection.js
new file mode 100644
index 000000000..5404cb119
--- /dev/null
+++ b/node_modules/ava/lib/test-collection.js
@@ -0,0 +1,206 @@
+'use strict';
+const EventEmitter = require('events');
+const fnName = require('fn-name');
+const Concurrent = require('./concurrent');
+const Sequence = require('./sequence');
+const Test = require('./test');
+
+class TestCollection extends EventEmitter {
+ constructor(options) {
+ super();
+
+ this.bail = options.bail;
+ this.failWithoutAssertions = options.failWithoutAssertions;
+ this.getSnapshotState = options.getSnapshotState;
+ this.hasExclusive = false;
+ this.testCount = 0;
+
+ this.tests = {
+ concurrent: [],
+ serial: []
+ };
+
+ this.hooks = {
+ before: [],
+ beforeEach: [],
+ after: [],
+ afterAlways: [],
+ afterEach: [],
+ afterEachAlways: []
+ };
+
+ this.pendingTestInstances = new Set();
+
+ this._emitTestResult = this._emitTestResult.bind(this);
+ }
+ add(test) {
+ const metadata = test.metadata;
+ const type = metadata.type;
+
+ if (!type) {
+ throw new Error('Test type must be specified');
+ }
+
+ if (!test.title && test.fn) {
+ test.title = fnName(test.fn);
+ }
+
+ // Workaround for Babel giving anonymous functions a name
+ if (test.title === 'callee$0$0') {
+ test.title = null;
+ }
+
+ if (!test.title) {
+ if (type === 'test') {
+ test.title = '[anonymous]';
+ } else {
+ test.title = type;
+ }
+ }
+
+ if (metadata.always && type !== 'after' && type !== 'afterEach') {
+ throw new Error('"always" can only be used with after and afterEach hooks');
+ }
+
+ // Add a hook
+ if (type !== 'test') {
+ if (metadata.exclusive) {
+ throw new Error(`"only" cannot be used with a ${type} hook`);
+ }
+
+ this.hooks[type + (metadata.always ? 'Always' : '')].push(test);
+ return;
+ }
+
+ this.testCount++;
+
+ // Add `.only()` tests if `.only()` was used previously
+ if (this.hasExclusive && !metadata.exclusive) {
+ return;
+ }
+
+ if (metadata.exclusive && !this.hasExclusive) {
+ this.tests.concurrent = [];
+ this.tests.serial = [];
+ this.hasExclusive = true;
+ }
+
+ if (metadata.serial) {
+ this.tests.serial.push(test);
+ } else {
+ this.tests.concurrent.push(test);
+ }
+ }
+ _skippedTest(test) {
+ return {
+ run: () => {
+ this._emitTestResult({
+ passed: true,
+ result: test
+ });
+
+ return true;
+ }
+ };
+ }
+ _emitTestResult(result) {
+ this.pendingTestInstances.delete(result.result);
+ this.emit('test', result);
+ }
+ _buildHooks(hooks, testTitle, context) {
+ return hooks.map(hook => {
+ const test = this._buildHook(hook, testTitle, context);
+
+ if (hook.metadata.skipped || hook.metadata.todo) {
+ return this._skippedTest(test);
+ }
+
+ return test;
+ });
+ }
+ _buildHook(hook, testTitle, contextRef) {
+ let title = hook.title;
+
+ if (testTitle) {
+ title += ` for ${testTitle}`;
+ }
+
+ if (!contextRef) {
+ contextRef = null;
+ }
+
+ const test = new Test({
+ contextRef,
+ failWithoutAssertions: false,
+ fn: hook.fn,
+ getSnapshotState: this.getSnapshotState,
+ metadata: hook.metadata,
+ onResult: this._emitTestResult,
+ title
+ });
+ this.pendingTestInstances.add(test);
+ return test;
+ }
+ _buildTest(test, contextRef) {
+ if (!contextRef) {
+ contextRef = null;
+ }
+
+ test = new Test({
+ contextRef,
+ failWithoutAssertions: this.failWithoutAssertions,
+ fn: test.fn,
+ getSnapshotState: this.getSnapshotState,
+ metadata: test.metadata,
+ onResult: this._emitTestResult,
+ title: test.title
+ });
+ this.pendingTestInstances.add(test);
+ return test;
+ }
+ _buildTestWithHooks(test) {
+ if (test.metadata.skipped || test.metadata.todo) {
+ return new Sequence([this._skippedTest(this._buildTest(test))], true);
+ }
+
+ const context = {context: {}};
+
+ const beforeHooks = this._buildHooks(this.hooks.beforeEach, test.title, context);
+ const afterHooks = this._buildHooks(this.hooks.afterEach, test.title, context);
+
+ let sequence = new Sequence([].concat(beforeHooks, this._buildTest(test, context), afterHooks), true);
+ if (this.hooks.afterEachAlways.length > 0) {
+ const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterEachAlways, test.title, context));
+ sequence = new Sequence([sequence, afterAlwaysHooks], false);
+ }
+ return sequence;
+ }
+ _buildTests(tests) {
+ return tests.map(test => this._buildTestWithHooks(test));
+ }
+ build() {
+ const beforeHooks = new Sequence(this._buildHooks(this.hooks.before));
+ const afterHooks = new Sequence(this._buildHooks(this.hooks.after));
+
+ const serialTests = new Sequence(this._buildTests(this.tests.serial), this.bail);
+ const concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), this.bail);
+ const allTests = new Sequence([serialTests, concurrentTests]);
+
+ let finalTests = new Sequence([beforeHooks, allTests, afterHooks], true);
+ if (this.hooks.afterAlways.length > 0) {
+ const afterAlwaysHooks = new Sequence(this._buildHooks(this.hooks.afterAlways));
+ finalTests = new Sequence([finalTests, afterAlwaysHooks], false);
+ }
+ return finalTests;
+ }
+ attributeLeakedError(err) {
+ for (const test of this.pendingTestInstances) {
+ if (test.attributeLeakedError(err)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+module.exports = TestCollection;
diff --git a/node_modules/ava/lib/test-worker.js b/node_modules/ava/lib/test-worker.js
new file mode 100644
index 000000000..2df7f745d
--- /dev/null
+++ b/node_modules/ava/lib/test-worker.js
@@ -0,0 +1,130 @@
+'use strict';
+
+// Check if the test is being run without AVA cli
+{
+ /* eslint-disable import/order */
+ const path = require('path');
+ const chalk = require('chalk');
+
+ const isForked = typeof process.send === 'function';
+ if (!isForked) {
+ const fp = path.relative('.', process.argv[1]);
+
+ console.log();
+ console.error('Test files must be run with the AVA CLI:\n\n ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n');
+
+ process.exit(1); // eslint-disable-line unicorn/no-process-exit
+ }
+}
+
+/* eslint-enable import/order */
+const Bluebird = require('bluebird');
+const currentlyUnhandled = require('currently-unhandled')();
+const isObj = require('is-obj');
+const adapter = require('./process-adapter');
+const globals = require('./globals');
+const serializeError = require('./serialize-error');
+
+const opts = adapter.opts;
+const testPath = opts.file;
+globals.options = opts;
+
+// Bluebird specific
+Bluebird.longStackTraces();
+
+(opts.require || []).forEach(require);
+
+adapter.installSourceMapSupport();
+adapter.installPrecompilerHook();
+
+const dependencies = [];
+adapter.installDependencyTracking(dependencies, testPath);
+
+// Set when main.js is required (since test files should have `require('ava')`).
+let runner = null;
+exports.setRunner = newRunner => {
+ runner = newRunner;
+};
+
+require(testPath); // eslint-disable-line import/no-dynamic-require
+
+// If AVA was not required, show an error
+if (!runner) {
+ adapter.send('no-tests', {avaRequired: false});
+}
+
+function attributeLeakedError(err) {
+ if (!runner) {
+ return false;
+ }
+
+ return runner.attributeLeakedError(err);
+}
+
+const attributedRejections = new Set();
+process.on('unhandledRejection', (reason, promise) => {
+ if (attributeLeakedError(reason)) {
+ attributedRejections.add(promise);
+ }
+});
+
+process.on('uncaughtException', exception => {
+ if (attributeLeakedError(exception)) {
+ return;
+ }
+
+ let serialized;
+ try {
+ serialized = serializeError(exception);
+ } catch (ignore) { // eslint-disable-line unicorn/catch-error-name
+ // Avoid using serializeError
+ const err = new Error('Failed to serialize uncaught exception');
+ serialized = {
+ avaAssertionError: false,
+ name: err.name,
+ message: err.message,
+ stack: err.stack
+ };
+ }
+
+ // Ensure the IPC channel is refereced. The uncaught exception will kick off
+ // the teardown sequence, for which the messages must be received.
+ adapter.ipcChannel.ref();
+
+ adapter.send('uncaughtException', {exception: serialized});
+});
+
+let tearingDown = false;
+process.on('ava-teardown', () => {
+ // AVA-teardown can be sent more than once
+ if (tearingDown) {
+ return;
+ }
+ tearingDown = true;
+
+ let rejections = currentlyUnhandled()
+ .filter(rejection => !attributedRejections.has(rejection.promise));
+
+ if (rejections.length > 0) {
+ rejections = rejections.map(rejection => {
+ let reason = rejection.reason;
+ if (!isObj(reason) || typeof reason.message !== 'string') {
+ reason = {
+ message: String(reason)
+ };
+ }
+ return serializeError(reason);
+ });
+
+ adapter.send('unhandledRejections', {rejections});
+ }
+
+ // Include dependencies in the final teardown message. This ensures the full
+ // set of dependencies is included no matter how the process exits, unless
+ // it flat out crashes.
+ adapter.send('teardown', {dependencies});
+});
+
+process.on('ava-exit', () => {
+ process.exit(0); // eslint-disable-line xo/no-process-exit
+});
diff --git a/node_modules/ava/lib/test.js b/node_modules/ava/lib/test.js
new file mode 100644
index 000000000..a9b0fb1d9
--- /dev/null
+++ b/node_modules/ava/lib/test.js
@@ -0,0 +1,416 @@
+'use strict';
+const isGeneratorFn = require('is-generator-fn');
+const co = require('co-with-promise');
+const observableToPromise = require('observable-to-promise');
+const isPromise = require('is-promise');
+const isObservable = require('is-observable');
+const plur = require('plur');
+const assert = require('./assert');
+const formatAssertError = require('./format-assert-error');
+const globals = require('./globals');
+
+class SkipApi {
+ constructor(test) {
+ this._test = test;
+ }
+}
+
+const captureStack = start => {
+ const limitBefore = Error.stackTraceLimit;
+ Error.stackTraceLimit = 1;
+ const obj = {};
+ Error.captureStackTrace(obj, start);
+ Error.stackTraceLimit = limitBefore;
+ return obj.stack;
+};
+
+class ExecutionContext {
+ constructor(test) {
+ this._test = test;
+ this.skip = new SkipApi(test);
+ }
+
+ plan(ct) {
+ this._test.plan(ct, captureStack(this.plan));
+ }
+
+ get end() {
+ const end = this._test.bindEndCallback();
+ const endFn = err => end(err, captureStack(endFn));
+ return endFn;
+ }
+
+ get title() {
+ return this._test.title;
+ }
+
+ get context() {
+ const contextRef = this._test.contextRef;
+ return contextRef && contextRef.context;
+ }
+
+ set context(context) {
+ const contextRef = this._test.contextRef;
+
+ if (!contextRef) {
+ this._test.saveFirstError(new Error(`\`t.context\` is not available in ${this._test.metadata.type} tests`));
+ return;
+ }
+
+ contextRef.context = context;
+ }
+
+ _throwsArgStart(assertion, file, line) {
+ this._test.trackThrows({assertion, file, line});
+ }
+ _throwsArgEnd() {
+ this._test.trackThrows(null);
+ }
+}
+Object.defineProperty(ExecutionContext.prototype, 'context', {enumerable: true});
+
+{
+ const assertions = assert.wrapAssertions({
+ pass(executionContext) {
+ executionContext._test.countPassedAssertion();
+ },
+
+ pending(executionContext, promise) {
+ executionContext._test.addPendingAssertion(promise);
+ },
+
+ fail(executionContext, error) {
+ executionContext._test.addFailedAssertion(error);
+ }
+ });
+ Object.assign(ExecutionContext.prototype, assertions);
+
+ function skipFn() {
+ this._test.countPassedAssertion();
+ }
+ Object.keys(assertions).forEach(el => {
+ SkipApi.prototype[el] = skipFn;
+ });
+}
+
+class Test {
+ constructor(options) {
+ this.contextRef = options.contextRef;
+ this.failWithoutAssertions = options.failWithoutAssertions;
+ this.fn = isGeneratorFn(options.fn) ? co.wrap(options.fn) : options.fn;
+ this.getSnapshotState = options.getSnapshotState;
+ this.metadata = options.metadata;
+ this.onResult = options.onResult;
+ this.title = options.title;
+
+ this.assertCount = 0;
+ this.assertError = undefined;
+ this.calledEnd = false;
+ this.duration = null;
+ this.endCallbackFinisher = null;
+ this.finishDueToAttributedError = null;
+ this.finishDueToInactivity = null;
+ this.finishing = false;
+ this.pendingAssertionCount = 0;
+ this.pendingThrowsAssertion = null;
+ this.planCount = null;
+ this.startedAt = 0;
+ }
+
+ bindEndCallback() {
+ if (this.metadata.callback) {
+ return (err, stack) => {
+ this.endCallback(err, stack);
+ };
+ }
+
+ throw new Error('`t.end()`` is not supported in this context. To use `t.end()` as a callback, you must use "callback mode" via `test.cb(testName, fn)`');
+ }
+
+ endCallback(err, stack) {
+ if (this.calledEnd) {
+ this.saveFirstError(new Error('`t.end()` called more than once'));
+ return;
+ }
+ this.calledEnd = true;
+
+ if (err) {
+ this.saveFirstError(new assert.AssertionError({
+ actual: err,
+ message: 'Callback called with an error',
+ stack,
+ values: [formatAssertError.formatWithLabel('Error:', err)]
+ }));
+ }
+
+ if (this.endCallbackFinisher) {
+ this.endCallbackFinisher();
+ }
+ }
+
+ createExecutionContext() {
+ return new ExecutionContext(this);
+ }
+
+ countPassedAssertion() {
+ if (this.finishing) {
+ this.saveFirstError(new Error('Assertion passed, but test has already finished'));
+ }
+
+ this.assertCount++;
+ }
+
+ addPendingAssertion(promise) {
+ if (this.finishing) {
+ this.saveFirstError(new Error('Assertion passed, but test has already finished'));
+ }
+
+ this.assertCount++;
+ this.pendingAssertionCount++;
+ promise
+ .catch(err => this.saveFirstError(err))
+ .then(() => this.pendingAssertionCount--);
+ }
+
+ addFailedAssertion(error) {
+ if (this.finishing) {
+ this.saveFirstError(new Error('Assertion failed, but test has already finished'));
+ }
+
+ this.assertCount++;
+ this.saveFirstError(error);
+ }
+
+ saveFirstError(err) {
+ if (!this.assertError) {
+ this.assertError = err;
+ }
+ }
+
+ plan(count, planStack) {
+ if (typeof count !== 'number') {
+ throw new TypeError('Expected a number');
+ }
+
+ this.planCount = count;
+
+ // In case the `planCount` doesn't match `assertCount, we need the stack of
+ // this function to throw with a useful stack.
+ this.planStack = planStack;
+ }
+
+ verifyPlan() {
+ if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) {
+ this.saveFirstError(new assert.AssertionError({
+ assertion: 'plan',
+ message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`,
+ operator: '===',
+ stack: this.planStack
+ }));
+ }
+ }
+
+ verifyAssertions() {
+ if (!this.assertError) {
+ if (this.failWithoutAssertions && !this.calledEnd && this.planCount === null && this.assertCount === 0) {
+ this.saveFirstError(new Error('Test finished without running any assertions'));
+ } else if (this.pendingAssertionCount > 0) {
+ this.saveFirstError(new Error('Test finished, but an assertion is still pending'));
+ }
+ }
+ }
+
+ trackThrows(pending) {
+ this.pendingThrowsAssertion = pending;
+ }
+
+ detectImproperThrows(err) {
+ if (!this.pendingThrowsAssertion) {
+ return false;
+ }
+
+ const pending = this.pendingThrowsAssertion;
+ this.pendingThrowsAssertion = null;
+
+ const values = [];
+ if (err) {
+ values.push(formatAssertError.formatWithLabel(`The following error was thrown, possibly before \`t.${pending.assertion}()\` could be called:`, err));
+ }
+
+ this.saveFirstError(new assert.AssertionError({
+ assertion: pending.assertion,
+ fixedSource: {file: pending.file, line: pending.line},
+ improperUsage: true,
+ message: `Improper usage of \`t.${pending.assertion}()\` detected`,
+ stack: err instanceof Error && err.stack,
+ values
+ }));
+ return true;
+ }
+
+ waitForPendingThrowsAssertion() {
+ return new Promise(resolve => {
+ this.finishDueToAttributedError = () => {
+ resolve(this.finishPromised());
+ };
+
+ this.finishDueToInactivity = () => {
+ this.detectImproperThrows();
+ resolve(this.finishPromised());
+ };
+
+ // Wait up to a second to see if an error can be attributed to the
+ // pending assertion.
+ globals.setTimeout(() => this.finishDueToInactivity(), 1000).unref();
+ });
+ }
+
+ attributeLeakedError(err) {
+ if (!this.detectImproperThrows(err)) {
+ return false;
+ }
+
+ this.finishDueToAttributedError();
+ return true;
+ }
+
+ callFn() {
+ try {
+ return {
+ ok: true,
+ retval: this.fn(this.createExecutionContext())
+ };
+ } catch (err) {
+ return {
+ ok: false,
+ error: err
+ };
+ }
+ }
+
+ run() {
+ this.startedAt = globals.now();
+
+ const result = this.callFn();
+ if (!result.ok) {
+ if (!this.detectImproperThrows(result.error)) {
+ this.saveFirstError(new assert.AssertionError({
+ message: 'Error thrown in test',
+ stack: result.error instanceof Error && result.error.stack,
+ values: [formatAssertError.formatWithLabel('Error:', result.error)]
+ }));
+ }
+ return this.finish();
+ }
+
+ const returnedObservable = isObservable(result.retval);
+ const returnedPromise = isPromise(result.retval);
+
+ let promise;
+ if (returnedObservable) {
+ promise = observableToPromise(result.retval);
+ } else if (returnedPromise) {
+ // `retval` can be any thenable, so convert to a proper promise.
+ promise = Promise.resolve(result.retval);
+ }
+
+ if (this.metadata.callback) {
+ if (returnedObservable || returnedPromise) {
+ const asyncType = returnedObservable ? 'observables' : 'promises';
+ this.saveFirstError(new Error(`Do not return ${asyncType} from tests declared via \`test.cb(...)\`, if you want to return a promise simply declare the test via \`test(...)\``));
+ return this.finish();
+ }
+
+ if (this.calledEnd) {
+ return this.finish();
+ }
+
+ return new Promise(resolve => {
+ this.endCallbackFinisher = () => {
+ resolve(this.finishPromised());
+ };
+
+ this.finishDueToAttributedError = () => {
+ resolve(this.finishPromised());
+ };
+
+ this.finishDueToInactivity = () => {
+ this.saveFirstError(new Error('`t.end()` was never called'));
+ resolve(this.finishPromised());
+ };
+ });
+ }
+
+ if (promise) {
+ return new Promise(resolve => {
+ this.finishDueToAttributedError = () => {
+ resolve(this.finishPromised());
+ };
+
+ this.finishDueToInactivity = () => {
+ const err = returnedObservable ?
+ new Error('Observable returned by test never completed') :
+ new Error('Promise returned by test never resolved');
+ this.saveFirstError(err);
+ resolve(this.finishPromised());
+ };
+
+ promise
+ .catch(err => {
+ if (!this.detectImproperThrows(err)) {
+ this.saveFirstError(new assert.AssertionError({
+ message: 'Rejected promise returned by test',
+ stack: err instanceof Error && err.stack,
+ values: [formatAssertError.formatWithLabel('Rejection reason:', err)]
+ }));
+ }
+ })
+ .then(() => resolve(this.finishPromised()));
+ });
+ }
+
+ return this.finish();
+ }
+
+ finish() {
+ this.finishing = true;
+
+ if (!this.assertError && this.pendingThrowsAssertion) {
+ return this.waitForPendingThrowsAssertion();
+ }
+
+ this.verifyPlan();
+ this.verifyAssertions();
+
+ this.duration = globals.now() - this.startedAt;
+
+ let reason = this.assertError;
+ let passed = !reason;
+
+ if (this.metadata.failing) {
+ passed = !passed;
+
+ if (passed) {
+ reason = undefined;
+ } else {
+ reason = new Error('Test was expected to fail, but succeeded, you should stop marking the test as failing');
+ }
+ }
+
+ this.onResult({
+ passed,
+ result: this,
+ reason
+ });
+
+ return passed;
+ }
+
+ finishPromised() {
+ return new Promise(resolve => {
+ resolve(this.finish());
+ });
+ }
+}
+
+module.exports = Test;
diff --git a/node_modules/ava/lib/validate-test.js b/node_modules/ava/lib/validate-test.js
new file mode 100644
index 000000000..8258a5990
--- /dev/null
+++ b/node_modules/ava/lib/validate-test.js
@@ -0,0 +1,48 @@
+'use strict';
+
+function validate(title, fn, metadata) {
+ if (metadata.type !== 'test') {
+ if (metadata.exclusive) {
+ return '`only` is only for tests and cannot be used with hooks';
+ }
+
+ if (metadata.failing) {
+ return '`failing` is only for tests and cannot be used with hooks';
+ }
+
+ if (metadata.todo) {
+ return '`todo` is only for documentation of future tests and cannot be used with hooks';
+ }
+ }
+
+ if (metadata.todo) {
+ if (typeof fn === 'function') {
+ return '`todo` tests are not allowed to have an implementation. Use ' +
+ '`test.skip()` for tests with an implementation.';
+ }
+
+ if (typeof title !== 'string') {
+ return '`todo` tests require a title';
+ }
+
+ if (metadata.skipped || metadata.failing || metadata.exclusive) {
+ return '`todo` tests are just for documentation and cannot be used with `skip`, `only`, or `failing`';
+ }
+ } else if (typeof fn !== 'function') {
+ return 'Expected an implementation. Use `test.todo()` for tests without an implementation.';
+ }
+
+ if (metadata.always) {
+ if (!(metadata.type === 'after' || metadata.type === 'afterEach')) {
+ return '`always` can only be used with `after` and `afterEach`';
+ }
+ }
+
+ if (metadata.skipped && metadata.exclusive) {
+ return '`only` tests cannot be skipped';
+ }
+
+ return null;
+}
+
+module.exports = validate;
diff --git a/node_modules/ava/lib/watcher.js b/node_modules/ava/lib/watcher.js
new file mode 100644
index 000000000..3d7094ffb
--- /dev/null
+++ b/node_modules/ava/lib/watcher.js
@@ -0,0 +1,322 @@
+'use strict';
+const nodePath = require('path');
+const debug = require('debug')('ava:watcher');
+const diff = require('lodash.difference');
+const chokidar = require('chokidar');
+const flatten = require('arr-flatten');
+const union = require('array-union');
+const uniq = require('array-uniq');
+const AvaFiles = require('./ava-files');
+
+function rethrowAsync(err) {
+ // Don't swallow exceptions. Note that any
+ // expected error should already have been logged
+ setImmediate(() => {
+ throw err;
+ });
+}
+
+class Debouncer {
+ constructor(watcher) {
+ this.watcher = watcher;
+ this.timer = null;
+ this.repeat = false;
+ }
+ debounce() {
+ if (this.timer) {
+ this.again = true;
+ return;
+ }
+
+ const timer = setTimeout(() => {
+ this.watcher.busy.then(() => {
+ // Do nothing if debouncing was canceled while waiting for the busy
+ // promise to fulfil
+ if (this.timer !== timer) {
+ return;
+ }
+
+ if (this.again) {
+ this.timer = null;
+ this.again = false;
+ this.debounce();
+ } else {
+ this.watcher.runAfterChanges();
+ this.timer = null;
+ this.again = false;
+ }
+ });
+ }, 10);
+
+ this.timer = timer;
+ }
+ cancel() {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ this.again = false;
+ }
+ }
+}
+
+class TestDependency {
+ constructor(file, sources) {
+ this.file = file;
+ this.sources = sources;
+ }
+ contains(source) {
+ return this.sources.indexOf(source) !== -1;
+ }
+}
+
+class Watcher {
+ constructor(logger, api, files, sources) {
+ this.debouncer = new Debouncer(this);
+ this.avaFiles = new AvaFiles({
+ files,
+ sources
+ });
+
+ this.clearLogOnNextRun = true;
+ this.runVector = 0;
+ this.run = specificFiles => {
+ if (this.runVector > 0) {
+ const cleared = this.clearLogOnNextRun && logger.clear();
+ if (!cleared) {
+ logger.reset();
+ logger.section();
+ }
+ this.clearLogOnNextRun = true;
+
+ logger.reset();
+ logger.start();
+ }
+
+ this.runVector += 1;
+
+ const currentVector = this.runVector;
+
+ let runOnlyExclusive = false;
+
+ if (specificFiles) {
+ const exclusiveFiles = specificFiles.filter(file => this.filesWithExclusiveTests.indexOf(file) !== -1);
+
+ runOnlyExclusive = exclusiveFiles.length !== this.filesWithExclusiveTests.length;
+
+ if (runOnlyExclusive) {
+ // The test files that previously contained exclusive tests are always
+ // run, together with the remaining specific files.
+ const remainingFiles = diff(specificFiles, exclusiveFiles);
+ specificFiles = this.filesWithExclusiveTests.concat(remainingFiles);
+ }
+ }
+
+ this.busy = api.run(specificFiles || files, {runOnlyExclusive})
+ .then(runStatus => {
+ runStatus.previousFailCount = this.sumPreviousFailures(currentVector);
+ logger.finish(runStatus);
+
+ const badCounts = runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount;
+ this.clearLogOnNextRun = this.clearLogOnNextRun && badCounts === 0;
+ })
+ .catch(rethrowAsync);
+ };
+
+ this.testDependencies = [];
+ this.trackTestDependencies(api, sources);
+
+ this.filesWithExclusiveTests = [];
+ this.trackExclusivity(api);
+
+ this.filesWithFailures = [];
+ this.trackFailures(api);
+
+ this.dirtyStates = {};
+ this.watchFiles();
+ this.rerunAll();
+ }
+ watchFiles() {
+ const patterns = this.avaFiles.getChokidarPatterns();
+
+ chokidar.watch(patterns.paths, {
+ ignored: patterns.ignored,
+ ignoreInitial: true
+ }).on('all', (event, path) => {
+ if (event === 'add' || event === 'change' || event === 'unlink') {
+ debug('Detected %s of %s', event, path);
+ this.dirtyStates[path] = event;
+ this.debouncer.debounce();
+ }
+ });
+ }
+ trackTestDependencies(api) {
+ const relative = absPath => nodePath.relative(process.cwd(), absPath);
+
+ api.on('test-run', runStatus => {
+ runStatus.on('dependencies', (file, dependencies) => {
+ const sourceDeps = dependencies.map(relative).filter(this.avaFiles.isSource);
+ this.updateTestDependencies(file, sourceDeps);
+ });
+ });
+ }
+ updateTestDependencies(file, sources) {
+ if (sources.length === 0) {
+ this.testDependencies = this.testDependencies.filter(dep => dep.file !== file);
+ return;
+ }
+
+ const isUpdate = this.testDependencies.some(dep => {
+ if (dep.file !== file) {
+ return false;
+ }
+
+ dep.sources = sources;
+
+ return true;
+ });
+
+ if (!isUpdate) {
+ this.testDependencies.push(new TestDependency(file, sources));
+ }
+ }
+ trackExclusivity(api) {
+ api.on('stats', stats => {
+ this.updateExclusivity(stats.file, stats.hasExclusive);
+ });
+ }
+ updateExclusivity(file, hasExclusiveTests) {
+ const index = this.filesWithExclusiveTests.indexOf(file);
+
+ if (hasExclusiveTests && index === -1) {
+ this.filesWithExclusiveTests.push(file);
+ } else if (!hasExclusiveTests && index !== -1) {
+ this.filesWithExclusiveTests.splice(index, 1);
+ }
+ }
+ trackFailures(api) {
+ api.on('test-run', (runStatus, files) => {
+ files.forEach(file => {
+ this.pruneFailures(nodePath.relative(process.cwd(), file));
+ });
+
+ const currentVector = this.runVector;
+ runStatus.on('error', err => {
+ this.countFailure(err.file, currentVector);
+ });
+ runStatus.on('test', result => {
+ if (result.error) {
+ this.countFailure(result.file, currentVector);
+ }
+ });
+ });
+ }
+ pruneFailures(file) {
+ this.filesWithFailures = this.filesWithFailures.filter(state => state.file !== file);
+ }
+ countFailure(file, vector) {
+ const isUpdate = this.filesWithFailures.some(state => {
+ if (state.file !== file) {
+ return false;
+ }
+
+ state.count++;
+ return true;
+ });
+
+ if (!isUpdate) {
+ this.filesWithFailures.push({
+ file,
+ vector,
+ count: 1
+ });
+ }
+ }
+ sumPreviousFailures(beforeVector) {
+ let total = 0;
+
+ this.filesWithFailures.forEach(state => {
+ if (state.vector < beforeVector) {
+ total += state.count;
+ }
+ });
+
+ return total;
+ }
+ cleanUnlinkedTests(unlinkedTests) {
+ unlinkedTests.forEach(testFile => {
+ this.updateTestDependencies(testFile, []);
+ this.updateExclusivity(testFile, false);
+ this.pruneFailures(testFile);
+ });
+ }
+ observeStdin(stdin) {
+ stdin.resume();
+ stdin.setEncoding('utf8');
+
+ stdin.on('data', data => {
+ data = data.trim().toLowerCase();
+ if (data !== 'r' && data !== 'rs') {
+ return;
+ }
+
+ // Cancel the debouncer, it might rerun specific tests whereas *all* tests
+ // need to be rerun
+ this.debouncer.cancel();
+ this.busy.then(() => {
+ // Cancel the debouncer again, it might have restarted while waiting for
+ // the busy promise to fulfil
+ this.debouncer.cancel();
+ this.clearLogOnNextRun = false;
+ this.rerunAll();
+ });
+ });
+ }
+ rerunAll() {
+ this.dirtyStates = {};
+ this.run();
+ }
+ runAfterChanges() {
+ const dirtyStates = this.dirtyStates;
+ this.dirtyStates = {};
+
+ const dirtyPaths = Object.keys(dirtyStates);
+ const dirtyTests = dirtyPaths.filter(this.avaFiles.isTest);
+ const dirtySources = diff(dirtyPaths, dirtyTests);
+ const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink');
+ const unlinkedTests = diff(dirtyTests, addedOrChangedTests);
+
+ this.cleanUnlinkedTests(unlinkedTests);
+
+ // No need to rerun tests if the only change is that tests were deleted
+ if (unlinkedTests.length === dirtyPaths.length) {
+ return;
+ }
+
+ if (dirtySources.length === 0) {
+ // Run any new or changed tests
+ this.run(addedOrChangedTests);
+ return;
+ }
+
+ // Try to find tests that depend on the changed source files
+ const testsBySource = dirtySources.map(path => {
+ return this.testDependencies.filter(dep => dep.contains(path)).map(dep => {
+ debug('%s is a dependency of %s', path, dep.file);
+ return dep.file;
+ });
+ }, this).filter(tests => tests.length > 0);
+
+ // Rerun all tests if source files were changed that could not be traced to
+ // specific tests
+ if (testsBySource.length !== dirtySources.length) {
+ debug('Sources remain that cannot be traced to specific tests. Rerunning all tests');
+ this.run();
+ return;
+ }
+
+ // Run all affected tests
+ this.run(union(addedOrChangedTests, uniq(flatten(testsBySource))));
+ }
+}
+
+module.exports = Watcher;