diff options
Diffstat (limited to 'node_modules/ava')
55 files changed, 2369 insertions, 950 deletions
diff --git a/node_modules/ava/api.js b/node_modules/ava/api.js index e5c5a7b92..99213a757 100644 --- a/node_modules/ava/api.js +++ b/node_modules/ava/api.js @@ -21,7 +21,7 @@ const fork = require('./lib/fork'); function resolveModules(modules) { return arrify(modules).map(name => { - const modulePath = resolveCwd(name); + const modulePath = resolveCwd.silent(name); if (modulePath === null) { throw new Error(`Could not resolve required module '${name}'`); @@ -60,6 +60,10 @@ class Api extends EventEmitter { precompiled[resolvedfpath] = hash; const options = Object.assign({}, this.options, {precompiled}); + if (runStatus.updateSnapshots) { + // Don't use in Object.assign() since it'll override options.updateSnapshots even when false. + options.updateSnapshots = true; + } const emitter = fork(file, options, execArgv); runStatus.observeFork(emitter); @@ -137,7 +141,8 @@ class Api extends EventEmitter { runOnlyExclusive: options.runOnlyExclusive, prefixTitles: this.options.explicitTitles || files.length > 1, base: path.relative(process.cwd(), commonPathPrefix(files)) + path.sep, - failFast: this.options.failFast + failFast: this.options.failFast, + updateSnapshots: options.updateSnapshots }); this.emit('test-run', runStatus, files); @@ -171,9 +176,9 @@ class Api extends EventEmitter { const execArgv = this.options.testOnlyExecArgv || process.execArgv; let debugArgIndex = -1; - // --debug-brk is used in addition to --inspect to break on first line and wait + // --inspect-brk is used in addition to --inspect to break on first line and wait execArgv.some((arg, index) => { - const isDebugArg = arg === '--inspect' || arg.indexOf('--inspect=') === 0; + const isDebugArg = /^--inspect(-brk)?($|=)/.test(arg); if (isDebugArg) { debugArgIndex = index; } @@ -184,7 +189,7 @@ class Api extends EventEmitter { const isInspect = debugArgIndex >= 0; if (!isInspect) { execArgv.some((arg, index) => { - const isDebugArg = arg === '--debug' || arg === '--debug-brk' || arg.indexOf('--debug-brk=') === 0 || arg.indexOf('--debug=') === 0; + const isDebugArg = /^--debug(-brk)?($|=)/.test(arg); if (isDebugArg) { debugArgIndex = index; } diff --git a/node_modules/ava/cli.js b/node_modules/ava/cli.js index d9d338941..80151397c 100755 --- a/node_modules/ava/cli.js +++ b/node_modules/ava/cli.js @@ -1,18 +1,11 @@ #!/usr/bin/env node 'use strict'; -const path = require('path'); const debug = require('debug')('ava'); +const importLocal = require('import-local'); -// Prefer the local installation of AVA. -const resolveCwd = require('resolve-cwd'); -const localCLI = resolveCwd('ava/cli'); - -// Use `path.relative()` to detect local AVA installation, -// because __filename's case is inconsistent on Windows -// see https://github.com/nodejs/node/issues/6624 -if (localCLI && path.relative(localCLI, __filename) !== '') { +// Prefer the local installation of AVA +if (importLocal(__filename)) { debug('Using local install of AVA'); - require(localCLI); // eslint-disable-line import/no-dynamic-require } else { if (debug.enabled) { require('time-require'); // eslint-disable-line import/no-unassigned-import diff --git a/node_modules/ava/index.js b/node_modules/ava/index.js index 3640c4839..536279f20 100644 --- a/node_modules/ava/index.js +++ b/node_modules/ava/index.js @@ -2,7 +2,7 @@ // Ensure the same AVA install is loaded by the test file as by the test worker if (process.env.AVA_PATH && process.env.AVA_PATH !== __dirname) { - module.exports = require(process.env.AVA_PATH); // eslint-disable-line import/no-dynamic-require + module.exports = require(process.env.AVA_PATH); } else { module.exports = require('./lib/main'); } diff --git a/node_modules/ava/index.js.flow b/node_modules/ava/index.js.flow index 38670fc39..b86e22c65 100644 --- a/node_modules/ava/index.js.flow +++ b/node_modules/ava/index.js.flow @@ -58,8 +58,8 @@ type AssertContext = { // Assert that function throws an error or promise rejects. // @param error Can be a constructor, regex, error message or validation function. throws: { - (value: PromiseLike<mixed>, error?: ErrorValidator, message?: string): Promise<Error>; - (value: () => mixed, error?: ErrorValidator, message?: string): Error; + (value: PromiseLike<mixed>, error?: ErrorValidator, message?: string): Promise<any>; + (value: () => mixed, error?: ErrorValidator, message?: string): any; }; // Assert that function doesn't throw an error or promise resolves. notThrows: { @@ -84,9 +84,8 @@ type TestContext = AssertContext & { plan(count: number): void; skip: AssertContext; }; -type CallbackTestContext = TestContext & { end(): void; }; -type ContextualTestContext = TestContext & { context: any; }; -type ContextualCallbackTestContext = CallbackTestContext & { context: any; }; +type ContextualTestContext = TestContext & { context: any; }; +type ContextualCallbackTestContext = TestContext & { context: any; end(): void; }; /** * Test Implementations @@ -101,8 +100,6 @@ type TestImplementation<T, R> = | TestFunction<T, R> | Array<TestFunction<T, R>>; -type Test = TestImplementation<TestContext, SpecialReturnTypes | void>; -type CallbackTest = TestImplementation<CallbackTestContext, void>; type ContextualTest = TestImplementation<ContextualTestContext, SpecialReturnTypes | void>; type ContextualCallbackTest = TestImplementation<ContextualCallbackTestContext, void>; @@ -111,40 +108,6 @@ type ContextualCallbackTest = TestImplementation<ContextualCallbackTestContext, * Method Types */ -type TestMethod = { - ( implementation: Test): void; - (name: string, implementation: Test): void; - - serial : TestMethod; - before : TestMethod; - after : TestMethod; - skip : TestMethod; - todo : TestMethod; - failing : TestMethod; - only : TestMethod; - beforeEach : TestMethod; - afterEach : TestMethod; - cb : CallbackTestMethod; - always : TestMethod; -}; - -type CallbackTestMethod = { - ( implementation: CallbackTest): void; - (name: string, implementation: CallbackTest): void; - - serial : CallbackTestMethod; - before : CallbackTestMethod; - after : CallbackTestMethod; - skip : CallbackTestMethod; - todo : CallbackTestMethod; - failing : CallbackTestMethod; - only : CallbackTestMethod; - beforeEach : CallbackTestMethod; - afterEach : CallbackTestMethod; - cb : CallbackTestMethod; - always : CallbackTestMethod; -}; - type ContextualTestMethod = { ( implementation: ContextualTest): void; (name: string, implementation: ContextualTest): void; @@ -184,8 +147,8 @@ type ContextualCallbackTestMethod = { */ declare module.exports: { - ( run: ContextualTest): void; - (name: string, run: ContextualTest): void; + ( run: ContextualTest, ...args: any): void; + (name: string, run: ContextualTest, ...args: any): void; beforeEach : ContextualTestMethod; afterEach : ContextualTestMethod; diff --git a/node_modules/ava/lib/assert.js b/node_modules/ava/lib/assert.js index c16e11a1a..a0e9fe82c 100644 --- a/node_modules/ava/lib/assert.js +++ b/node_modules/ava/lib/assert.js @@ -1,12 +1,32 @@ 'use strict'; +const concordance = require('concordance'); 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 concordanceOptions = require('./concordance-options').default; +const concordanceDiffOptions = require('./concordance-options').diff; const enhanceAssert = require('./enhance-assert'); -const formatAssertError = require('./format-assert-error'); +const snapshotManager = require('./snapshot-manager'); + +function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) { + options = Object.assign({}, options, concordanceDiffOptions); + return { + label: 'Difference:', + formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options) + }; +} + +function formatDescriptorWithLabel(label, descriptor) { + return { + label, + formatted: concordance.formatDescriptor(descriptor, concordanceOptions) + }; +} + +function formatWithLabel(label, value) { + return formatDescriptorWithLabel(label, concordance.describe(value, concordanceOptions)); +} class AssertionError extends Error { constructor(opts) { @@ -19,6 +39,11 @@ class AssertionError extends Error { this.operator = opts.operator; this.values = opts.values || []; + // Raw expected and actual objects are stored for custom reporters + // (such as wallaby.js), that manage worker processes directly and + // use the values for custom diff views + this.raw = opts.raw; + // Reserved for power-assert statements this.statements = []; @@ -41,7 +66,6 @@ function wrapAssertions(callbacks) { const fail = callbacks.fail; const noop = () => {}; - const makeNoop = () => noop; const makeRethrow = reason => () => { throw reason; }; @@ -59,31 +83,27 @@ function wrapAssertions(callbacks) { }, is(actual, expected, message) { - if (actual === expected) { + if (Object.is(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) - ]; - + const actualDescriptor = concordance.describe(actual, concordanceOptions); + const expectedDescriptor = concordance.describe(expected, concordanceOptions); fail(this, new AssertionError({ assertion: 'is', message, - operator: '===', - values + raw: {actual, expected}, + values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)] })); } }, not(actual, expected, message) { - if (actual === expected) { + if (Object.is(actual, expected)) { fail(this, new AssertionError({ assertion: 'not', message, - operator: '!==', - values: [formatAssertError.formatWithLabel('Value is strictly equal:', actual)] + raw: {actual, expected}, + values: [formatWithLabel('Value is the same as:', actual)] })); } else { pass(this); @@ -91,29 +111,30 @@ function wrapAssertions(callbacks) { }, deepEqual(actual, expected, message) { - if (deepEqual(actual, expected)) { + const result = concordance.compare(actual, expected, concordanceOptions); + if (result.pass) { 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) - ]; - + const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions); + const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions); fail(this, new AssertionError({ assertion: 'deepEqual', message, - values + raw: {actual, expected}, + values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)] })); } }, notDeepEqual(actual, expected, message) { - if (deepEqual(actual, expected)) { + const result = concordance.compare(actual, expected, concordanceOptions); + if (result.pass) { + const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions); fail(this, new AssertionError({ assertion: 'notDeepEqual', message, - values: [formatAssertError.formatWithLabel('Value is deeply equal:', actual)] + raw: {actual, expected}, + values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)] })); } else { pass(this); @@ -131,7 +152,7 @@ function wrapAssertions(callbacks) { assertion: 'throws', improperUsage: true, message: '`t.throws()` must be called with a function, Promise, or Observable', - values: [formatAssertError.formatWithLabel('Called with:', fn)] + values: [formatWithLabel('Called with:', fn)] })); return; } @@ -160,15 +181,13 @@ function wrapAssertions(callbacks) { }, coreAssertThrowsErrorArg); return actual; } catch (err) { - const values = threw ? - [formatAssertError.formatWithLabel('Threw unexpected exception:', actual)] : - null; - throw new AssertionError({ assertion: 'throws', message, stack, - values + values: threw ? + [formatWithLabel('Threw unexpected exception:', actual)] : + null }); } }; @@ -176,7 +195,14 @@ function wrapAssertions(callbacks) { 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)); + const intermediate = promise.then(value => { + throw new AssertionError({ + assertion: 'throws', + message: 'Expected promise to be rejected, but it was resolved instead', + values: [formatWithLabel('Resolved with:', value)] + }); + }, reason => test(makeRethrow(reason), stack)); + pending(this, intermediate); // Don't reject the returned promise, even if the assertion fails. return intermediate.catch(noop); @@ -202,7 +228,7 @@ function wrapAssertions(callbacks) { assertion: 'notThrows', improperUsage: true, message: '`t.notThrows()` must be called with a function, Promise, or Observable', - values: [formatAssertError.formatWithLabel('Called with:', fn)] + values: [formatWithLabel('Called with:', fn)] })); return; } @@ -215,7 +241,7 @@ function wrapAssertions(callbacks) { assertion: 'notThrows', message, stack, - values: [formatAssertError.formatWithLabel('Threw:', err.actual)] + values: [formatWithLabel('Threw:', err.actual)] }); } }; @@ -242,28 +268,57 @@ function wrapAssertions(callbacks) { fail(this, new AssertionError({ assertion: 'ifError', message, - values: [formatAssertError.formatWithLabel('Error:', actual)] + values: [formatWithLabel('Error:', actual)] })); } else { pass(this); } }, - snapshot(actual, message) { - const state = this._test.getSnapshotState(); - const result = state.match(this.title, actual); + snapshot(expected, optionsOrMessage, message) { + const options = {}; + if (typeof optionsOrMessage === 'string') { + message = optionsOrMessage; + } else if (optionsOrMessage) { + options.id = optionsOrMessage.id; + } + options.expected = expected; + options.message = message; + + let result; + try { + result = this._test.compareWithSnapshot(options); + } catch (err) { + if (!(err instanceof snapshotManager.SnapshotError)) { + throw err; + } + + const improperUsage = {name: err.name, snapPath: err.snapPath}; + if (err instanceof snapshotManager.VersionMismatchError) { + improperUsage.snapVersion = err.snapVersion; + improperUsage.expectedVersion = err.expectedVersion; + } + + fail(this, new AssertionError({ + assertion: 'snapshot', + message: message || 'Could not compare snapshot', + improperUsage + })); + return; + } + 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'); + } else if (result.actual) { fail(this, new AssertionError({ assertion: 'snapshot', message: message || 'Did not match snapshot', - values: [{label: 'Difference:', formatted: diff}] + values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})] + })); + } else { + fail(this, new AssertionError({ + assertion: 'snapshot', + message: message || 'No snapshot available, run with --update-snapshots' })); } } @@ -276,7 +331,7 @@ function wrapAssertions(callbacks) { assertion: 'truthy', message, operator: '!!', - values: [formatAssertError.formatWithLabel('Value is not truthy:', actual)] + values: [formatWithLabel('Value is not truthy:', actual)] }); } }, @@ -287,7 +342,7 @@ function wrapAssertions(callbacks) { assertion: 'falsy', message, operator: '!', - values: [formatAssertError.formatWithLabel('Value is not falsy:', actual)] + values: [formatWithLabel('Value is not falsy:', actual)] }); } }, @@ -297,7 +352,7 @@ function wrapAssertions(callbacks) { throw new AssertionError({ assertion: 'true', message, - values: [formatAssertError.formatWithLabel('Value is not `true`:', actual)] + values: [formatWithLabel('Value is not `true`:', actual)] }); } }, @@ -307,7 +362,7 @@ function wrapAssertions(callbacks) { throw new AssertionError({ assertion: 'false', message, - values: [formatAssertError.formatWithLabel('Value is not `false`:', actual)] + values: [formatWithLabel('Value is not `false`:', actual)] }); } }, @@ -318,7 +373,7 @@ function wrapAssertions(callbacks) { assertion: 'regex', improperUsage: true, message: '`t.regex()` must be called with a string', - values: [formatAssertError.formatWithLabel('Called with:', string)] + values: [formatWithLabel('Called with:', string)] }); } if (!(regex instanceof RegExp)) { @@ -326,7 +381,7 @@ function wrapAssertions(callbacks) { assertion: 'regex', improperUsage: true, message: '`t.regex()` must be called with a regular expression', - values: [formatAssertError.formatWithLabel('Called with:', regex)] + values: [formatWithLabel('Called with:', regex)] }); } @@ -335,8 +390,8 @@ function wrapAssertions(callbacks) { assertion: 'regex', message, values: [ - formatAssertError.formatWithLabel('Value must match expression:', string), - formatAssertError.formatWithLabel('Regular expression:', regex) + formatWithLabel('Value must match expression:', string), + formatWithLabel('Regular expression:', regex) ] }); } @@ -348,7 +403,7 @@ function wrapAssertions(callbacks) { assertion: 'notRegex', improperUsage: true, message: '`t.notRegex()` must be called with a string', - values: [formatAssertError.formatWithLabel('Called with:', string)] + values: [formatWithLabel('Called with:', string)] }); } if (!(regex instanceof RegExp)) { @@ -356,7 +411,7 @@ function wrapAssertions(callbacks) { assertion: 'notRegex', improperUsage: true, message: '`t.notRegex()` must be called with a regular expression', - values: [formatAssertError.formatWithLabel('Called with:', regex)] + values: [formatWithLabel('Called with:', regex)] }); } @@ -365,8 +420,8 @@ function wrapAssertions(callbacks) { assertion: 'notRegex', message, values: [ - formatAssertError.formatWithLabel('Value must not match expression:', string), - formatAssertError.formatWithLabel('Regular expression:', regex) + formatWithLabel('Value must not match expression:', string), + formatWithLabel('Regular expression:', regex) ] }); } diff --git a/node_modules/ava/lib/ava-files.js b/node_modules/ava/lib/ava-files.js index dd9a2ee6d..cfdc9f202 100644 --- a/node_modules/ava/lib/ava-files.js +++ b/node_modules/ava/lib/ava-files.js @@ -265,7 +265,7 @@ class AvaFiles { ignored = getDefaultIgnorePatterns().concat(ignored, overrideDefaultIgnorePatterns); if (paths.length === 0) { - paths = ['package.json', '**/*.js']; + paths = ['package.json', '**/*.js', '**/*.snap']; } paths = paths.concat(this.files); diff --git a/node_modules/ava/lib/babel-config.js b/node_modules/ava/lib/babel-config.js index c3be0dcfb..62e841f05 100644 --- a/node_modules/ava/lib/babel-config.js +++ b/node_modules/ava/lib/babel-config.js @@ -5,7 +5,7 @@ const chalk = require('chalk'); const figures = require('figures'); const configManager = require('hullabaloo-config-manager'); const md5Hex = require('md5-hex'); -const mkdirp = require('mkdirp'); +const makeDir = require('make-dir'); const colors = require('./colors'); function validate(conf) { @@ -19,7 +19,7 @@ function validate(conf) { 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.'; + message += 'See ' + chalk.underline('https://github.com/avajs/ava#es2017-support') + ' for allowed values.'; throw new Error(message); } @@ -90,7 +90,7 @@ function build(projectDir, cacheDir, userOptions, powerAssert) { const seed = md5Hex([process.versions.node, projectDir]); // Ensure cacheDir exists - mkdirp.sync(cacheDir); + makeDir.sync(cacheDir); // The file names predict where valid options may be cached, and thus should // include the seed. @@ -136,7 +136,7 @@ function build(projectDir, cacheDir, userOptions, powerAssert) { return resolveOptions(baseConfig, cache, optionsFile, verifierFile); }) .then(cacheKeys => ({ - getOptions: require(optionsFile).getOptions, // eslint-disable-line import/no-dynamic-require + getOptions: require(optionsFile).getOptions, // Include the seed in the cache keys used to store compilation results. cacheKeys: Object.assign({seed}, cacheKeys) })); diff --git a/node_modules/ava/lib/cli.js b/node_modules/ava/lib/cli.js index f6213f107..5649a8190 100644 --- a/node_modules/ava/lib/cli.js +++ b/node_modules/ava/lib/cli.js @@ -118,6 +118,10 @@ exports.run = () => { throw new Error(colors.error(figures.cross) + ' Watch mode is not available in CI, as it prevents AVA from terminating.'); } + if (cli.flags.concurrency === '') { + throw new Error(colors.error(figures.cross) + ' The --concurrency and -c flags must be provided the maximum number of test files to run at once.'); + } + 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.'); } diff --git a/node_modules/ava/lib/concordance-options.js b/node_modules/ava/lib/concordance-options.js new file mode 100644 index 000000000..18b4b0c77 --- /dev/null +++ b/node_modules/ava/lib/concordance-options.js @@ -0,0 +1,130 @@ +'use strict'; +const ansiStyles = require('ansi-styles'); +const chalk = require('chalk'); +const stripAnsi = require('strip-ansi'); +const cloneDeepWith = require('lodash.clonedeepwith'); +const reactPlugin = require('@concordance/react'); +const options = require('./globals').options; + +// Wrap Concordance's React plugin. Change the name to avoid collisions if in +// the future users can register plugins themselves. +const avaReactPlugin = Object.assign({}, reactPlugin, {name: 'ava-plugin-react'}); +const plugins = [avaReactPlugin]; + +const forceColor = new chalk.constructor({enabled: true}); + +const colorTheme = { + boolean: ansiStyles.yellow, + circular: forceColor.grey('[Circular]'), + date: { + invalid: forceColor.red('invalid'), + value: ansiStyles.blue + }, + diffGutters: { + actual: forceColor.red('-') + ' ', + expected: forceColor.green('+') + ' ', + padding: ' ' + }, + error: { + ctor: {open: ansiStyles.grey.open + '(', close: ')' + ansiStyles.grey.close}, + name: ansiStyles.magenta + }, + function: { + name: ansiStyles.blue, + stringTag: ansiStyles.magenta + }, + global: ansiStyles.magenta, + item: {after: forceColor.grey(',')}, + list: {openBracket: forceColor.grey('['), closeBracket: forceColor.grey(']')}, + mapEntry: {after: forceColor.grey(',')}, + maxDepth: forceColor.grey('…'), + null: ansiStyles.yellow, + number: ansiStyles.yellow, + object: { + openBracket: forceColor.grey('{'), + closeBracket: forceColor.grey('}'), + ctor: ansiStyles.magenta, + stringTag: {open: ansiStyles.magenta.open + '@', close: ansiStyles.magenta.close}, + secondaryStringTag: {open: ansiStyles.grey.open + '@', close: ansiStyles.grey.close} + }, + property: { + after: forceColor.grey(','), + keyBracket: {open: forceColor.grey('['), close: forceColor.grey(']')}, + valueFallback: forceColor.grey('…') + }, + react: { + functionType: forceColor.grey('\u235F'), + openTag: { + start: forceColor.grey('<'), + end: forceColor.grey('>'), + selfClose: forceColor.grey('/'), + selfCloseVoid: ' ' + forceColor.grey('/') + }, + closeTag: { + open: forceColor.grey('</'), + close: forceColor.grey('>') + }, + tagName: ansiStyles.magenta, + attribute: { + separator: '=', + value: { + openBracket: forceColor.grey('{'), + closeBracket: forceColor.grey('}'), + string: { + line: {open: forceColor.blue('"'), close: forceColor.blue('"'), escapeQuote: '"'} + } + } + }, + child: { + openBracket: forceColor.grey('{'), + closeBracket: forceColor.grey('}') + } + }, + regexp: { + source: {open: ansiStyles.blue.open + '/', close: '/' + ansiStyles.blue.close}, + flags: ansiStyles.yellow + }, + stats: {separator: forceColor.grey('---')}, + string: { + open: ansiStyles.blue.open, + close: ansiStyles.blue.close, + line: {open: forceColor.blue('\''), close: forceColor.blue('\'')}, + multiline: {start: forceColor.blue('`'), end: forceColor.blue('`')}, + controlPicture: ansiStyles.grey, + diff: { + insert: { + open: ansiStyles.bgGreen.open + ansiStyles.black.open, + close: ansiStyles.black.close + ansiStyles.bgGreen.close + }, + delete: { + open: ansiStyles.bgRed.open + ansiStyles.black.open, + close: ansiStyles.black.close + ansiStyles.bgRed.close + }, + equal: ansiStyles.blue, + insertLine: { + open: ansiStyles.green.open, + close: ansiStyles.green.close + }, + deleteLine: { + open: ansiStyles.red.open, + close: ansiStyles.red.close + } + } + }, + symbol: ansiStyles.yellow, + typedArray: { + bytes: ansiStyles.yellow + }, + undefined: ansiStyles.yellow +}; + +const plainTheme = cloneDeepWith(colorTheme, value => { + if (typeof value === 'string') { + return stripAnsi(value); + } +}); + +const theme = options.color === false ? plainTheme : colorTheme; +exports.default = {maxDepth: 3, plugins, theme}; +exports.diff = {maxDepth: 1, plugins, theme}; +exports.snapshotManager = {plugins, theme: plainTheme}; diff --git a/node_modules/ava/lib/enhance-assert.js b/node_modules/ava/lib/enhance-assert.js index 7808765b7..6e127b3d6 100644 --- a/node_modules/ava/lib/enhance-assert.js +++ b/node_modules/ava/lib/enhance-assert.js @@ -1,6 +1,7 @@ 'use strict'; +const concordance = require('concordance'); const dotProp = require('dot-prop'); -const formatValue = require('./format-assert-error').formatValue; +const concordanceOptions = require('./concordance-options').default; // When adding patterns, don't forget to add to // https://github.com/avajs/babel-preset-transform-test-files/blob/master/espower-patterns.json @@ -37,7 +38,10 @@ const formatter = context => { return args .map(arg => { const range = getNode(ast, arg.espath).range; - return [computeStatement(tokens, range), formatValue(arg.value, {maxDepth: 1})]; + const statement = computeStatement(tokens, range); + + const formatted = concordance.format(arg.value, concordanceOptions); + return [statement, formatted]; }) .reverse(); }; diff --git a/node_modules/ava/lib/format-assert-error.js b/node_modules/ava/lib/format-assert-error.js deleted file mode 100644 index a899af463..000000000 --- a/node_modules/ava/lib/format-assert-error.js +++ /dev/null @@ -1,121 +0,0 @@ -'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/main.js b/node_modules/ava/lib/main.js index 52618e8b7..1b03cc854 100644 --- a/node_modules/ava/lib/main.js +++ b/node_modules/ava/lib/main.js @@ -11,6 +11,7 @@ const runner = new Runner({ failWithoutAssertions: opts.failWithoutAssertions, file: opts.file, match: opts.match, + projectDir: opts.projectDir, serial: opts.serial, updateSnapshots: opts.updateSnapshots }); diff --git a/node_modules/ava/lib/process-adapter.js b/node_modules/ava/lib/process-adapter.js index b50f37398..5f9c0d79d 100644 --- a/node_modules/ava/lib/process-adapter.js +++ b/node_modules/ava/lib/process-adapter.js @@ -94,7 +94,7 @@ exports.installDependencyTracking = (dependencies, testPath) => { require.extensions[ext] = (module, filename) => { if (filename !== testPath) { - dependencies.push(filename); + dependencies.add(filename); } wrappedHandler(module, filename); diff --git a/node_modules/ava/lib/reporters/format-serialized-error.js b/node_modules/ava/lib/reporters/format-serialized-error.js new file mode 100644 index 000000000..6ab59e47c --- /dev/null +++ b/node_modules/ava/lib/reporters/format-serialized-error.js @@ -0,0 +1,26 @@ +'use strict'; +const chalk = require('chalk'); +const trimOffNewlines = require('trim-off-newlines'); + +function formatSerializedError(error) { + const printMessage = error.values.length === 0 ? + Boolean(error.message) : + !error.values[0].label.startsWith(error.message); + + if (error.statements.length === 0 && error.values.length === 0) { + return {formatted: null, printMessage}; + } + + let formatted = ''; + for (const value of error.values) { + formatted += `${value.label}\n\n${trimOffNewlines(value.formatted)}\n\n`; + } + + for (const statement of error.statements) { + formatted += `${statement[0]}\n${chalk.grey('=>')} ${trimOffNewlines(statement[1])}\n\n`; + } + + formatted = trimOffNewlines(formatted); + return {formatted, printMessage}; +} +module.exports = formatSerializedError; diff --git a/node_modules/ava/lib/reporters/improper-usage-messages.js b/node_modules/ava/lib/reporters/improper-usage-messages.js index 0a2626638..298ef79a5 100644 --- a/node_modules/ava/lib/reporters/improper-usage-messages.js +++ b/node_modules/ava/lib/reporters/improper-usage-messages.js @@ -7,15 +7,48 @@ exports.forError = error => { } const assertion = error.assertion; - if (assertion !== 'throws' || !assertion === 'notThrows') { - return null; - } - - return `Try wrapping the first argument to \`t.${assertion}()\` in a function: + if (assertion === 'throws' || assertion === 'notThrows') { + 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')}`; + } else if (assertion === 'snapshot') { + const name = error.improperUsage.name; + const snapPath = error.improperUsage.snapPath; + + if (name === 'ChecksumError') { + return `The snapshot file is corrupted. + +File path: ${chalk.yellow(snapPath)} + +Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to recreate it.`; + } + + if (name === 'LegacyError') { + return `The snapshot file was created with AVA 0.19. It's not supported by this AVA version. + +File path: ${chalk.yellow(snapPath)} + +Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrade.`; + } + + if (name === 'VersionMismatchError') { + const snapVersion = error.improperUsage.snapVersion; + const expectedVersion = error.improperUsage.expectedVersion; + const upgradeMessage = snapVersion < expectedVersion ? + `Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrade.` : + 'You should upgrade AVA.'; + + return `The snapshot file is v${snapVersion}, but only v${expectedVersion} is supported. + +File path: ${chalk.yellow(snapPath)} + +${upgradeMessage}`; + } + } + + return null; }; diff --git a/node_modules/ava/lib/reporters/mini.js b/node_modules/ava/lib/reporters/mini.js index df481a76a..8acfab8e7 100644 --- a/node_modules/ava/lib/reporters/mini.js +++ b/node_modules/ava/lib/reporters/mini.js @@ -8,33 +8,14 @@ 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 ansiEscapes = require('ansi-escapes'); +const trimOffNewlines = require('trim-off-newlines'); const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); +const formatSerializedError = require('./format-serialized-error'); 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); @@ -151,38 +132,37 @@ class MiniReporter { time = chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); } - let status = this.reportCounts(time); + let status = this.reportCounts(time) + '\n'; if (this.rejectionCount > 0) { - status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount)); + status += ' ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount)) + '\n'; } if (this.exceptionCount > 0) { - status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount)); + status += ' ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount)) + '\n'; } if (runStatus.previousFailCount > 0) { - status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun'); + status += ' ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun') + '\n'; } if (this.knownFailureCount > 0) { for (const test of runStatus.knownFailures) { const title = test.title; - status += '\n\n ' + colors.title(title); + status += '\n ' + colors.title(title) + '\n'; // TODO: Output description with link // status += colors.stack(description); } } + status += '\n'; if (this.failCount > 0) { - runStatus.errors.forEach((test, index) => { + runStatus.errors.forEach(test => { if (!test.error) { return; } - const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n'; - - status += beforeSpacing + ' ' + colors.title(test.title) + '\n'; + status += ' ' + colors.title(test.title) + '\n'; if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; @@ -192,28 +172,32 @@ class MiniReporter { } } - 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 result = formatSerializedError(test.error); + if (result.printMessage) { + status += '\n' + indentString(test.error.message, 2) + '\n'; + } + + if (result.formatted) { + status += '\n' + indentString(result.formatted, 2) + '\n'; } const message = improperUsageMessages.forError(test.error); if (message) { status += '\n' + indentString(message, 2) + '\n'; } + } else if (test.error.message) { + status += '\n' + indentString(test.error.message, 2) + '\n'; } if (test.error.stack) { const extracted = extractStack(test.error.stack); if (extracted.includes('\n')) { - status += '\n' + indentString(colors.errorStack(extracted), 2); + status += '\n' + indentString(colors.errorStack(extracted), 2) + '\n'; } } + + status += '\n\n\n'; }); } @@ -225,7 +209,7 @@ class MiniReporter { } if (err.type === 'exception' && err.name === 'AvaError') { - status += '\n\n ' + colors.error(cross + ' ' + err.message); + status += ' ' + colors.error(cross + ' ' + err.message) + '\n\n'; } else { const title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception'; let description = err.stack ? err.stack.trimRight() : JSON.stringify(err); @@ -233,23 +217,23 @@ class MiniReporter { 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.title(title) + '\n'; status += ' ' + colors.stack(errorTitle) + '\n'; - status += colors.errorStack(errorStack); + status += colors.errorStack(errorStack) + '\n\n'; } }); } 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); + status += ' ' + colors.information('`--fail-fast` is on. ' + remaining) + '\n\n'; } 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'); + status += ' ' + 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'; + return '\n' + trimOffNewlines(status) + '\n'; } section() { return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); @@ -284,7 +268,7 @@ class MiniReporter { } // Erase the existing status message, plus the last log line. - str += eraseLines(ct); + str += ansiEscapes.eraseLines(ct); // Rewrite the last log line. str += lastLine; diff --git a/node_modules/ava/lib/reporters/verbose.js b/node_modules/ava/lib/reporters/verbose.js index 1be43ce5e..cd47683e8 100644 --- a/node_modules/ava/lib/reporters/verbose.js +++ b/node_modules/ava/lib/reporters/verbose.js @@ -4,10 +4,11 @@ const prettyMs = require('pretty-ms'); const figures = require('figures'); const chalk = require('chalk'); const plur = require('plur'); -const formatAssertError = require('../format-assert-error'); +const trimOffNewlines = require('trim-off-newlines'); const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); +const formatSerializedError = require('./format-serialized-error'); const improperUsageMessages = require('./improper-usage-messages'); class VerboseReporter { @@ -70,7 +71,7 @@ class VerboseReporter { return output; } finish(runStatus) { - let output = '\n'; + let output = ''; const lines = [ runStatus.failCount > 0 ? @@ -86,23 +87,23 @@ class VerboseReporter { if (lines.length > 0) { lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); - output += lines.join('\n'); + output += lines.join('\n') + '\n'; } if (runStatus.knownFailureCount > 0) { runStatus.knownFailures.forEach(test => { - output += '\n\n\n ' + colors.error(test.title); + output += '\n\n ' + colors.error(test.title) + '\n'; }); } + output += '\n'; if (runStatus.failCount > 0) { - runStatus.tests.forEach((test, index) => { + runStatus.tests.forEach(test => { if (!test.error) { return; } - const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n'; - output += beforeSpacing + ' ' + colors.title(test.title) + '\n'; + output += ' ' + colors.title(test.title) + '\n'; if (test.error.source) { output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; @@ -112,41 +113,45 @@ class VerboseReporter { } } - 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 result = formatSerializedError(test.error); + if (result.printMessage) { + output += '\n' + indentString(test.error.message, 2) + '\n'; + } + + if (result.formatted) { + output += '\n' + indentString(result.formatted, 2) + '\n'; } const message = improperUsageMessages.forError(test.error); if (message) { output += '\n' + indentString(message, 2) + '\n'; } + } else if (test.error.message) { + output += '\n' + indentString(test.error.message, 2) + '\n'; } if (test.error.stack) { const extracted = extractStack(test.error.stack); if (extracted.includes('\n')) { - output += '\n' + indentString(colors.errorStack(extracted), 2); + output += '\n' + indentString(colors.errorStack(extracted), 2) + '\n'; } } + + output += '\n\n\n'; }); } 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); + output += ' ' + colors.information('`--fail-fast` is on. ' + remaining) + '\n\n'; } 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'); + output += ' ' + 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'; + return '\n' + trimOffNewlines(output) + '\n'; } section() { return chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); diff --git a/node_modules/ava/lib/run-status.js b/node_modules/ava/lib/run-status.js index 6526f7bdc..8e095655a 100644 --- a/node_modules/ava/lib/run-status.js +++ b/node_modules/ava/lib/run-status.js @@ -40,6 +40,7 @@ class RunStatus extends EventEmitter { this.stats = []; this.tests = []; this.failFastEnabled = opts.failFast || false; + this.updateSnapshots = opts.updateSnapshots || false; autoBind(this); } @@ -73,6 +74,7 @@ class RunStatus extends EventEmitter { } handleTeardown(data) { this.emit('dependencies', data.file, data.dependencies, this); + this.emit('touchedFiles', data.touchedFiles); } handleStats(stats) { this.emit('stats', stats, this); diff --git a/node_modules/ava/lib/runner.js b/node_modules/ava/lib/runner.js index 5f0edacb2..bda2132fd 100644 --- a/node_modules/ava/lib/runner.js +++ b/node_modules/ava/lib/runner.js @@ -2,9 +2,9 @@ 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 snapshotManager = require('./snapshot-manager'); const TestCollection = require('./test-collection'); const validateTest = require('./validate-test'); @@ -49,16 +49,17 @@ class Runner extends EventEmitter { this.file = options.file; this.match = options.match || []; + this.projectDir = options.projectDir; this.serial = options.serial; this.updateSnapshots = options.updateSnapshots; this.hasStarted = false; this.results = []; - this.snapshotState = null; + this.snapshots = null; this.tests = new TestCollection({ bail: options.bail, failWithoutAssertions: options.failWithoutAssertions, - getSnapshotState: () => this.getSnapshotState() + compareTestSnapshot: this.compareTestSnapshot.bind(this) }); this.chain = optionChain(chainableMethods, (opts, args) => { @@ -179,26 +180,31 @@ class Runner extends EventEmitter { return stats; } - getSnapshotState() { - if (this.snapshotState) { - return this.snapshotState; + compareTestSnapshot(options) { + if (!this.snapshots) { + this.snapshots = snapshotManager.load({ + name: path.basename(this.file), + projectDir: this.projectDir, + relFile: path.relative(this.projectDir, this.file), + testDir: path.dirname(this.file), + updating: this.updateSnapshots + }); + this.emit('dependency', this.snapshots.snapPath); } - 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; + return this.snapshots.compare(options); } saveSnapshotState() { - if (this.snapshotState) { - this.snapshotState.save(this.updateSnapshots); + if (this.snapshots) { + const files = this.snapshots.save(); + if (files) { + this.emit('touched', files); + } + } else if (this.updateSnapshots) { + // TODO: There may be unused snapshot files if no test caused the + // snapshots to be loaded. Prune them. But not if tests (including hooks!) + // were skipped. Perhaps emit a warning if this occurs? } } diff --git a/node_modules/ava/lib/snapshot-manager.js b/node_modules/ava/lib/snapshot-manager.js new file mode 100644 index 000000000..ea1246585 --- /dev/null +++ b/node_modules/ava/lib/snapshot-manager.js @@ -0,0 +1,396 @@ +'use strict'; + +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); + +const writeFileAtomic = require('@ava/write-file-atomic'); +const concordance = require('concordance'); +const indentString = require('indent-string'); +const makeDir = require('make-dir'); +const md5Hex = require('md5-hex'); +const Buffer = require('safe-buffer').Buffer; + +const concordanceOptions = require('./concordance-options').snapshotManager; + +// Increment if encoding layout or Concordance serialization versions change. Previous AVA versions will not be able to +// decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself. +// The version is encoded as an unsigned 16 bit integer. +const VERSION = 1; + +const VERSION_HEADER = Buffer.alloc(2); +VERSION_HEADER.writeUInt16LE(VERSION); + +// The decoder matches on the trailing newline byte (0x0A). +const READABLE_PREFIX = Buffer.from(`AVA Snapshot v${VERSION}\n`, 'ascii'); +const REPORT_SEPARATOR = Buffer.from('\n\n', 'ascii'); +const REPORT_TRAILING_NEWLINE = Buffer.from('\n', 'ascii'); + +const MD5_HASH_LENGTH = 16; + +class SnapshotError extends Error { + constructor(message, snapPath) { + super(message); + this.name = 'SnapshotError'; + this.snapPath = snapPath; + } +} +exports.SnapshotError = SnapshotError; + +class ChecksumError extends SnapshotError { + constructor(snapPath) { + super('Checksum mismatch', snapPath); + this.name = 'ChecksumError'; + } +} +exports.ChecksumError = ChecksumError; + +class VersionMismatchError extends SnapshotError { + constructor(snapPath, version) { + super('Unexpected snapshot version', snapPath); + this.name = 'VersionMismatchError'; + this.snapVersion = version; + this.expectedVersion = VERSION; + } +} +exports.VersionMismatchError = VersionMismatchError; + +const LEGACY_SNAPSHOT_HEADER = Buffer.from('// Jest Snapshot v1'); +function isLegacySnapshot(buffer) { + return LEGACY_SNAPSHOT_HEADER.equals(buffer.slice(0, LEGACY_SNAPSHOT_HEADER.byteLength)); +} + +class LegacyError extends SnapshotError { + constructor(snapPath) { + super('Legacy snapshot file', snapPath); + this.name = 'LegacyError'; + } +} +exports.LegacyError = LegacyError; + +function tryRead(file) { + try { + return fs.readFileSync(file); + } catch (err) { + if (err.code === 'ENOENT') { + return null; + } + + throw err; + } +} + +function withoutLineEndings(buffer) { + let newLength = buffer.byteLength - 1; + while (buffer[newLength] === 0x0A || buffer[newLength] === 0x0D) { + newLength--; + } + return buffer.slice(0, newLength); +} + +function formatEntry(label, descriptor) { + if (label) { + label = `> ${label}\n\n`; + } + const codeBlock = indentString(concordance.formatDescriptor(descriptor, concordanceOptions), 4); + return Buffer.from(label + codeBlock, 'utf8'); +} + +function combineEntries(entries) { + const buffers = []; + let byteLength = 0; + + const sortedKeys = Array.from(entries.keys()).sort(); + for (const key of sortedKeys) { + const keyBuffer = Buffer.from(`\n\n## ${key}\n\n`, 'utf8'); + buffers.push(keyBuffer); + byteLength += keyBuffer.byteLength; + + const formattedEntries = entries.get(key); + const last = formattedEntries[formattedEntries.length - 1]; + for (const entry of formattedEntries) { + buffers.push(entry); + byteLength += entry.byteLength; + + if (entry !== last) { + buffers.push(REPORT_SEPARATOR); + byteLength += REPORT_SEPARATOR.byteLength; + } + } + } + + return {buffers, byteLength}; +} + +function generateReport(relFile, snapFile, entries) { + const combined = combineEntries(entries); + const buffers = combined.buffers; + let byteLength = combined.byteLength; + + const header = Buffer.from(`# Snapshot report for \`${relFile}\` + +The actual snapshot is saved in \`${snapFile}\`. + +Generated by [AVA](https://ava.li).`, 'utf8'); + buffers.unshift(header); + byteLength += header.byteLength; + + buffers.push(REPORT_TRAILING_NEWLINE); + byteLength += REPORT_TRAILING_NEWLINE.byteLength; + return Buffer.concat(buffers, byteLength); +} + +function appendReportEntries(existingReport, entries) { + const combined = combineEntries(entries); + const buffers = combined.buffers; + let byteLength = combined.byteLength; + + const prepend = withoutLineEndings(existingReport); + buffers.unshift(prepend); + byteLength += prepend.byteLength; + + return Buffer.concat(buffers, byteLength); +} + +function encodeSnapshots(buffersByHash) { + const buffers = []; + let byteOffset = 0; + + // Entry start and end pointers are relative to the header length. This means + // it's possible to append new entries to an existing snapshot file, without + // having to rewrite pointers for existing entries. + const headerLength = Buffer.alloc(4); + buffers.push(headerLength); + byteOffset += 4; + + // Allows 65535 hashes (tests or identified snapshots) per file. + const numHashes = Buffer.alloc(2); + numHashes.writeUInt16LE(buffersByHash.size); + buffers.push(numHashes); + byteOffset += 2; + + const entries = []; + for (const pair of buffersByHash) { + const hash = pair[0]; + const snapshotBuffers = pair[1]; + + buffers.push(Buffer.from(hash, 'hex')); + byteOffset += MD5_HASH_LENGTH; + + // Allows 65535 snapshots per hash. + const numSnapshots = Buffer.alloc(2); + numSnapshots.writeUInt16LE(snapshotBuffers.length, 0); + buffers.push(numSnapshots); + byteOffset += 2; + + for (const value of snapshotBuffers) { + // Each pointer is 32 bits, restricting the total, uncompressed buffer to + // 4 GiB. + const start = Buffer.alloc(4); + const end = Buffer.alloc(4); + entries.push({start, end, value}); + + buffers.push(start, end); + byteOffset += 8; + } + } + + headerLength.writeUInt32LE(byteOffset, 0); + + let bodyOffset = 0; + for (const entry of entries) { + const start = bodyOffset; + const end = bodyOffset + entry.value.byteLength; + entry.start.writeUInt32LE(start, 0); + entry.end.writeUInt32LE(end, 0); + buffers.push(entry.value); + bodyOffset = end; + } + byteOffset += bodyOffset; + + const compressed = zlib.gzipSync(Buffer.concat(buffers, byteOffset)); + const md5sum = crypto.createHash('md5').update(compressed).digest(); + return Buffer.concat([ + READABLE_PREFIX, + VERSION_HEADER, + md5sum, + compressed + ], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength + MD5_HASH_LENGTH + compressed.byteLength); +} + +function decodeSnapshots(buffer, snapPath) { + if (isLegacySnapshot(buffer)) { + throw new LegacyError(snapPath); + } + + // The version starts after the readable prefix, which is ended by a newline + // byte (0x0A). + const versionOffset = buffer.indexOf(0x0A) + 1; + const version = buffer.readUInt16LE(versionOffset); + if (version !== VERSION) { + throw new VersionMismatchError(snapPath, version); + } + + const md5sumOffset = versionOffset + 2; + const compressedOffset = md5sumOffset + MD5_HASH_LENGTH; + const compressed = buffer.slice(compressedOffset); + + const md5sum = crypto.createHash('md5').update(compressed).digest(); + const expectedSum = buffer.slice(md5sumOffset, compressedOffset); + if (!md5sum.equals(expectedSum)) { + throw new ChecksumError(snapPath); + } + + const decompressed = zlib.gunzipSync(compressed); + let byteOffset = 0; + + const headerLength = decompressed.readUInt32LE(byteOffset); + byteOffset += 4; + + const snapshotsByHash = new Map(); + const numHashes = decompressed.readUInt16LE(byteOffset); + byteOffset += 2; + + for (let count = 0; count < numHashes; count++) { + const hash = decompressed.toString('hex', byteOffset, byteOffset + MD5_HASH_LENGTH); + byteOffset += MD5_HASH_LENGTH; + + const numSnapshots = decompressed.readUInt16LE(byteOffset); + byteOffset += 2; + + const snapshotsBuffers = new Array(numSnapshots); + for (let index = 0; index < numSnapshots; index++) { + const start = decompressed.readUInt32LE(byteOffset) + headerLength; + byteOffset += 4; + const end = decompressed.readUInt32LE(byteOffset) + headerLength; + byteOffset += 4; + snapshotsBuffers[index] = decompressed.slice(start, end); + } + + // Allow for new entries to be appended to an existing header, which could + // lead to the same hash being present multiple times. + if (snapshotsByHash.has(hash)) { + snapshotsByHash.set(hash, snapshotsByHash.get(hash).concat(snapshotsBuffers)); + } else { + snapshotsByHash.set(hash, snapshotsBuffers); + } + } + + return snapshotsByHash; +} + +class Manager { + constructor(options) { + this.appendOnly = options.appendOnly; + this.dir = options.dir; + this.relFile = options.relFile; + this.reportFile = options.reportFile; + this.snapFile = options.snapFile; + this.snapPath = options.snapPath; + this.snapshotsByHash = options.snapshotsByHash; + + this.hasChanges = false; + this.reportEntries = new Map(); + } + + compare(options) { + const hash = md5Hex(options.belongsTo); + const entries = this.snapshotsByHash.get(hash) || []; + if (options.index > entries.length) { + throw new RangeError(`Cannot record snapshot ${options.index} for ${JSON.stringify(options.belongsTo)}, exceeds expected index of ${entries.length}`); + } + if (options.index === entries.length) { + this.record(hash, options); + return {pass: true}; + } + + const snapshotBuffer = entries[options.index]; + const actual = concordance.deserialize(snapshotBuffer, concordanceOptions); + + const expected = concordance.describe(options.expected, concordanceOptions); + const pass = concordance.compareDescriptors(actual, expected); + + return {actual, expected, pass}; + } + + record(hash, options) { + const descriptor = concordance.describe(options.expected, concordanceOptions); + + this.hasChanges = true; + const snapshot = concordance.serialize(descriptor); + if (this.snapshotsByHash.has(hash)) { + this.snapshotsByHash.get(hash).push(snapshot); + } else { + this.snapshotsByHash.set(hash, [snapshot]); + } + + const entry = formatEntry(options.label, descriptor); + if (this.reportEntries.has(options.belongsTo)) { + this.reportEntries.get(options.belongsTo).push(entry); + } else { + this.reportEntries.set(options.belongsTo, [entry]); + } + } + + save() { + if (!this.hasChanges) { + return null; + } + + const snapPath = this.snapPath; + const buffer = encodeSnapshots(this.snapshotsByHash); + + const reportPath = path.join(this.dir, this.reportFile); + const existingReport = this.appendOnly ? tryRead(reportPath) : null; + const reportBuffer = existingReport ? + appendReportEntries(existingReport, this.reportEntries) : + generateReport(this.relFile, this.snapFile, this.reportEntries); + + makeDir.sync(this.dir); + const tmpSnapPath = writeFileAtomic.sync(snapPath, buffer); + const tmpReportPath = writeFileAtomic.sync(reportPath, reportBuffer); + + return [tmpSnapPath, tmpReportPath, snapPath, reportPath]; + } +} + +function determineSnapshotDir(projectDir, testDir) { + const parts = new Set(path.relative(projectDir, testDir).split(path.sep)); + if (parts.has('__tests__')) { + return path.join(testDir, '__snapshots__'); + } else if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns + return path.join(testDir, 'snapshots'); + } + return testDir; +} + +function load(options) { + const dir = determineSnapshotDir(options.projectDir, options.testDir); + const reportFile = `${options.name}.md`; + const snapFile = `${options.name}.snap`; + const snapPath = path.join(dir, snapFile); + + let appendOnly = !options.updating; + let snapshotsByHash; + + if (!options.updating) { + const buffer = tryRead(snapPath); + if (buffer) { + snapshotsByHash = decodeSnapshots(buffer, snapPath); + } else { + appendOnly = false; + } + } + + return new Manager({ + appendOnly, + dir, + relFile: options.relFile, + reportFile, + snapFile, + snapPath, + snapshotsByHash: snapshotsByHash || new Map() + }); +} +exports.load = load; diff --git a/node_modules/ava/lib/test-collection.js b/node_modules/ava/lib/test-collection.js index 5404cb119..91c604e06 100644 --- a/node_modules/ava/lib/test-collection.js +++ b/node_modules/ava/lib/test-collection.js @@ -11,7 +11,7 @@ class TestCollection extends EventEmitter { this.bail = options.bail; this.failWithoutAssertions = options.failWithoutAssertions; - this.getSnapshotState = options.getSnapshotState; + this.compareTestSnapshot = options.compareTestSnapshot; this.hasExclusive = false; this.testCount = 0; @@ -133,7 +133,7 @@ class TestCollection extends EventEmitter { contextRef, failWithoutAssertions: false, fn: hook.fn, - getSnapshotState: this.getSnapshotState, + compareTestSnapshot: this.compareTestSnapshot, metadata: hook.metadata, onResult: this._emitTestResult, title @@ -150,7 +150,7 @@ class TestCollection extends EventEmitter { contextRef, failWithoutAssertions: this.failWithoutAssertions, fn: test.fn, - getSnapshotState: this.getSnapshotState, + compareTestSnapshot: this.compareTestSnapshot, metadata: test.metadata, onResult: this._emitTestResult, title: test.title diff --git a/node_modules/ava/lib/test-worker.js b/node_modules/ava/lib/test-worker.js index 2df7f745d..0061775f0 100644 --- a/node_modules/ava/lib/test-worker.js +++ b/node_modules/ava/lib/test-worker.js @@ -17,18 +17,18 @@ } } -/* 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; +/* eslint-enable import/order */ +const Bluebird = require('bluebird'); +const currentlyUnhandled = require('currently-unhandled')(); +const isObj = require('is-obj'); +const serializeError = require('./serialize-error'); + // Bluebird specific Bluebird.longStackTraces(); @@ -37,16 +37,28 @@ Bluebird.longStackTraces(); adapter.installSourceMapSupport(); adapter.installPrecompilerHook(); -const dependencies = []; +const testPath = opts.file; + +const dependencies = new Set(); adapter.installDependencyTracking(dependencies, testPath); +const touchedFiles = new Set(); + // Set when main.js is required (since test files should have `require('ava')`). let runner = null; exports.setRunner = newRunner => { runner = newRunner; + runner.on('dependency', file => { + dependencies.add(file); + }); + runner.on('touched', files => { + for (const file of files) { + touchedFiles.add(file); + } + }); }; -require(testPath); // eslint-disable-line import/no-dynamic-require +require(testPath); // If AVA was not required, show an error if (!runner) { @@ -121,8 +133,12 @@ process.on('ava-teardown', () => { // 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}); + // it flat out crashes. Also include any files that AVA touched during the + // test run. This allows the watcher to ignore modifications to those files. + adapter.send('teardown', { + dependencies: Array.from(dependencies), + touchedFiles: Array.from(touchedFiles) + }); }); process.on('ava-exit', () => { diff --git a/node_modules/ava/lib/test.js b/node_modules/ava/lib/test.js index a9b0fb1d9..58be54d32 100644 --- a/node_modules/ava/lib/test.js +++ b/node_modules/ava/lib/test.js @@ -1,13 +1,19 @@ 'use strict'; const isGeneratorFn = require('is-generator-fn'); const co = require('co-with-promise'); +const concordance = require('concordance'); 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'); +const concordanceOptions = require('./concordance-options').default; + +function formatErrorValue(label, error) { + const formatted = concordance.format(error, concordanceOptions); + return {label, formatted}; +} class SkipApi { constructor(test) { @@ -26,8 +32,10 @@ const captureStack = start => { class ExecutionContext { constructor(test) { - this._test = test; - this.skip = new SkipApi(test); + Object.defineProperties(this, { + _test: {value: test}, + skip: {value: new SkipApi(test)} + }); } plan(ct) { @@ -67,7 +75,6 @@ class ExecutionContext { this._test.trackThrows(null); } } -Object.defineProperty(ExecutionContext.prototype, 'context', {enumerable: true}); { const assertions = assert.wrapAssertions({ @@ -98,11 +105,19 @@ class Test { 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.snapshotInvocationCount = 0; + this.compareWithSnapshot = assertionOptions => { + const belongsTo = assertionOptions.id || this.title; + const expected = assertionOptions.expected; + const index = assertionOptions.id ? 0 : this.snapshotInvocationCount++; + const label = assertionOptions.id ? '' : assertionOptions.message || `Snapshot ${this.snapshotInvocationCount}`; + return options.compareTestSnapshot({belongsTo, expected, index, label}); + }; + this.assertCount = 0; this.assertError = undefined; this.calledEnd = false; @@ -139,7 +154,7 @@ class Test { actual: err, message: 'Callback called with an error', stack, - values: [formatAssertError.formatWithLabel('Error:', err)] + values: [formatErrorValue('Callback called with an error:', err)] })); } @@ -234,7 +249,7 @@ class Test { const values = []; if (err) { - values.push(formatAssertError.formatWithLabel(`The following error was thrown, possibly before \`t.${pending.assertion}()\` could be called:`, err)); + values.push(formatErrorValue(`The following error was thrown, possibly before \`t.${pending.assertion}()\` could be called:`, err)); } this.saveFirstError(new assert.AssertionError({ @@ -297,7 +312,7 @@ class Test { this.saveFirstError(new assert.AssertionError({ message: 'Error thrown in test', stack: result.error instanceof Error && result.error.stack, - values: [formatAssertError.formatWithLabel('Error:', result.error)] + values: [formatErrorValue('Error thrown in test:', result.error)] })); } return this.finish(); @@ -361,7 +376,7 @@ class Test { this.saveFirstError(new assert.AssertionError({ message: 'Rejected promise returned by test', stack: err instanceof Error && err.stack, - values: [formatAssertError.formatWithLabel('Rejection reason:', err)] + values: [formatErrorValue('Rejected promise returned by test. Reason:', err)] })); } }) diff --git a/node_modules/ava/lib/watcher.js b/node_modules/ava/lib/watcher.js index 3d7094ffb..c90c810f0 100644 --- a/node_modules/ava/lib/watcher.js +++ b/node_modules/ava/lib/watcher.js @@ -16,18 +16,23 @@ function rethrowAsync(err) { }); } +const MIN_DEBOUNCE_DELAY = 10; +const INITIAL_DEBOUNCE_DELAY = 100; + class Debouncer { constructor(watcher) { this.watcher = watcher; this.timer = null; this.repeat = false; } - debounce() { + debounce(delay) { if (this.timer) { this.again = true; return; } + delay = delay ? Math.max(delay, MIN_DEBOUNCE_DELAY) : INITIAL_DEBOUNCE_DELAY; + const timer = setTimeout(() => { this.watcher.busy.then(() => { // Do nothing if debouncing was canceled while waiting for the busy @@ -39,14 +44,14 @@ class Debouncer { if (this.again) { this.timer = null; this.again = false; - this.debounce(); + this.debounce(delay / 2); } else { this.watcher.runAfterChanges(); this.timer = null; this.again = false; } }); - }, 10); + }, delay); this.timer = timer; } @@ -79,7 +84,8 @@ class Watcher { this.clearLogOnNextRun = true; this.runVector = 0; - this.run = specificFiles => { + this.previousFiles = files; + this.run = (specificFiles, updateSnapshots) => { if (this.runVector > 0) { const cleared = this.clearLogOnNextRun && logger.clear(); if (!cleared) { @@ -111,7 +117,9 @@ class Watcher { } } - this.busy = api.run(specificFiles || files, {runOnlyExclusive}) + this.touchedFiles.clear(); + this.previousFiles = specificFiles || files; + this.busy = api.run(this.previousFiles, {runOnlyExclusive, updateSnapshots: updateSnapshots === true}) .then(runStatus => { runStatus.previousFailCount = this.sumPreviousFailures(currentVector); logger.finish(runStatus); @@ -125,6 +133,9 @@ class Watcher { this.testDependencies = []; this.trackTestDependencies(api, sources); + this.touchedFiles = new Set(); + this.trackTouchedFiles(api); + this.filesWithExclusiveTests = []; this.trackExclusivity(api); @@ -179,6 +190,15 @@ class Watcher { this.testDependencies.push(new TestDependency(file, sources)); } } + trackTouchedFiles(api) { + api.on('test-run', runStatus => { + runStatus.on('touchedFiles', files => { + for (const file of files) { + this.touchedFiles.add(nodePath.relative(process.cwd(), file)); + } + }); + }); + } trackExclusivity(api) { api.on('stats', stats => { this.updateExclusivity(stats.file, stats.hasExclusive); @@ -255,7 +275,7 @@ class Watcher { stdin.on('data', data => { data = data.trim().toLowerCase(); - if (data !== 'r' && data !== 'rs') { + if (data !== 'r' && data !== 'rs' && data !== 'u') { return; } @@ -267,7 +287,11 @@ class Watcher { // the busy promise to fulfil this.debouncer.cancel(); this.clearLogOnNextRun = false; - this.rerunAll(); + if (data === 'u') { + this.updatePreviousSnapshots(); + } else { + this.rerunAll(); + } }); }); } @@ -275,11 +299,22 @@ class Watcher { this.dirtyStates = {}; this.run(); } + updatePreviousSnapshots() { + this.dirtyStates = {}; + this.run(this.previousFiles, true); + } runAfterChanges() { const dirtyStates = this.dirtyStates; this.dirtyStates = {}; - const dirtyPaths = Object.keys(dirtyStates); + const dirtyPaths = Object.keys(dirtyStates).filter(path => { + if (this.touchedFiles.has(path)) { + debug('Ignoring known touched file %s', path); + this.touchedFiles.delete(path); + return false; + } + return true; + }); const dirtyTests = dirtyPaths.filter(this.avaFiles.isTest); const dirtySources = diff(dirtyPaths, dirtyTests); const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink'); @@ -309,7 +344,8 @@ class Watcher { // 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'); + debug('Sources remain that cannot be traced to specific tests: %O', dirtySources); + debug('Rerunning all tests'); this.run(); return; } diff --git a/node_modules/ava/license b/node_modules/ava/license index 654d0bfe9..e7af2f771 100644 --- a/node_modules/ava/license +++ b/node_modules/ava/license @@ -1,21 +1,9 @@ -The MIT License (MIT) +MIT License Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ava/node_modules/.bin/import-local-fixture b/node_modules/ava/node_modules/.bin/import-local-fixture new file mode 120000 index 000000000..653f8d95a --- /dev/null +++ b/node_modules/ava/node_modules/.bin/import-local-fixture @@ -0,0 +1 @@ +../../../import-local/fixtures/cli.js
\ No newline at end of file diff --git a/node_modules/ava/node_modules/.bin/mkdirp b/node_modules/ava/node_modules/.bin/mkdirp deleted file mode 120000 index 91a5f623f..000000000 --- a/node_modules/ava/node_modules/.bin/mkdirp +++ /dev/null @@ -1 +0,0 @@ -../../../mkdirp/bin/cmd.js
\ No newline at end of file diff --git a/node_modules/ava/node_modules/ansi-regex/index.js b/node_modules/ava/node_modules/ansi-regex/index.js new file mode 100644 index 000000000..c4aaecf50 --- /dev/null +++ b/node_modules/ava/node_modules/ansi-regex/index.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = () => { + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' + ].join('|'); + + return new RegExp(pattern, 'g'); +}; diff --git a/node_modules/ava/node_modules/ansi-regex/license b/node_modules/ava/node_modules/ansi-regex/license new file mode 100644 index 000000000..e7af2f771 --- /dev/null +++ b/node_modules/ava/node_modules/ansi-regex/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ava/node_modules/ansi-regex/package.json b/node_modules/ava/node_modules/ansi-regex/package.json new file mode 100644 index 000000000..e94852fd7 --- /dev/null +++ b/node_modules/ava/node_modules/ansi-regex/package.json @@ -0,0 +1,53 @@ +{ + "name": "ansi-regex", + "version": "3.0.0", + "description": "Regular expression for matching ANSI escape codes", + "license": "MIT", + "repository": "chalk/ansi-regex", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && ava", + "view-supported": "node fixtures/view-codes.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "ansi", + "styles", + "color", + "colour", + "colors", + "terminal", + "console", + "cli", + "string", + "tty", + "escape", + "formatting", + "rgb", + "256", + "shell", + "xterm", + "command-line", + "text", + "regex", + "regexp", + "re", + "match", + "test", + "find", + "pattern" + ], + "devDependencies": { + "ava": "*", + "xo": "*" + } +} diff --git a/node_modules/ava/node_modules/ansi-regex/readme.md b/node_modules/ava/node_modules/ansi-regex/readme.md new file mode 100644 index 000000000..22db1c340 --- /dev/null +++ b/node_modules/ava/node_modules/ansi-regex/readme.md @@ -0,0 +1,46 @@ +# ansi-regex [](https://travis-ci.org/chalk/ansi-regex) + +> Regular expression for matching [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) + + +## Install + +``` +$ npm install ansi-regex +``` + + +## Usage + +```js +const ansiRegex = require('ansi-regex'); + +ansiRegex().test('\u001B[4mcake\u001B[0m'); +//=> true + +ansiRegex().test('cake'); +//=> false + +'\u001B[4mcake\u001B[0m'.match(ansiRegex()); +//=> ['\u001B[4m', '\u001B[0m'] +``` + + +## FAQ + +### Why do you test for codes not in the ECMA 48 standard? + +Some of the codes we run as a test are codes that we acquired finding various lists of non-standard or manufacturer specific codes. We test for both standard and non-standard codes, as most of them follow the same or similar format and can be safely matched in strings without the risk of removing actual string content. There are a few non-standard control codes that do not follow the traditional format (i.e. they end in numbers) thus forcing us to exclude them from the test because we cannot reliably match them. + +On the historical side, those ECMA standards were established in the early 90's whereas the VT100, for example, was designed in the mid/late 70's. At that point in time, control codes were still pretty ungoverned and engineers used them for a multitude of things, namely to activate hardware ports that may have been proprietary. Somewhere else you see a similar 'anarchy' of codes is in the x86 architecture for processors; there are a ton of "interrupts" that can mean different things on certain brands of processors, most of which have been phased out. + + +## Maintainers + +- [Sindre Sorhus](https://github.com/sindresorhus) +- [Josh Junon](https://github.com/qix-) + + +## License + +MIT diff --git a/node_modules/ava/node_modules/chalk/index.js b/node_modules/ava/node_modules/chalk/index.js new file mode 100644 index 000000000..4c81d6d20 --- /dev/null +++ b/node_modules/ava/node_modules/chalk/index.js @@ -0,0 +1,220 @@ +'use strict'; +const escapeStringRegexp = require('escape-string-regexp'); +const ansiStyles = require('ansi-styles'); +const supportsColor = require('supports-color'); + +const template = require('./templates.js'); + +const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; + +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); + +const styles = Object.create(null); + +function applyOptions(obj, options) { + options = options || {}; + + // Detect level if not set manually + const scLevel = supportsColor ? supportsColor.level : 0; + obj.level = options.level === undefined ? scLevel : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; +} + +function Chalk(options) { + // We check for this.template here since calling `chalk.constructor()` + // by itself will have a `this` of a previously constructed chalk object + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); + + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; + + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + + chalk.template.constructor = Chalk; + + return chalk.template; + } + + applyOptions(this, options); +} + +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} + +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], key); + } + }; +} + +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } + + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); + }; + } + }; +} + +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } + + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], model); + }; + } + }; +} + +const proto = Object.defineProperties(() => {}, styles); + +function build(_styles, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; + + builder._styles = _styles; + + const self = this; + + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); + + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); + + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype + builder.__proto__ = proto; // eslint-disable-line no-proto + + return builder; +} + +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = String(arguments[0]); + + if (argsLen === 0) { + return ''; + } + + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!this.enabled || this.level <= 0 || !str) { + return str; + } + + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } + + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; + + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } + + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; + + return str; +} + +function chalkTag(chalk, strings) { + if (!Array.isArray(strings)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return [].slice.call(arguments, 1).join(' '); + } + + const args = [].slice.call(arguments, 2); + const parts = [strings.raw[0]]; + + for (let i = 1; i < strings.length; i++) { + parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); + parts.push(String(strings.raw[i])); + } + + return template(chalk, parts.join('')); +} + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = supportsColor; diff --git a/node_modules/ava/node_modules/chalk/license b/node_modules/ava/node_modules/chalk/license new file mode 100644 index 000000000..e7af2f771 --- /dev/null +++ b/node_modules/ava/node_modules/chalk/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ava/node_modules/chalk/package.json b/node_modules/ava/node_modules/chalk/package.json new file mode 100644 index 000000000..a25712701 --- /dev/null +++ b/node_modules/ava/node_modules/chalk/package.json @@ -0,0 +1,63 @@ +{ + "name": "chalk", + "version": "2.1.0", + "description": "Terminal string styling done right", + "license": "MIT", + "repository": "chalk/chalk", + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && nyc ava", + "bench": "matcha benchmark.js", + "coveralls": "nyc report --reporter=text-lcov | coveralls" + }, + "files": [ + "index.js", + "templates.js" + ], + "keywords": [ + "color", + "colour", + "colors", + "terminal", + "console", + "cli", + "string", + "str", + "ansi", + "style", + "styles", + "tty", + "formatting", + "rgb", + "256", + "shell", + "xterm", + "log", + "logging", + "command-line", + "text" + ], + "dependencies": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, + "devDependencies": { + "ava": "*", + "coveralls": "^2.11.2", + "execa": "^0.7.0", + "import-fresh": "^2.0.0", + "matcha": "^0.7.0", + "nyc": "^11.0.2", + "resolve-from": "^3.0.0", + "xo": "*" + }, + "xo": { + "envs": [ + "node", + "mocha" + ] + } +} diff --git a/node_modules/ava/node_modules/chalk/readme.md b/node_modules/ava/node_modules/chalk/readme.md new file mode 100644 index 000000000..dfcfdf25d --- /dev/null +++ b/node_modules/ava/node_modules/chalk/readme.md @@ -0,0 +1,306 @@ +<h1 align="center"> + <br> + <br> + <img width="320" src="https://cdn.rawgit.com/chalk/chalk/19935d6484811c5e468817f846b7b3d417d7bf4a/logo.svg" alt="chalk"> + <br> + <br> + <br> +</h1> + +> Terminal string styling done right + +[](https://travis-ci.org/chalk/chalk) [](https://coveralls.io/github/chalk/chalk?branch=master) [](https://www.youtube.com/watch?v=9auOCbH5Ns4) [](https://github.com/sindresorhus/xo) + +### [See what's new in Chalk 2](https://github.com/chalk/chalk/releases/tag/v2.0.0) + + + + +## Highlights + +- Expressive API +- Highly performant +- Ability to nest styles +- [256/Truecolor color support](#256-and-truecolor-color-support) +- Auto-detects color support +- Doesn't extend `String.prototype` +- Clean and focused +- Actively maintained +- [Used by ~17,000 packages](https://www.npmjs.com/browse/depended/chalk) as of June 20th, 2017 + + +## Install + +```console +$ npm install chalk +``` + + +## Usage + +```js +const chalk = require('chalk'); + +console.log(chalk.blue('Hello world!')); +``` + +Chalk comes with an easy to use composable API where you just chain and nest the styles you want. + +```js +const chalk = require('chalk'); +const log = console.log; + +// Combine styled and normal strings +log(chalk.blue('Hello') + 'World' + chalk.red('!')); + +// Compose multiple styles using the chainable API +log(chalk.blue.bgRed.bold('Hello world!')); + +// Pass in multiple arguments +log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz')); + +// Nest styles +log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!')); + +// Nest styles of the same type even (color, underline, background) +log(chalk.green( + 'I am a green line ' + + chalk.blue.underline.bold('with a blue substring') + + ' that becomes green again!' +)); + +// ES2015 template literal +log(` +CPU: ${chalk.red('90%')} +RAM: ${chalk.green('40%')} +DISK: ${chalk.yellow('70%')} +`); + +// ES2015 tagged template literal +log(chalk` +CPU: {red ${cpu.totalPercent}%} +RAM: {green ${ram.used / ram.total * 100}%} +DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} +`); + +// Use RGB colors in terminal emulators that support it. +log(chalk.keyword('orange')('Yay for orange colored text!')); +log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); +log(chalk.hex('#DEADED').bold('Bold gray!')); +``` + +Easily define your own themes: + +```js +const chalk = require('chalk'); + +const error = chalk.bold.red; +const warning = chalk.keyword('orange'); + +console.log(error('Error!')); +console.log(warning('Warning!')); +``` + +Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args): + +```js +const name = 'Sindre'; +console.log(chalk.green('Hello %s'), name); +//=> 'Hello Sindre' +``` + + +## API + +### chalk.`<style>[.<style>...](string, [string...])` + +Example: `chalk.red.bold.underline('Hello', 'world');` + +Chain [styles](#styles) and call the last one as a method with a string argument. Order doesn't matter, and later styles take precedent in case of a conflict. This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`. + +Multiple arguments will be separated by space. + +### chalk.enabled + +Color support is automatically detected, as is the level (see `chalk.level`). However, if you'd like to simply enable/disable Chalk, you can do so via the `.enabled` property. + +Chalk is enabled by default unless expicitly disabled via the constructor or `chalk.level` is `0`. + +If you need to change this in a reusable module, create a new instance: + +```js +const ctx = new chalk.constructor({enabled: false}); +``` + +### chalk.level + +Color support is automatically detected, but you can override it by setting the `level` property. You should however only do this in your own code as it applies globally to all Chalk consumers. + +If you need to change this in a reusable module, create a new instance: + +```js +const ctx = new chalk.constructor({level: 0}); +``` + +Levels are as follows: + +0. All colors disabled +1. Basic color support (16 colors) +2. 256 color support +3. Truecolor support (16 million colors) + +### chalk.supportsColor + +Detect whether the terminal [supports color](https://github.com/chalk/supports-color). Used internally and handled for you, but exposed for convenience. + +Can be overridden by the user with the flags `--color` and `--no-color`. For situations where using `--color` is not possible, add the environment variable `FORCE_COLOR=1` to forcefully enable color or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks. + +Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=16m` flags, respectively. + + +## Styles + +### Modifiers + +- `reset` +- `bold` +- `dim` +- `italic` *(Not widely supported)* +- `underline` +- `inverse` +- `hidden` +- `strikethrough` *(Not widely supported)* + +### Colors + +- `black` +- `red` +- `green` +- `yellow` +- `blue` *(On Windows the bright version is used since normal blue is illegible)* +- `magenta` +- `cyan` +- `white` +- `gray` ("bright black") +- `redBright` +- `greenBright` +- `yellowBright` +- `blueBright` +- `magentaBright` +- `cyanBright` +- `whiteBright` + +### Background colors + +- `bgBlack` +- `bgRed` +- `bgGreen` +- `bgYellow` +- `bgBlue` +- `bgMagenta` +- `bgCyan` +- `bgWhite` +- `bgBlackBright` +- `bgRedBright` +- `bgGreenBright` +- `bgYellowBright` +- `bgBlueBright` +- `bgMagentaBright` +- `bgCyanBright` +- `bgWhiteBright` + + +## Tagged template literal + +Chalk can be used as a [tagged template literal](http://exploringjs.com/es6/ch_template-literals.html#_tagged-template-literals). + +```js +const chalk = require('chalk'); + +const miles = 18; +const calculateFeet = miles => miles * 5280; + +console.log(chalk` + There are {bold 5280 feet} in a mile. + In {bold ${miles} miles}, there are {green.bold ${calculateFeet(miles)} feet}. +`); +``` + +Blocks are delimited by an opening curly brace (`{`), a style, some content, and a closing curly brace (`}`). + +Template styles are chained exactly like normal Chalk styles. The following two statements are equivalent: + +```js +console.log(chalk.bold.rgb(10, 100, 200)('Hello!')); +console.log(chalk`{bold.rgb(10,100,200) Hello!}`); +``` + +Note that function styles (`rgb()`, `hsl()`, `keyword()`, etc.) may not contain spaces between parameters. + +All interpolated values (`` chalk`${foo}` ``) are converted to strings via the `.toString()` method. All curly braces (`{` and `}`) in interpolated value strings are escaped. + + +## 256 and Truecolor color support + +Chalk supports 256 colors and [Truecolor](https://gist.github.com/XVilka/8346728) (16 million colors) on supported terminal apps. + +Colors are downsampled from 16 million RGB values to an ANSI color format that is supported by the terminal emulator (or by specifying `{level: n}` as a Chalk option). For example, Chalk configured to run at level 1 (basic color support) will downsample an RGB value of #FF0000 (red) to 31 (ANSI escape for red). + +Examples: + +- `chalk.hex('#DEADED').underline('Hello, world!')` +- `chalk.keyword('orange')('Some orange text')` +- `chalk.rgb(15, 100, 204).inverse('Hello!')` + +Background versions of these models are prefixed with `bg` and the first level of the module capitalized (e.g. `keyword` for foreground colors and `bgKeyword` for background colors). + +- `chalk.bgHex('#DEADED').underline('Hello, world!')` +- `chalk.bgKeyword('orange')('Some orange text')` +- `chalk.bgRgb(15, 100, 204).inverse('Hello!')` + +The following color models can be used: + +- [`rgb`](https://en.wikipedia.org/wiki/RGB_color_model) - Example: `chalk.rgb(255, 136, 0).bold('Orange!')` +- [`hex`](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) - Example: `chalk.hex('#FF8800').bold('Orange!')` +- [`keyword`](https://www.w3.org/wiki/CSS/Properties/color/keywords) (CSS keywords) - Example: `chalk.keyword('orange').bold('Orange!')` +- [`hsl`](https://en.wikipedia.org/wiki/HSL_and_HSV) - Example: `chalk.hsl(32, 100, 50).bold('Orange!')` +- [`hsv`](https://en.wikipedia.org/wiki/HSL_and_HSV) - Example: `chalk.hsl(32, 1, 1).bold('Orange!')` +- [`hwb`](https://en.wikipedia.org/wiki/HWB_color_model) - Example: `chalk.hsl(32, 0, 50).bold('Orange!')` +- `ansi16` +- `ansi256` + + +## Windows + +If you're on Windows, do yourself a favor and use [`cmder`](http://cmder.net/) instead of `cmd.exe`. + + +## Origin story + +[colors.js](https://github.com/Marak/colors.js) used to be the most popular string styling module, but it has serious deficiencies like extending `String.prototype` which causes all kinds of [problems](https://github.com/yeoman/yo/issues/68) and the package is unmaintained. Although there are other packages, they either do too much or not enough. Chalk is a clean and focused alternative. + + +## Related + +- [chalk-cli](https://github.com/chalk/chalk-cli) - CLI for this module +- [ansi-styles](https://github.com/chalk/ansi-styles) - ANSI escape codes for styling strings in the terminal +- [supports-color](https://github.com/chalk/supports-color) - Detect whether a terminal supports color +- [strip-ansi](https://github.com/chalk/strip-ansi) - Strip ANSI escape codes +- [has-ansi](https://github.com/chalk/has-ansi) - Check if a string has ANSI escape codes +- [ansi-regex](https://github.com/chalk/ansi-regex) - Regular expression for matching ANSI escape codes +- [wrap-ansi](https://github.com/chalk/wrap-ansi) - Wordwrap a string with ANSI escape codes +- [slice-ansi](https://github.com/chalk/slice-ansi) - Slice a string with ANSI escape codes +- [color-convert](https://github.com/qix-/color-convert) - Converts colors between different models +- [chalk-animation](https://github.com/bokub/chalk-animation) - Animate strings in the terminal +- [gradient-string](https://github.com/bokub/gradient-string) - Apply color gradients to strings + + +## Maintainers + +- [Sindre Sorhus](https://github.com/sindresorhus) +- [Josh Junon](https://github.com/qix-) + + +## License + +MIT diff --git a/node_modules/ava/node_modules/chalk/templates.js b/node_modules/ava/node_modules/chalk/templates.js new file mode 100644 index 000000000..101551528 --- /dev/null +++ b/node_modules/ava/node_modules/chalk/templates.js @@ -0,0 +1,128 @@ +'use strict'; +const TEMPLATE_REGEX = /(?:\\(u[a-f0-9]{4}|x[a-f0-9]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; +const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; +const ESCAPE_REGEX = /\\(u[0-9a-f]{4}|x[0-9a-f]{2}|.)|([^\\])/gi; + +const ESCAPES = { + n: '\n', + r: '\r', + t: '\t', + b: '\b', + f: '\f', + v: '\v', + 0: '\0', + '\\': '\\', + e: '\u001b', + a: '\u0007' +}; + +function unescape(c) { + if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } + + return ESCAPES[c] || c; +} + +function parseArguments(name, args) { + const results = []; + const chunks = args.trim().split(/\s*,\s*/g); + let matches; + + for (const chunk of chunks) { + if (!isNaN(chunk)) { + results.push(Number(chunk)); + } else if ((matches = chunk.match(STRING_REGEX))) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + + return results; +} + +function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; + + const results = []; + let matches; + + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; + + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } + + return results; +} + +function buildStyle(chalk, styles) { + const enabled = {}; + + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(1); + } + } + + let current = chalk; + for (const styleName of Object.keys(enabled)) { + if (Array.isArray(enabled[styleName])) { + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + if (enabled[styleName].length > 0) { + current = current[styleName].apply(current, enabled[styleName]); + } else { + current = current[styleName]; + } + } + } + + return current; +} + +module.exports = (chalk, tmp) => { + const styles = []; + const chunks = []; + let chunk = []; + + // eslint-disable-next-line max-params + tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { + if (escapeChar) { + chunk.push(unescape(escapeChar)); + } else if (style) { + const str = chunk.join(''); + chunk = []; + chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); + styles.push({inverse, styles: parseStyle(style)}); + } else if (close) { + if (styles.length === 0) { + throw new Error('Found extraneous } in Chalk template literal'); + } + + chunks.push(buildStyle(chalk, styles)(chunk.join(''))); + chunk = []; + styles.pop(); + } else { + chunk.push(chr); + } + }); + + chunks.push(chunk.join('')); + + if (styles.length > 0) { + const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; + throw new Error(errMsg); + } + + return chunks.join(''); +}; diff --git a/node_modules/ava/node_modules/find-cache-dir/index.js b/node_modules/ava/node_modules/find-cache-dir/index.js new file mode 100644 index 000000000..2502e4433 --- /dev/null +++ b/node_modules/ava/node_modules/find-cache-dir/index.js @@ -0,0 +1,34 @@ +'use strict'; +const path = require('path'); +const commonDir = require('commondir'); +const pkgDir = require('pkg-dir'); +const makeDir = require('make-dir'); + +module.exports = options => { + const name = options.name; + let dir = options.cwd; + + if (options.files) { + dir = commonDir(dir, options.files); + } else { + dir = dir || process.cwd(); + } + + dir = pkgDir.sync(dir); + + if (dir) { + dir = path.join(dir, 'node_modules', '.cache', name); + + if (dir && options.create) { + makeDir.sync(dir); + } + + if (options.thunk) { + return function () { + return path.join.apply(path, [dir].concat(Array.prototype.slice.call(arguments))); + }; + } + } + + return dir; +}; diff --git a/node_modules/ava/node_modules/md5-hex/license b/node_modules/ava/node_modules/find-cache-dir/license index 654d0bfe9..ad5d021ed 100644 --- a/node_modules/ava/node_modules/md5-hex/license +++ b/node_modules/ava/node_modules/find-cache-dir/license @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) +Copyright (c) James Talmage <james@talmage.io> (github.com/jamestalmage) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/node_modules/ava/node_modules/find-cache-dir/package.json b/node_modules/ava/node_modules/find-cache-dir/package.json new file mode 100644 index 000000000..bf5a64db1 --- /dev/null +++ b/node_modules/ava/node_modules/find-cache-dir/package.json @@ -0,0 +1,47 @@ +{ + "name": "find-cache-dir", + "version": "1.0.0", + "description": "My well-made module", + "license": "MIT", + "repository": "avajs/find-cache-dir", + "author": { + "name": "James Talmage", + "email": "james@talmage.io", + "url": "github.com/jamestalmage" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && nyc ava" + }, + "files": [ + "index.js" + ], + "keywords": [ + "cache", + "directory", + "dir", + "caching", + "find", + "search" + ], + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + }, + "devDependencies": { + "ava": "^0.19.1", + "coveralls": "^2.11.6", + "del": "^2.2.2", + "nyc": "^10.3.2", + "xo": "^0.18.2" + }, + "nyc": { + "reporter": [ + "lcov", + "text" + ] + } +} diff --git a/node_modules/ava/node_modules/find-cache-dir/readme.md b/node_modules/ava/node_modules/find-cache-dir/readme.md new file mode 100644 index 000000000..447d62020 --- /dev/null +++ b/node_modules/ava/node_modules/find-cache-dir/readme.md @@ -0,0 +1,113 @@ +# find-cache-dir [](https://travis-ci.org/avajs/find-cache-dir) [](https://coveralls.io/github/avajs/find-cache-dir?branch=master) + +> Finds the common standard cache directory + +Recently the [`nyc`](https://github.com/bcoe/nyc) and [`AVA`](https://ava.li) projects decided to standardize on a common directory structure for storing cache information: + +```sh +# nyc +./node_modules/.cache/nyc + +# ava +./node_modules/.cache/ava + +# your-module +./node_modules/.cache/your-module +``` + +This module makes it easy to correctly locate the cache directory according to this shared spec. If this pattern becomes ubiquitous, clearing the cache for multiple dependencies becomes easy and consistent: + +``` +rm -rf ./node_modules/.cache +``` + +If you decide to adopt this pattern, please file a PR adding your name to the list of adopters below. + + +## Install + +``` +$ npm install --save find-cache-dir +``` + + +## Usage + +```js +const findCacheDir = require('find-cache-dir'); + +findCacheDir({name: 'unicorns'}); +//=> '/user/path/node-modules/.cache/unicorns' +``` + + +## API + +### findCacheDir([options]) + +Finds the cache directory using the supplied options. The algorithm tries to find a `package.json` file, searching every parent directory of the `cwd` specified (or implied from other options). It returns a `string` containing the absolute path to the cache directory, or `null` if `package.json` was never found. + +#### options + +##### name + +*Required*<br> +Type: `string` + +Should be the same as your project name in `package.json`. + +##### files + +Type: `Array` `string + +An array of files that will be searched for a common parent directory. This common parent directory will be used in lieu of the `cwd` option below. + +##### cwd + +Type: `string`<br> +Default `process.cwd()` + +Directory to start searching for a `package.json` from. + +##### create + +Type: `boolean`<br> +Default `false` + +If `true`, the directory will be created synchronously before returning. + +##### thunk + +Type: `boolean`<br> +Default `false` + +If `true`, this modifies the return type to be a function that is a thunk for `path.join(theFoundCacheDirectory)`. + +```js +const thunk = findCacheDir({name: 'foo', thunk: true}); + +thunk(); +//=> '/some/path/node_modules/.cache/foo' + +thunk('bar.js') +//=> '/some/path/node_modules/.cache/foo/bar.js' + +thunk('baz', 'quz.js') +//=> '/some/path/node_modules/.cache/foo/baz/quz.js' +``` + +This is helpful for actually putting actual files in the cache! + + +## Adopters + +- [`AVA`](https://ava.li) +- [`nyc`](https://github.com/bcoe/nyc) +- [`babel-loader`](https://github.com/babel/babel-loader) +- [`eslint-loader`](https://github.com/MoOx/eslint-loader) +- [`Phenomic`](https://phenomic.io) + + +## License + +MIT © [James Talmage](https://github.com/jamestalmage) diff --git a/node_modules/ava/node_modules/has-flag/index.js b/node_modules/ava/node_modules/has-flag/index.js deleted file mode 100644 index 68820307d..000000000 --- a/node_modules/ava/node_modules/has-flag/index.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -module.exports = function (flag, argv) { - argv = argv || process.argv; - - var terminatorPos = argv.indexOf('--'); - var prefix = /^-{1,2}/.test(flag) ? '' : '--'; - var pos = argv.indexOf(prefix + flag); - - return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos); -}; diff --git a/node_modules/ava/node_modules/has-flag/license b/node_modules/ava/node_modules/has-flag/license deleted file mode 100644 index 654d0bfe9..000000000 --- a/node_modules/ava/node_modules/has-flag/license +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/node_modules/ava/node_modules/has-flag/package.json b/node_modules/ava/node_modules/has-flag/package.json deleted file mode 100644 index bfcd302ea..000000000 --- a/node_modules/ava/node_modules/has-flag/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "has-flag", - "version": "2.0.0", - "description": "Check if argv has a specific flag", - "license": "MIT", - "repository": "sindresorhus/has-flag", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "maintainers": [ - "Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)", - "Joshua Appelman <jappelman@xebia.com> (jbnicolai.com)", - "JD Ballard <i.am.qix@gmail.com> (github.com/qix-)" - ], - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "xo && ava" - }, - "files": [ - "index.js" - ], - "keywords": [ - "has", - "check", - "detect", - "contains", - "find", - "flag", - "cli", - "command-line", - "argv", - "process", - "arg", - "args", - "argument", - "arguments", - "getopt", - "minimist", - "optimist" - ], - "devDependencies": { - "ava": "*", - "xo": "*" - } -} diff --git a/node_modules/ava/node_modules/has-flag/readme.md b/node_modules/ava/node_modules/has-flag/readme.md deleted file mode 100644 index 0caca6cba..000000000 --- a/node_modules/ava/node_modules/has-flag/readme.md +++ /dev/null @@ -1,67 +0,0 @@ -# has-flag [](https://travis-ci.org/sindresorhus/has-flag) - -> Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag - -Correctly stops looking after an `--` argument terminator. - - -## Install - -``` -$ npm install --save has-flag -``` - - -## Usage - -```js -// foo.js -const hasFlag = require('has-flag'); - -hasFlag('unicorn'); -//=> true - -hasFlag('--unicorn'); -//=> true - -hasFlag('-f'); -//=> true - -hasFlag('foo=bar'); -//=> true - -hasFlag('foo'); -//=> false - -hasFlag('rainbow'); -//=> false -``` - -``` -$ node foo.js -f --unicorn --foo=bar -- --rainbow -``` - - -## API - -### hasFlag(flag, [argv]) - -Returns a boolean whether the flag exists. - -#### flag - -Type: `string` - -CLI flag to look for. The `--` prefix is optional. - -#### argv - -Type: `array`<br> -Default: `process.argv` - -CLI arguments. - - -## License - -MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/node_modules/ava/node_modules/md5-hex/browser.js b/node_modules/ava/node_modules/md5-hex/browser.js deleted file mode 100644 index d6c2da0bf..000000000 --- a/node_modules/ava/node_modules/md5-hex/browser.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -const md5OMatic = require('md5-o-matic'); - -module.exports = input => { - if (Array.isArray(input)) { - input = input.join(''); - } - - return md5OMatic(input); -}; diff --git a/node_modules/ava/node_modules/md5-hex/index.js b/node_modules/ava/node_modules/md5-hex/index.js deleted file mode 100644 index 82cfae306..000000000 --- a/node_modules/ava/node_modules/md5-hex/index.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -const crypto = require('crypto'); - -module.exports = function (input) { - const hash = crypto.createHash('md5'); - - const update = buf => { - const inputEncoding = typeof buf === 'string' ? 'utf8' : undefined; - hash.update(buf, inputEncoding); - }; - - if (arguments.length > 1) { - throw new Error('Too many arguments. Try specifying an array.'); - } - - if (Array.isArray(input)) { - input.forEach(update); - } else { - update(input); - } - - return hash.digest('hex'); -}; diff --git a/node_modules/ava/node_modules/md5-hex/package.json b/node_modules/ava/node_modules/md5-hex/package.json deleted file mode 100644 index a87ce154f..000000000 --- a/node_modules/ava/node_modules/md5-hex/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "md5-hex", - "version": "2.0.0", - "description": "Create a MD5 hash with hex encoding", - "license": "MIT", - "repository": "sindresorhus/md5-hex", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && ava" - }, - "files": [ - "index.js", - "browser.js" - ], - "keywords": [ - "hash", - "crypto", - "md5", - "hex", - "buffer", - "browser", - "browserify" - ], - "dependencies": { - "md5-o-matic": "^0.1.1" - }, - "devDependencies": { - "ava": "*", - "xo": "*" - }, - "browser": "browser.js" -} diff --git a/node_modules/ava/node_modules/md5-hex/readme.md b/node_modules/ava/node_modules/md5-hex/readme.md deleted file mode 100644 index 630b31d0b..000000000 --- a/node_modules/ava/node_modules/md5-hex/readme.md +++ /dev/null @@ -1,46 +0,0 @@ -# md5-hex [](https://travis-ci.org/sindresorhus/md5-hex) - -> Create a MD5 hash with hex encoding - -*Please don't use MD5 hashes for anything sensitive!* - -Works in the browser too, when used with browserify/webpack. - -Checkout [`hasha`](https://github.com/sindresorhus/hasha) if you need something more flexible. - - -## Install - -``` -$ npm install --save md5-hex -``` - - -## Usage - -```js -const fs = require('fs'); -const md5Hex = require('md5-hex'); -const buffer = fs.readFileSync('unicorn.png'); - -md5Hex(buffer); -//=> '1abcb33beeb811dca15f0ac3e47b88d9' -``` - - -## API - -### md5Hex(input) - -#### input - -Type: `Buffer` `string` `Buffer[]` `string[]` - -Prefer buffers as they're faster to hash, but strings can be useful for small things. - -Pass an array instead of concatenating strings and/or buffers. The output is the same, but arrays do not incur the overhead of concatenation. - - -## License - -MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/node_modules/ava/node_modules/strip-ansi/index.js b/node_modules/ava/node_modules/strip-ansi/index.js new file mode 100644 index 000000000..96e0292c8 --- /dev/null +++ b/node_modules/ava/node_modules/strip-ansi/index.js @@ -0,0 +1,4 @@ +'use strict'; +const ansiRegex = require('ansi-regex'); + +module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; diff --git a/node_modules/ava/node_modules/strip-ansi/license b/node_modules/ava/node_modules/strip-ansi/license new file mode 100644 index 000000000..e7af2f771 --- /dev/null +++ b/node_modules/ava/node_modules/strip-ansi/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/ava/node_modules/strip-ansi/package.json b/node_modules/ava/node_modules/strip-ansi/package.json new file mode 100644 index 000000000..555f19461 --- /dev/null +++ b/node_modules/ava/node_modules/strip-ansi/package.json @@ -0,0 +1,52 @@ +{ + "name": "strip-ansi", + "version": "4.0.0", + "description": "Strip ANSI escape codes", + "license": "MIT", + "repository": "chalk/strip-ansi", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && ava" + }, + "files": [ + "index.js" + ], + "keywords": [ + "strip", + "trim", + "remove", + "ansi", + "styles", + "color", + "colour", + "colors", + "terminal", + "console", + "string", + "tty", + "escape", + "formatting", + "rgb", + "256", + "shell", + "xterm", + "log", + "logging", + "command-line", + "text" + ], + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "devDependencies": { + "ava": "*", + "xo": "*" + } +} diff --git a/node_modules/ava/node_modules/strip-ansi/readme.md b/node_modules/ava/node_modules/strip-ansi/readme.md new file mode 100644 index 000000000..dc76f0cb1 --- /dev/null +++ b/node_modules/ava/node_modules/strip-ansi/readme.md @@ -0,0 +1,39 @@ +# strip-ansi [](https://travis-ci.org/chalk/strip-ansi) + +> Strip [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) + + +## Install + +``` +$ npm install strip-ansi +``` + + +## Usage + +```js +const stripAnsi = require('strip-ansi'); + +stripAnsi('\u001B[4mUnicorn\u001B[0m'); +//=> 'Unicorn' +``` + + +## Related + +- [strip-ansi-cli](https://github.com/chalk/strip-ansi-cli) - CLI for this module +- [has-ansi](https://github.com/chalk/has-ansi) - Check if a string has ANSI escape codes +- [ansi-regex](https://github.com/chalk/ansi-regex) - Regular expression for matching ANSI escape codes +- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right + + +## Maintainers + +- [Sindre Sorhus](https://github.com/sindresorhus) +- [Josh Junon](https://github.com/qix-) + + +## License + +MIT diff --git a/node_modules/ava/package.json b/node_modules/ava/package.json index eb9678ff1..2cf988dd7 100644 --- a/node_modules/ava/package.json +++ b/node_modules/ava/package.json @@ -1,209 +1,220 @@ { - "name": "ava", - "version": "0.19.1", - "description": "Futuristic test runner 🚀", - "license": "MIT", - "repository": "avajs/ava", - "homepage": "https://ava.li", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "maintainers": [ - { - "name": "Vadim Demedes", - "email": "vdemedes@gmail.com", - "url": "github.com/vadimdemedes" - }, - { - "name": "James Talmage", - "email": "james@talmage.io", - "url": "github.com/jamestalmage" - }, - { - "name": "Mark Wubben", - "email": "mark@novemberborn.net", - "url": "novemberborn.net" - }, - { - "name": "Juan Soto", - "email": "juan@juansoto.me", - "url": "juansoto.me" - }, - { - "name": "Jeroen Engels", - "email": "jfm.engels@gmail.com", - "url": "github.com/jfmengels" - } - ], - "bin": "cli.js", - "typings": "types/generated.d.ts", - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && flow check test/flow-types && tsc -p test/ts-types && nyc tap --no-cov --timeout=150 --jobs=4 test/*.js test/reporters/*.js", - "test-win": "tap --no-cov --reporter=classic --timeout=150 --jobs=4 test/*.js test/reporters/*.js", - "visual": "node test/visual/run-visual-tests.js", - "prepublish": "npm run make-ts", - "make-ts": "node types/make.js" - }, - "files": [ - "lib", - "*.js", - "*.js.flow", - "types/generated.d.ts" - ], - "keywords": [ - "test", - "runner", - "ava", - "concurrent", - "parallel", - "fast", - "tape", - "tap", - "jest", - "mocha", - "qunit", - "jasmine", - "testing", - "tdd", - "cli-app", - "cli", - "assert", - "assertion", - "futuristic", - "promise", - "promises", - "async", - "function", - "await", - "generator", - "generators", - "yield", - "observable", - "observables" - ], - "dependencies": { - "@ava/babel-preset-stage-4": "^1.0.0", - "@ava/babel-preset-transform-test-files": "^3.0.0", - "@ava/pretty-format": "^1.1.0", - "arr-flatten": "^1.0.1", - "array-union": "^1.0.1", - "array-uniq": "^1.0.2", - "arrify": "^1.0.0", - "auto-bind": "^1.1.0", - "ava-init": "^0.2.0", - "babel-code-frame": "^6.16.0", - "babel-core": "^6.17.0", - "bluebird": "^3.0.0", - "caching-transform": "^1.0.0", - "chalk": "^1.0.0", - "chokidar": "^1.4.2", - "clean-stack": "^1.1.1", - "clean-yaml-object": "^0.1.0", - "cli-cursor": "^2.1.0", - "cli-spinners": "^1.0.0", - "cli-truncate": "^1.0.0", - "co-with-promise": "^4.6.0", - "code-excerpt": "^2.1.0", - "common-path-prefix": "^1.0.0", - "convert-source-map": "^1.2.0", - "core-assert": "^0.2.0", - "currently-unhandled": "^0.4.1", - "debug": "^2.2.0", - "diff": "^3.0.1", - "diff-match-patch": "^1.0.0", - "dot-prop": "^4.1.0", - "empower-core": "^0.6.1", - "equal-length": "^1.0.0", - "figures": "^2.0.0", - "find-cache-dir": "^0.1.1", - "fn-name": "^2.0.0", - "get-port": "^3.0.0", - "globby": "^6.0.0", - "has-flag": "^2.0.0", - "hullabaloo-config-manager": "^1.0.0", - "ignore-by-default": "^1.0.0", - "indent-string": "^3.0.0", - "is-ci": "^1.0.7", - "is-generator-fn": "^1.0.0", - "is-obj": "^1.0.0", - "is-observable": "^0.2.0", - "is-promise": "^2.1.0", - "jest-diff": "19.0.0", - "jest-snapshot": "19.0.2", - "js-yaml": "^3.8.2", - "last-line-stream": "^1.0.0", - "lodash.debounce": "^4.0.3", - "lodash.difference": "^4.3.0", - "lodash.flatten": "^4.2.0", - "lodash.isequal": "^4.5.0", - "loud-rejection": "^1.2.0", - "matcher": "^0.1.1", - "md5-hex": "^2.0.0", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", - "ms": "^0.7.1", - "multimatch": "^2.1.0", - "observable-to-promise": "^0.5.0", - "option-chain": "^0.1.0", - "package-hash": "^2.0.0", - "pkg-conf": "^2.0.0", - "plur": "^2.0.0", - "pretty-ms": "^2.0.0", - "require-precompiled": "^0.1.0", - "resolve-cwd": "^1.0.0", - "slash": "^1.0.0", - "source-map-support": "^0.4.0", - "stack-utils": "^1.0.0", - "strip-ansi": "^3.0.1", - "strip-bom-buf": "^1.0.0", - "supports-color": "^3.2.3", - "time-require": "^0.1.2", - "unique-temp-dir": "^1.0.0", - "update-notifier": "^2.1.0" - }, - "devDependencies": { - "babel-preset-react": "^6.5.0", - "cli-table2": "^0.2.0", - "coveralls": "^2.11.4", - "delay": "^1.3.0", - "execa": "^0.6.0", - "flow-bin": "^0.42.0", - "get-stream": "^3.0.0", - "git-branch": "^0.3.0", - "has-ansi": "^2.0.0", - "inquirer": "^3.0.5", - "is-array-sorted": "^1.0.0", - "lolex": "^1.4.0", - "nyc": "^10.0.0", - "proxyquire": "^1.7.4", - "rimraf": "^2.5.0", - "signal-exit": "^3.0.0", - "sinon": "^2.0.0", - "source-map-fixtures": "^2.1.0", - "tap": "^10.0.0", - "temp-write": "^3.1.0", - "touch": "^1.0.0", - "typescript": "^2.2.2", - "xo": "^0.18.0", - "zen-observable": "^0.5.1" - }, - "xo": { - "esnext": true, - "rules": { - "import/newline-after-import": "off", - "no-use-extend-native/no-use-extend-native": "off" - } - }, - "nyc": { - "reporter": [ - "html", - "lcov", - "text" - ] - } + "name": "ava", + "version": "0.21.0", + "description": "Futuristic test runner 🚀", + "license": "MIT", + "repository": "avajs/ava", + "homepage": "https://ava.li", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "maintainers": [ + { + "name": "Vadim Demedes", + "email": "vdemedes@gmail.com", + "url": "github.com/vadimdemedes" + }, + { + "name": "James Talmage", + "email": "james@talmage.io", + "url": "github.com/jamestalmage" + }, + { + "name": "Mark Wubben", + "email": "mark@novemberborn.net", + "url": "novemberborn.net" + }, + { + "name": "Juan Soto", + "email": "juan@juansoto.me", + "url": "juansoto.me" + }, + { + "name": "Jeroen Engels", + "email": "jfm.engels@gmail.com", + "url": "github.com/jfmengels" + } + ], + "bin": "cli.js", + "engines": { + "node": ">=4" + }, + "scripts": { + "test": "xo && flow check test/flow-types && tsc -p test/ts-types && nyc tap --no-cov --timeout=300 --jobs=4 test/*.js test/reporters/*.js", + "test-win": "tap --no-cov --reporter=classic --timeout=300 --jobs=4 test/*.js test/reporters/*.js", + "visual": "node test/visual/run-visual-tests.js", + "prepublish": "npm run make-ts", + "make-ts": "node types/make.js" + }, + "files": [ + "lib", + "*.js", + "*.js.flow", + "types/generated.d.ts" + ], + "keywords": [ + "🦄", + "test", + "runner", + "testing", + "ava", + "concurrent", + "parallel", + "fast", + "tdd", + "cli-app", + "cli", + "jest", + "mocha", + "tape", + "tap", + "qunit", + "jasmine", + "babel", + "assert", + "assertion", + "futuristic", + "promise", + "promises", + "async", + "function", + "await", + "generator", + "generators", + "yield", + "observable", + "unit", + "observables", + "snapshot", + "expect", + "typescript", + "flow" + ], + "dependencies": { + "@ava/babel-preset-stage-4": "^1.1.0", + "@ava/babel-preset-transform-test-files": "^3.0.0", + "@ava/write-file-atomic": "^2.2.0", + "@concordance/react": "^1.0.0", + "ansi-escapes": "^2.0.0", + "ansi-styles": "^3.1.0", + "arr-flatten": "^1.0.1", + "array-union": "^1.0.1", + "array-uniq": "^1.0.2", + "arrify": "^1.0.0", + "auto-bind": "^1.1.0", + "ava-init": "^0.2.0", + "babel-core": "^6.17.0", + "bluebird": "^3.0.0", + "caching-transform": "^1.0.0", + "chalk": "^2.0.1", + "chokidar": "^1.4.2", + "clean-stack": "^1.1.1", + "clean-yaml-object": "^0.1.0", + "cli-cursor": "^2.1.0", + "cli-spinners": "^1.0.0", + "cli-truncate": "^1.0.0", + "co-with-promise": "^4.6.0", + "code-excerpt": "^2.1.0", + "common-path-prefix": "^1.0.0", + "concordance": "^3.0.0", + "convert-source-map": "^1.2.0", + "core-assert": "^0.2.0", + "currently-unhandled": "^0.4.1", + "debug": "^2.2.0", + "dot-prop": "^4.1.0", + "empower-core": "^0.6.1", + "equal-length": "^1.0.0", + "figures": "^2.0.0", + "find-cache-dir": "^1.0.0", + "fn-name": "^2.0.0", + "get-port": "^3.0.0", + "globby": "^6.0.0", + "has-flag": "^2.0.0", + "hullabaloo-config-manager": "^1.1.0", + "ignore-by-default": "^1.0.0", + "import-local": "^0.1.1", + "indent-string": "^3.0.0", + "is-ci": "^1.0.7", + "is-generator-fn": "^1.0.0", + "is-obj": "^1.0.0", + "is-observable": "^0.2.0", + "is-promise": "^2.1.0", + "js-yaml": "^3.8.2", + "last-line-stream": "^1.0.0", + "lodash.clonedeepwith": "^4.5.0", + "lodash.debounce": "^4.0.3", + "lodash.difference": "^4.3.0", + "lodash.flatten": "^4.2.0", + "loud-rejection": "^1.2.0", + "make-dir": "^1.0.0", + "matcher": "^1.0.0", + "md5-hex": "^2.0.0", + "meow": "^3.7.0", + "ms": "^2.0.0", + "multimatch": "^2.1.0", + "observable-to-promise": "^0.5.0", + "option-chain": "^1.0.0", + "package-hash": "^2.0.0", + "pkg-conf": "^2.0.0", + "plur": "^2.0.0", + "pretty-ms": "^2.0.0", + "require-precompiled": "^0.1.0", + "resolve-cwd": "^2.0.0", + "safe-buffer": "^5.1.1", + "slash": "^1.0.0", + "source-map-support": "^0.4.0", + "stack-utils": "^1.0.0", + "strip-ansi": "^4.0.0", + "strip-bom-buf": "^1.0.0", + "supports-color": "^4.0.0", + "time-require": "^0.1.2", + "trim-off-newlines": "^1.0.1", + "unique-temp-dir": "^1.0.0", + "update-notifier": "^2.1.0" + }, + "devDependencies": { + "cli-table2": "^0.2.0", + "codecov": "^2.1.0", + "del": "^3.0.0", + "delay": "^2.0.0", + "execa": "^0.7.0", + "flow-bin": "^0.49.1", + "get-stream": "^3.0.0", + "git-branch": "^1.0.0", + "has-ansi": "^3.0.0", + "inquirer": "^3.0.5", + "is-array-sorted": "^1.0.0", + "lolex": "^1.4.0", + "nyc": "^11.0.3", + "proxyquire": "^1.7.4", + "react": "^15.6.1", + "react-test-renderer": "^15.6.1", + "signal-exit": "^3.0.0", + "sinon": "^2.0.0", + "source-map-fixtures": "^2.1.0", + "tap": "^10.0.0", + "temp-write": "^3.1.0", + "touch": "^2.0.2", + "typescript": "^2.2.2", + "xo": "^0.18.2", + "zen-observable": "^0.5.1" + }, + "typings": "types/generated.d.ts", + "xo": { + "ignores": [ + "media/**" + ], + "rules": { + "no-use-extend-native/no-use-extend-native": "off" + } + }, + "nyc": { + "reporter": [ + "html", + "lcov", + "text" + ] + } } diff --git a/node_modules/ava/profile.js b/node_modules/ava/profile.js index 530543fcb..3067e663a 100644 --- a/node_modules/ava/profile.js +++ b/node_modules/ava/profile.js @@ -19,7 +19,7 @@ const globals = require('./lib/globals'); function resolveModules(modules) { return arrify(modules).map(name => { - const modulePath = resolveCwd(name); + const modulePath = resolveCwd.silent(name); if (modulePath === null) { throw new Error(`Could not resolve required module '${name}'`); @@ -66,7 +66,7 @@ const cli = meow(` } }); -if (cli.input.length !== 1) { +if (cli.input.length === 0) { throw new Error('Specify a test file'); } diff --git a/node_modules/ava/readme.md b/node_modules/ava/readme.md index 1283a43bb..239f257a4 100644 --- a/node_modules/ava/readme.md +++ b/node_modules/ava/readme.md @@ -6,7 +6,7 @@ Even though JavaScript is single-threaded, IO in Node.js can happen in parallel due to its async nature. AVA takes advantage of this and runs your tests concurrently, which is especially beneficial for IO heavy tests. In addition, test files are run in parallel as separate processes, giving you even better performance and an isolated environment for each test file. [Switching](https://github.com/sindresorhus/pageres/commit/663be15acb3dd2eb0f71b1956ef28c2cd3fdeed0) from Mocha to AVA in Pageres brought the test time down from 31 to 11 seconds. Having tests run concurrently forces you to write atomic tests, meaning tests don't depend on global state or the state of other tests, which is a great thing! - + *Read our [contributing guide](contributing.md) if you're looking to contribute (issues/PRs/etc).* @@ -88,13 +88,13 @@ Your `package.json` will then look like this: ```json { - "name": "awesome-package", - "scripts": { - "test": "ava" - }, - "devDependencies": { - "ava": "^0.18.0" - } + "name": "awesome-package", + "scripts": { + "test": "ava" + }, + "devDependencies": { + "ava": "^0.20.0" + } } ``` @@ -203,6 +203,7 @@ $ node --inspect node_modules/ava/profile.js some/test/file.js - [Chrome DevTools](docs/recipes/debugging-with-chrome-devtools.md) - [WebStorm](docs/recipes/debugging-with-webstorm.md) +- [Visual Studio Code](docs/recipes/debugging-with-vscode.md) ## Reporters @@ -211,13 +212,13 @@ $ node --inspect node_modules/ava/profile.js some/test/file.js The mini-reporter is the default reporter. -<img src="media/screenshot-mini-reporter.gif" width="460"> +<img src="media/mini-reporter.gif" width="460"> ### Verbose reporter Use the `--verbose` flag to enable the verbose reporter. This is always used in CI environments unless the [TAP reporter](#tap-reporter) is enabled. -<img src="media/screenshot.png" width="150"> +<img src="media/verbose-reporter.png" width="294"> ### TAP reporter @@ -227,7 +228,7 @@ AVA supports the TAP format and thus is compatible with [any TAP reporter](https $ ava --tap | tap-nyan ``` -<img src="media/tap-output.png" width="398"> +<img src="media/tap-reporter.png" width="420"> Please note that the TAP reporter is unavailable when using [watch mode](#watch-it). @@ -248,29 +249,29 @@ All of the CLI options can be configured in the `ava` section of your `package.j ```json { - "ava": { - "files": [ - "my-test-folder/*.js", - "!**/not-this-file.js" - ], - "source": [ - "**/*.{js,jsx}", - "!dist/**/*" - ], - "match": [ - "*oo", - "!foo" - ], - "concurrency": 5, - "failFast": true, - "failWithoutAssertions": false, - "tap": true, - "powerAssert": false, - "require": [ - "babel-register" - ], - "babel": "inherit" - } + "ava": { + "files": [ + "my-test-folder/*.js", + "!**/not-this-file.js" + ], + "source": [ + "**/*.{js,jsx}", + "!dist/**/*" + ], + "match": [ + "*oo", + "!foo" + ], + "concurrency": 5, + "failFast": true, + "failWithoutAssertions": false, + "tap": true, + "powerAssert": false, + "require": [ + "babel-register" + ], + "babel": "inherit" + } } ``` @@ -375,9 +376,7 @@ test(t => { ### Running tests serially -By default tests are run concurrently, which is awesome. Sometimes though you have to write tests that cannot run concurrently. - -In these rare cases you can use the `.serial` modifier. It will force those tests to run serially *before* the concurrent ones. +Tests are run concurrently by default, however, sometimes you have to write tests that cannot run concurrently. In these rare cases you can use the `.serial` modifier. It will force those tests to run serially *before* the concurrent ones. ```js test.serial(t => { @@ -632,8 +631,8 @@ function macro(t, input, expected) { t.is(eval(input), expected); } -test('2 + 2 === 4', macro, '2 + 2', 4); -test('2 * 3 === 6', macro, '2 * 3', 6); +test('2 + 2 = 4', macro, '2 + 2', 4); +test('2 * 3 = 6', macro, '2 * 3', 6); ``` You can build the test title programmatically by attaching a `title` function to the macro: @@ -643,7 +642,7 @@ function macro(t, input, expected) { t.is(eval(input), expected); } -macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} === ${expected}`.trim(); +macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} = ${expected}`.trim(); test(macro, '2 + 2', 4); test(macro, '2 * 3', 6); @@ -695,10 +694,10 @@ The corresponding Babel config for AVA's setup is as follows: ```json { - "presets": [ - "@ava/stage-4", - "@ava/transform-test-files" - ] + "presets": [ + "@ava/stage-4", + "@ava/transform-test-files" + ] } ``` @@ -706,15 +705,15 @@ You can customize how AVA transpiles the test files through the `babel` option i ```json { - "ava": { - "babel": { - "presets": [ - "es2015", - "stage-0", - "react" - ] - } - } + "ava": { + "babel": { + "presets": [ + "es2015", + "stage-0", + "react" + ] + } + } } ``` @@ -722,16 +721,16 @@ You can also use the special `"inherit"` keyword. This makes AVA defer to the Ba ```json { - "babel": { - "presets": [ - "es2015", - "stage-0", - "react" - ] - }, - "ava": { - "babel": "inherit" - } + "babel": { + "presets": [ + "es2015", + "stage-0", + "react" + ] + }, + "ava": { + "babel": "inherit" + } } ``` @@ -866,7 +865,7 @@ Should contain the actual test. Type: `object` -The execution object of a particular test. Each test implementation receives a different object. Contains the [assertions](#assertions) as well as `.plan(count)` and `.end()` methods. `t.context` can contain shared state from `beforeEach` hooks. +The execution object of a particular test. Each test implementation receives a different object. Contains the [assertions](#assertions) as well as `.plan(count)` and `.end()` methods. `t.context` can contain shared state from `beforeEach` hooks. `t.title` returns the test's title. ###### `t.plan(count)` @@ -914,23 +913,19 @@ Assert that `value` is `false`. ### `.is(value, expected, [message])` -Assert that `value` is equal to `expected`. +Assert that `value` is the same as `expected`. This is based on [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). ### `.not(value, expected, [message])` -Assert that `value` is not equal to `expected`. +Assert that `value` is not the same as `expected`. This is based on [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). ### `.deepEqual(value, expected, [message])` -Assert that `value` is deep equal to `expected`. This is based on [Lodash' `isEqual()`](https://lodash.com/docs/4.17.4#isEqual): - -> Performs a deep comparison between two values to determine if they are equivalent. -> -> *Note*: This method supports comparing arrays, array buffers, booleans, date objects, error objects, maps, numbers, `Object` objects, regexes, sets, strings, symbols, and typed arrays. `Object` objects are compared by their own, not inherited, enumerable properties. Functions and DOM nodes are compared by strict equality, i.e. `===`. +Assert that `value` is deeply equal to `expected`. See [Concordance](https://github.com/concordancejs/concordance) for details. Works with [React elements and `react-test-renderer`](https://github.com/concordancejs/react). ### `.notDeepEqual(value, expected, [message])` -Assert that `value` is not deep equal to `expected`. The inverse of `.deepEqual()`. +Assert that `value` is not deeply equal to `expected`. The inverse of `.deepEqual()`. ### `.throws(function|promise, [error, [message]])` @@ -997,15 +992,14 @@ Assert that `contents` does not match `regex`. Assert that `error` is falsy. -### `.snapshot(contents, [message])` +### `.snapshot(expected, [message])` +### `.snapshot(expected, [options], [message])` -Make a snapshot of the stringified `contents`. +Compares the `expected` value with a previously recorded snapshot. Snapshots are stored for each test, so ensure you give your tests unique titles. Alternatively pass an `options` object to select a specific snapshot, for instance `{id: 'my snapshot'}`. ## Snapshot testing -Snapshot testing comes as another kind of assertion and uses [jest-snapshot](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) under the hood. - -When used with React, it looks very similar to Jest: +AVA supports snapshot testing, [as introduced by Jest](https://facebook.github.io/jest/docs/snapshot-testing.html), through its [Assertions](#assertions) interface. You can snapshot any value as well as React elements: ```js // Your component @@ -1018,41 +1012,35 @@ export default HelloWorld; // Your test import test from 'ava'; import render from 'react-test-renderer'; - import HelloWorld from '.'; test('HelloWorld component', t => { - const tree = render.create(<HelloWorld />).toJSON(); + const tree = render.create(<HelloWorld/>).toJSON(); t.snapshot(tree); }); ``` -The first time you run this test, a snapshot file will be created in `__snapshots__` folder looking something like this: +[Try it out in this example project.](https://github.com/avajs/ava-snapshot-example) -```js -exports[`HelloWorld component 1`] = ` -<h1> - Hello World...! -</h1> -`; -``` +Snapshots are stored alongside your test files. If your tests are in a `test` or `tests` folder the snapshots will be stored in a `snapshots` folder. If your tests are in a `__tests__` folder then they they'll be stored in a `__snapshots__` folder. + +Say you have `~/project/test/main.js` which contains snapshot assertions. AVA will create two files: -These snapshots should be committed together with your code so that everyone on the team shares current state of the app. +* `~/project/test/snapshots/main.js.snap` +* `~/project/test/snapshots/main.js.md` -Every time you run this test afterwards, it will check if the component render has changed. If it did, it will fail the test. +The first file contains the actual snapshot and is required for future comparisons. The second file contains your *snapshot report*. It's regenerated when you update your snapshots. If you commit it to source control you can diff it to see the changes to your snapshot. -<img src="media/snapshot-testing.png" width="814"> +AVA will show why your snapshot assertion failed: -Then you will have the choice to check your code - and if the change was intentional, you can use the `--update-snapshots` (or `-u`) flag to update the snapshots into their new version. +<img src="media/snapshot-testing.png" width="1048"> -That might look like this: +You can then check your code. If the change was intentional you can use the `--update-snapshots` (or `-u`) flag to update the snapshots: ```console $ ava --update-snapshots ``` -Note that snapshots can be used for much more than just testing components - you can equally well test any other (data) structure that you can stringify. - ### Skipping assertions Any assertion can be skipped using the `skip` modifier. Skipped assertions are still counted, so there is no need to change your planned assertion count. @@ -1158,10 +1146,12 @@ It's the [Andromeda galaxy](https://simple.wikipedia.org/wiki/Andromeda_galaxy). - [TypeScript](docs/recipes/typescript.md) - [Configuring Babel](docs/recipes/babelrc.md) - [Testing React components](docs/recipes/react.md) +- [Testing Vue.js components](docs/recipes/vue.md) - [JSPM and SystemJS](docs/recipes/jspm-systemjs.md) - [Debugging tests with Chrome DevTools](docs/recipes/debugging-with-chrome-devtools.md) - [Debugging tests with WebStorm](docs/recipes/debugging-with-webstorm.md) - [Precompiling source files with webpack](docs/recipes/precompiling-with-webpack.md) +- [Isolated MongoDB integration tests](docs/recipes/isolated-mongodb-integration-tests.md) ## Support |