From a47d8c5561ba36512e641ded5795bc1a8874551b Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Tue, 13 Feb 2018 17:47:11 +0000 Subject: [PATCH 01/18] Remove additional dependency lines from stacks --- lib/beautify-stack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/beautify-stack.js b/lib/beautify-stack.js index 4ae8c04be..84ef8b00f 100644 --- a/lib/beautify-stack.js +++ b/lib/beautify-stack.js @@ -7,7 +7,7 @@ const debug = require('debug')('ava'); let ignoreStackLines = []; const avaInternals = /\/ava\/(?:lib\/)?[\w-]+\.js:\d+:\d+\)?$/; -const avaDependencies = /\/node_modules\/(?:bluebird|empower-core|(?:ava\/node_modules\/)?(?:babel-runtime|core-js))\//; +const avaDependencies = /\/node_modules\/(?:append-transform|bluebird|empower-core|nyc|require-precompiled|(?:ava\/node_modules\/)?(?:babel-runtime|core-js))\//; const stackFrameLine = /^.+( \(.+:\d+:\d+\)|:\d+:\d+)$/; if (!debug.enabled) { From 2137739032fb8aa0236b8926704180197fbf17b4 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Tue, 13 Feb 2018 17:47:46 +0000 Subject: [PATCH 02/18] Read TTY columns from stream, not stdout --- lib/reporters/mini.js | 8 ++++---- lib/reporters/verbose.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index 5dae12ff8..1968d0977 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -101,7 +101,7 @@ class MiniReporter { _test(test) { const SPINNER_WIDTH = 3; const PADDING = 1; - let title = cliTruncate(test.title, process.stdout.columns - SPINNER_WIDTH - PADDING); + let title = cliTruncate(test.title, this.stream.columns - SPINNER_WIDTH - PADDING); if (test.error || test.failing) { title = colors.error(test.title); @@ -191,7 +191,7 @@ class MiniReporter { if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source, {maxWidth: this.stream.columns}); if (excerpt) { status += '\n' + indentString(excerpt, 2) + '\n'; } @@ -275,7 +275,7 @@ class MiniReporter { } section() { - return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); + return '\n' + chalk.gray.dim('\u2500'.repeat(this.stream.columns || 80)); } clear() { @@ -300,7 +300,7 @@ class MiniReporter { _update(data) { let str = ''; let ct = this.statusLineCount; - const columns = process.stdout.columns; + const columns = this.stream.columns; let lastLine = this.lastLineTracker.lastLine(); // Terminals automatically wrap text. We only need the last log line as seen on the screen. diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index d0083c8fe..283955ea7 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -131,7 +131,7 @@ class VerboseReporter { if (test.error.source) { output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; - const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); + const excerpt = codeExcerpt(test.error.source, {maxWidth: this.stream.columns}); if (excerpt) { output += '\n' + indentString(excerpt, 2) + '\n'; } @@ -192,7 +192,7 @@ class VerboseReporter { } section() { - return chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); + return chalk.gray.dim('\u2500'.repeat(this.stream.columns || 80)); } write(str) { From d793bf3567e977c2f44959e969ca92a9b344af1b Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 15 Feb 2018 13:03:37 +0000 Subject: [PATCH 03/18] Set up reporter streams so they can be overridden in tests --- lib/reporters/tap.js | 9 +++++++-- lib/reporters/verbose.js | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index 8340ba1ae..b6c60fc2e 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -36,6 +36,11 @@ function dumpError(error, includeMessage) { class TapReporter { constructor() { this.i = 0; + + this.streams = { + stderr: process.stderr, + stdout: process.stdout + }; } start() { @@ -78,11 +83,11 @@ class TapReporter { } write(str) { - console.log(str); + this.streams.stdout.write(str + '\n'); } stdout(data) { - process.stderr.write(data); + this.streams.stderr.write(data); } stderr(data) { diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index 283955ea7..4850fc901 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -18,6 +18,8 @@ class VerboseReporter { for (const key of Object.keys(colors)) { colors[key].enabled = this.options.color; } + + this.stream = process.stderr; } start() { @@ -196,15 +198,15 @@ class VerboseReporter { } write(str) { - console.error(str); + this.stream.write(str + '\n'); } stdout(data) { - process.stderr.write(data); + this.stream.write(data); } stderr(data) { - process.stderr.write(data); + this.stream.write(data); } } From 08419826fe85882e00cd9a97013cc80909202c7b Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 15 Feb 2018 13:40:09 +0000 Subject: [PATCH 04/18] Add integration tests for reporters This replaces the previous reporter tests. Instead we run the reporter for varying test outputs and record the results. The environment is modified to ensure the highest fidelity colors and figures are used. Where necessary output is rewritten so the resulting logs can be compared across Node.js versions and operating systems. `./test/helpers/replay.js` can be used to replay a log file. Code coverage is still pretty good, though. I'm about to refactor the reporters so any missing coverage will be resolved at that point. --- package-lock.json | 1448 +++++++++++------ package.json | 1 + test/fixture/report/README.md | 3 + test/fixture/report/failfast/a.js | 3 + test/fixture/report/failfast/b.js | 3 + test/fixture/report/failfast/package.json | 1 + test/fixture/report/failfast2/a.js | 4 + test/fixture/report/failfast2/b.js | 3 + test/fixture/report/failfast2/package.json | 1 + test/fixture/report/only/a.js | 5 + test/fixture/report/only/b.js | 3 + test/fixture/report/only/package.json | 1 + test/fixture/report/regular/bad-test-chain.js | 3 + test/fixture/report/regular/package.json | 1 + test/fixture/report/regular/slow.js | 5 + test/fixture/report/regular/test.js | 50 + .../report/regular/uncaught-exception.js | 8 + .../report/regular/unhandled-rejection.js | 12 + test/fixture/report/watch/package.json | 1 + test/fixture/report/watch/test.js | 3 + test/helper/colors.js | 40 - test/helper/compare-line-output.js | 30 - test/helper/error-from-worker.js | 23 - test/helper/fix-reporter-env.js | 22 + test/helper/replay-report.js | 15 + test/helper/report-worker.js | 3 + test/helper/report.js | 151 ++ test/helper/tty-stream.js | 35 + test/reporters/format-serialized-error.js | 130 -- test/reporters/mini.failfast.log | 23 + test/reporters/mini.failfast2.log | 23 + test/reporters/mini.js | 1091 +------------ test/reporters/mini.only.log | 14 + test/reporters/mini.regular.log | 285 ++++ test/reporters/mini.watch.log | 31 + test/reporters/tap.failfast.log | 18 + test/reporters/tap.failfast2.log | 18 + test/reporters/tap.js | 333 +--- test/reporters/tap.only.log | 15 + test/reporters/tap.regular.log | 157 ++ test/reporters/verbose.failfast.log | 22 + test/reporters/verbose.failfast2.log | 22 + test/reporters/verbose.js | 1020 +----------- test/reporters/verbose.only.log | 10 + test/reporters/verbose.regular.log | 210 +++ test/reporters/verbose.watch.log | 27 + 46 files changed, 2291 insertions(+), 3036 deletions(-) create mode 100644 test/fixture/report/README.md create mode 100644 test/fixture/report/failfast/a.js create mode 100644 test/fixture/report/failfast/b.js create mode 100644 test/fixture/report/failfast/package.json create mode 100644 test/fixture/report/failfast2/a.js create mode 100644 test/fixture/report/failfast2/b.js create mode 100644 test/fixture/report/failfast2/package.json create mode 100644 test/fixture/report/only/a.js create mode 100644 test/fixture/report/only/b.js create mode 100644 test/fixture/report/only/package.json create mode 100644 test/fixture/report/regular/bad-test-chain.js create mode 100644 test/fixture/report/regular/package.json create mode 100644 test/fixture/report/regular/slow.js create mode 100644 test/fixture/report/regular/test.js create mode 100644 test/fixture/report/regular/uncaught-exception.js create mode 100644 test/fixture/report/regular/unhandled-rejection.js create mode 100644 test/fixture/report/watch/package.json create mode 100644 test/fixture/report/watch/test.js delete mode 100644 test/helper/colors.js delete mode 100644 test/helper/compare-line-output.js delete mode 100644 test/helper/error-from-worker.js create mode 100644 test/helper/fix-reporter-env.js create mode 100755 test/helper/replay-report.js create mode 100644 test/helper/report-worker.js create mode 100644 test/helper/report.js create mode 100644 test/helper/tty-stream.js delete mode 100644 test/reporters/format-serialized-error.js create mode 100644 test/reporters/mini.failfast.log create mode 100644 test/reporters/mini.failfast2.log create mode 100644 test/reporters/mini.only.log create mode 100644 test/reporters/mini.regular.log create mode 100644 test/reporters/mini.watch.log create mode 100644 test/reporters/tap.failfast.log create mode 100644 test/reporters/tap.failfast2.log create mode 100644 test/reporters/tap.only.log create mode 100644 test/reporters/tap.regular.log create mode 100644 test/reporters/verbose.failfast.log create mode 100644 test/reporters/verbose.failfast2.log create mode 100644 test/reporters/verbose.only.log create mode 100644 test/reporters/verbose.regular.log create mode 100644 test/reporters/verbose.watch.log diff --git a/package-lock.json b/package-lock.json index dfd7afdb9..d3cb0d614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -455,19 +455,12 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, "requires": { "kind-of": "3.2.2", "longest": "1.0.1", "repeat-string": "1.6.1" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -919,12 +912,6 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -1907,12 +1894,6 @@ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-1.0.0.tgz", "integrity": "sha1-zVL28HEuC6q5fW+XModPIvR3UsA=" }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -2119,12 +2100,6 @@ } } }, - "debug-log": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", - "dev": true - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -3248,47 +3223,6 @@ "repeat-string": "1.6.1" } }, - "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "mkdirp": "0.5.1", - "pkg-dir": "1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "1.1.2" - } - } - } - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -3783,29 +3717,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", - "dev": true, - "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, "har-schema": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", @@ -4505,95 +4416,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz", - "integrity": "sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==", - "dev": true, - "requires": { - "append-transform": "0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", - "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", - "dev": true, - "requires": { - "babel-generator": "6.26.1", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.2.0", - "semver": "5.5.0" - }, - "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.3.tgz", - "integrity": "sha512-D4jVbMDtT2dPmloPJS/rmeP626N5Pr3Rp+SovrPn1+zPChGHcggd/0sL29jnbm4oK9W0wHjCRsdch9oLd7cm6g==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz", - "integrity": "sha512-fDa0hwU/5sDXwAklXgAoCJCOsFsBplVQ6WBldz5UwaqOzmDhUK4nfuR7/G//G2lERlblUNJB8P6e8cXq3a7MlA==", - "dev": true, - "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "rimraf": "2.6.1", - "source-map": "0.5.6" - } - }, - "istanbul-reports": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", - "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", - "dev": true, - "requires": { - "handlebars": "4.0.11" - } - }, "jest-docblock": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", @@ -4920,8 +4742,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "loose-envify": { "version": "1.3.1", @@ -5098,23 +4919,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "merge2": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.1.tgz", @@ -5532,25 +5336,141 @@ "yargs-parser": "8.1.0" }, "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true + }, + "arr-union": { + "version": "3.1.0", + "bundled": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "bundled": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "atob": { + "version": "2.0.3", + "bundled": true + }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base": { + "version": "0.11.2", + "bundled": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true + }, + "cache-base": { + "version": "1.0.1", + "bundled": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true + } + } + }, "camelcase": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "bundled": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } }, "chalk": { "version": "1.1.3", @@ -5564,48 +5484,190 @@ "supports-color": "2.0.0" } }, - "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" - }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "requires": { - "lru-cache": "4.0.2", - "which": "1.2.14" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "class-utils": { + "version": "0.3.6", + "bundled": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, "isobject": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "bundled": true + }, + "kind-of": { + "version": "5.1.0", + "bundled": true } } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "cliui": { + "version": "2.1.0", + "bundled": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "bundled": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "bundled": true + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "4.1.2", + "which": "1.2.14" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "define-property": { + "version": "2.0.2", + "bundled": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true + } + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, "requires": { "cross-spawn": "5.1.0", "get-stream": "3.0.0", @@ -5618,20 +5680,33 @@ "dependencies": { "cross-spawn": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "bundled": true, "requires": { - "lru-cache": "4.0.2", + "lru-cache": "4.1.2", "shebang-command": "1.2.0", "which": "1.2.14" } } } }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "requires": { + "fill-range": "2.2.3" + } + }, "extend-shallow": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "bundled": true, "requires": { "assign-symbols": "1.0.0", "is-extendable": "1.0.1" @@ -5639,54 +5714,505 @@ "dependencies": { "is-extendable": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "bundled": true, "requires": { "is-plain-object": "2.0.4" } } } }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fragment-cache": { + "version": "0.2.1", + "bundled": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "get-value": { + "version": "2.0.6", + "bundled": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "requires": { + "is-glob": "2.0.1" + } + }, "globals": { "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + "bundled": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } }, "has-ansi": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "bundled": true, "requires": { "ansi-regex": "2.1.1" } }, "has-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "bundled": true, + "dev": true }, - "invariant": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.3.tgz", - "integrity": "sha512-7Z5PPegwDTyjbaeCnV0efcyS6vdKAU51kpEmS7QFib3P4822l8ICYyMn7qvJnc+WzLoDsuI9gPMKbJ8pCu8XtA==", + "has-value": { + "version": "1.0.0", + "bundled": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true + } + } + }, + "has-values": { + "version": "1.0.0", + "bundled": true, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, "requires": { - "loose-envify": "1.3.1" + "builtin-modules": "1.1.1" } }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true + } + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-odd": { + "version": "2.0.0", + "bundled": true, + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "bundled": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true + }, + "is-windows": { + "version": "1.0.2", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.0", + "semver": "5.5.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.3", + "bundled": true, + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.2.0", + "mkdirp": "0.5.1", + "rimraf": "2.6.1", + "source-map": "0.5.6" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true + }, "jsesc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + "bundled": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-buffer": "1.1.6" + } }, "lazy-cache": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + "bundled": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "1.0.0" + } }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "bundled": true, "requires": { "graceful-fs": "4.1.11", "parse-json": "2.2.0", @@ -5695,24 +6221,115 @@ "strip-bom": "2.0.0" } }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, "lodash": { "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "bundled": true + }, + "longest": { + "version": "1.0.1", + "bundled": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.2", + "bundled": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "bundled": true + }, + "map-visit": { + "version": "1.0.0", + "bundled": true, + "requires": { + "object-visit": "1.0.1" + } }, "md5-hex": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", - "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", + "bundled": true, "dev": true, "requires": { "md5-o-matic": "0.1.1" } }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, "mimic-fn": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "ms": { "version": "2.0.0", @@ -5723,6 +6340,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, "requires": { "pinkie-promise": "2.0.1" } @@ -5741,6 +6359,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, "requires": { "find-up": "1.1.2" }, @@ -5749,36 +6368,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.3.8", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, "requires": { "path-exists": "2.1.0", "pinkie-promise": "2.0.1" @@ -5788,8 +6378,7 @@ }, "remove-trailing-separator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "bundled": true }, "resolve-from": { "version": "2.0.0", @@ -5797,23 +6386,6 @@ "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", "dev": true }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-license-ids": "3.0.0" - } - }, "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", @@ -5855,34 +6427,10 @@ "safe-regex": "1.1.0" } }, - "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "requires": { - "kind-of": "6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, "validate-npm-package-license": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" - } - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==" }, "write-file-atomic": { "version": "1.3.4", @@ -5893,81 +6441,6 @@ "imurmurhash": "0.1.4", "slide": "1.1.6" } - }, - "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", - "dev": true, - "requires": { - "cliui": "4.0.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.0.0", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - } - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - } - } } } }, @@ -6849,6 +7322,12 @@ "is-finite": "1.0.2" } }, + "replace-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/replace-string/-/replace-string-1.1.0.tgz", + "integrity": "sha1-hwYhF/gj/lgAwwa6yyz6NZuTX+o=", + "dev": true + }, "request": { "version": "2.85.0", "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", @@ -7104,8 +7583,6 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, "requires": { "align-text": "0.1.4" } @@ -8019,7 +8496,7 @@ "dev": true, "requires": { "arrify": "1.0.1", - "micromatch": "3.1.9", + "micromatch": "3.1.10", "object-assign": "4.1.1", "read-pkg-up": "1.0.1", "require-main-filename": "1.0.1" @@ -8038,34 +8515,23 @@ "dev": true }, "braces": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", - "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { "arr-flatten": "1.1.0", "array-unique": "0.3.2", - "define-property": "1.0.0", "extend-shallow": "2.0.1", "fill-range": "4.0.0", "isobject": "3.0.1", - "kind-of": "6.0.2", "repeat-element": "1.1.2", "snapdragon": "0.8.1", "snapdragon-node": "2.1.1", "split-string": "3.1.0", - "to-regex": "3.0.1" + "to-regex": "3.0.2" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -8108,7 +8574,7 @@ "posix-character-classes": "0.1.1", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -8182,7 +8648,7 @@ "fragment-cache": "0.2.1", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -8324,14 +8790,14 @@ } }, "micromatch": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.9.tgz", - "integrity": "sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { "arr-diff": "4.0.0", "array-unique": "0.3.2", - "braces": "2.3.1", + "braces": "2.3.2", "define-property": "2.0.2", "extend-shallow": "3.0.2", "extglob": "2.0.4", @@ -8341,7 +8807,7 @@ "object.pick": "1.3.0", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" } }, "ms": { @@ -8399,6 +8865,30 @@ "requires": { "is-utf8": "0.2.1" } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + }, + "dependencies": { + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + } + } } } }, @@ -8663,6 +9153,28 @@ "source-map": "0.5.6", "uglify-to-browserify": "1.0.2", "yargs": "3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } } }, "uglify-to-browserify": { @@ -9061,7 +9573,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "1.0.2", "strip-ansi": "3.0.1" @@ -9071,7 +9582,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -9080,7 +9590,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -9091,7 +9600,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -9368,24 +9876,56 @@ "dev": true }, "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, - "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", + "cliui": "4.0.0", "decamelize": "1.2.0", - "window-size": "0.1.0" + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.0.0", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" }, "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "cliui": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", + "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", "dev": true, - "optional": true + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } } } }, diff --git a/package.json b/package.json index 7e6a36ecd..b4a934708 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,7 @@ "proxyquire": "^2.0.1", "react": "^16.3.0", "react-test-renderer": "^16.3.0", + "replace-string": "^1.1.0", "signal-exit": "^3.0.0", "sinon": "^4.5.0", "source-map-fixtures": "^2.1.0", diff --git a/test/fixture/report/README.md b/test/fixture/report/README.md new file mode 100644 index 000000000..a3211c862 --- /dev/null +++ b/test/fixture/report/README.md @@ -0,0 +1,3 @@ +To run these tests directly, `cd` into the respective directories and invoke via `node ../../../../cli.js`. + +Note that the `report` helper sorts the test files before running tests. diff --git a/test/fixture/report/failfast/a.js b/test/fixture/report/failfast/a.js new file mode 100644 index 000000000..9ca43678f --- /dev/null +++ b/test/fixture/report/failfast/a.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test('fails', t => t.fail()); diff --git a/test/fixture/report/failfast/b.js b/test/fixture/report/failfast/b.js new file mode 100644 index 000000000..a20cc6d16 --- /dev/null +++ b/test/fixture/report/failfast/b.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test('passes', t => t.pass()); diff --git a/test/fixture/report/failfast/package.json b/test/fixture/report/failfast/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/report/failfast/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/report/failfast2/a.js b/test/fixture/report/failfast2/a.js new file mode 100644 index 000000000..cb8c65a8f --- /dev/null +++ b/test/fixture/report/failfast2/a.js @@ -0,0 +1,4 @@ +import test from '../../../..'; + +test('fails', t => t.fail()); +test('passes', t => t.pass()); diff --git a/test/fixture/report/failfast2/b.js b/test/fixture/report/failfast2/b.js new file mode 100644 index 000000000..a20cc6d16 --- /dev/null +++ b/test/fixture/report/failfast2/b.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test('passes', t => t.pass()); diff --git a/test/fixture/report/failfast2/package.json b/test/fixture/report/failfast2/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/report/failfast2/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/report/only/a.js b/test/fixture/report/only/a.js new file mode 100644 index 000000000..b1894d947 --- /dev/null +++ b/test/fixture/report/only/a.js @@ -0,0 +1,5 @@ +import test from '../../../..'; + +test.only('only', t => t.pass()); + +test('passes', t => t.pass()); diff --git a/test/fixture/report/only/b.js b/test/fixture/report/only/b.js new file mode 100644 index 000000000..a20cc6d16 --- /dev/null +++ b/test/fixture/report/only/b.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test('passes', t => t.pass()); diff --git a/test/fixture/report/only/package.json b/test/fixture/report/only/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/report/only/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/report/regular/bad-test-chain.js b/test/fixture/report/regular/bad-test-chain.js new file mode 100644 index 000000000..5535a7fa7 --- /dev/null +++ b/test/fixture/report/regular/bad-test-chain.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test.serial.test('passes', t => t.pass()); diff --git a/test/fixture/report/regular/package.json b/test/fixture/report/regular/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/report/regular/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/report/regular/slow.js b/test/fixture/report/regular/slow.js new file mode 100644 index 000000000..9801c480f --- /dev/null +++ b/test/fixture/report/regular/slow.js @@ -0,0 +1,5 @@ +import test from '../../../..'; + +test.cb('slow', t => { + setTimeout(t.end, 200); +}); diff --git a/test/fixture/report/regular/test.js b/test/fixture/report/regular/test.js new file mode 100644 index 000000000..4825784ba --- /dev/null +++ b/test/fixture/report/regular/test.js @@ -0,0 +1,50 @@ +import test from '../../../..'; + +console.log('stdout'); +console.error('stderr'); + +test('passes', t => t.pass()); + +test.todo('todo'); + +test.skip('skip', t => t.pass()); + +test('fails', t => t.fail()); + +test.failing('known failure', t => t.fail()); + +test.failing('no longer failing', t => t.pass()); + +test('logs', t => { + t.log('hello'); + t.log('world'); + t.fail(); +}); + +test('formatted', t => { + t.deepEqual('foo', 'bar'); +}); + +test('power-assert', t => { + const foo = 'bar'; + t.falsy(foo); +}); + +test('bad throws', t => { + const fn = () => { + throw new Error('err'); + }; + t.throws(fn()); +}); + +test('bad notThrows', t => { + const fn = () => { + throw new Error('err'); + }; + t.notThrows(fn()); +}); + +test('implementation throws non-error', () => { + const err = null; + throw err; +}); diff --git a/test/fixture/report/regular/uncaught-exception.js b/test/fixture/report/regular/uncaught-exception.js new file mode 100644 index 000000000..6a2a2c306 --- /dev/null +++ b/test/fixture/report/regular/uncaught-exception.js @@ -0,0 +1,8 @@ +import test from '../../../..'; + +test('passes', t => { + setTimeout(() => { + throw new Error('Can\'t catch me'); + }); + t.pass(); +}); diff --git a/test/fixture/report/regular/unhandled-rejection.js b/test/fixture/report/regular/unhandled-rejection.js new file mode 100644 index 000000000..0ec2c15c1 --- /dev/null +++ b/test/fixture/report/regular/unhandled-rejection.js @@ -0,0 +1,12 @@ +import test from '../../../..'; + +test('passes', t => { + Promise.reject(new Error('Can\'t catch me')); + t.pass(); +}); + +test('unhandled non-error rejection', t => { + const err = null; + Promise.reject(err); + t.pass(); +}); diff --git a/test/fixture/report/watch/package.json b/test/fixture/report/watch/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixture/report/watch/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixture/report/watch/test.js b/test/fixture/report/watch/test.js new file mode 100644 index 000000000..a20cc6d16 --- /dev/null +++ b/test/fixture/report/watch/test.js @@ -0,0 +1,3 @@ +import test from '../../../..'; + +test('passes', t => t.pass()); diff --git a/test/helper/colors.js b/test/helper/colors.js deleted file mode 100644 index bce6ddb28..000000000 --- a/test/helper/colors.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * This module is maintained to promote separation between the tests and the - * implementation. - */ -'use strict'; - -const ansiStyles = require('ansi-styles'); - -function make(name) { - const style = ansiStyles[name]; - return function (string) { - return style.open + string + style.close; - }; -} -const bold = make('bold'); -const white = make('white'); -const gray = make('gray'); - -// The following color definitions are contextual so that they produce expected -// values which mimic the behavior of the Chalk library. -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); -const openDim = isSimpleWindowsTerm ? '' : ansiStyles.dim.open; -const openBlue = isSimpleWindowsTerm ? '\u001B[94m' : ansiStyles.blue.open; -// "Use `bold` by default on Windows" -// https://github.com/chalk/chalk/issues/36 -const blue = string => openBlue + string + ansiStyles.blue.close; -// "(Windows) chalk.gray.dim not visible" -// https://github.com/chalk/chalk/issues/58 -const dimGray = string => gray(openDim + string + ansiStyles.dim.close); - -module.exports = { - blue, - boldWhite: string => bold(white(string)), - dimGray, - gray, - green: make('green'), - magenta: make('magenta'), - red: make('red'), - yellow: make('yellow') -}; diff --git a/test/helper/compare-line-output.js b/test/helper/compare-line-output.js deleted file mode 100644 index d3cddc254..000000000 --- a/test/helper/compare-line-output.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; -const SKIP_UNTIL_EMPTY_LINE = Symbol('SKIP_UNTIL_EMPTY_LINE'); - -function compareLineOutput(t, actual, lineExpectations) { - const actualLines = actual.split('\n'); - let expectationIndex = 0; - let lineIndex = 0; - - while (lineIndex < actualLines.length && expectationIndex < lineExpectations.length) { - const line = actualLines[lineIndex++]; - const expected = lineExpectations[expectationIndex++]; - - if (expected === SKIP_UNTIL_EMPTY_LINE) { - lineIndex = actualLines.indexOf('', lineIndex); - continue; - } - - if (typeof expected === 'string') { - // Assertion titles use 1-based line indexes - t.is(line, expected, `line ${lineIndex} ≪${line}≫ is ≪${expected}≫`); - } else { - t.match(line, expected, `line ${lineIndex} ≪${line}≫ matches ${expected}`); - } - } - - t.is(lineIndex, actualLines.length, `Compared ${lineIndex} of ${actualLines.length} lines`); -} - -module.exports = compareLineOutput; -compareLineOutput.SKIP_UNTIL_EMPTY_LINE = SKIP_UNTIL_EMPTY_LINE; diff --git a/test/helper/error-from-worker.js b/test/helper/error-from-worker.js deleted file mode 100644 index 18c0be9b7..000000000 --- a/test/helper/error-from-worker.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const serializeError = require('../../lib/serialize-error'); - -module.exports = function (err, options) { - options = Object.assign({}, options); - - if (options.stack) { - err.stack = options.stack; - } - - const serialized = serializeError(err); - - if (options.type) { - serialized.type = options.type; - } - - if (options.file) { - serialized.file = options.file; - } - - return serialized; -}; diff --git a/test/helper/fix-reporter-env.js b/test/helper/fix-reporter-env.js new file mode 100644 index 000000000..79c6c41cb --- /dev/null +++ b/test/helper/fix-reporter-env.js @@ -0,0 +1,22 @@ +'use strict'; +const lolex = require('lolex'); + +const fixColors = () => { + // Force consistent and high-fidelity logs. + process.env.FORCE_COLOR = 3; + Object.defineProperty(process, 'platform', {value: 'darwin', enumerable: true, configurable: true}); +}; + +module.exports = () => { + // Fix timestamps. + lolex.install({ + now: new Date(2014, 11, 19, 17, 19, 12, 200).getTime(), + toFake: [ + 'Date' + ] + }); + + fixColors(); +}; + +module.exports.onlyColors = fixColors; diff --git a/test/helper/replay-report.js b/test/helper/replay-report.js new file mode 100755 index 000000000..5e4a951fc --- /dev/null +++ b/test/helper/replay-report.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +'use strict'; +/* eslint-disable no-await-in-loop */ +const fs = require('fs'); +const TTYStream = require('./tty-stream'); + +const lines = fs.readFileSync(process.argv[2], 'utf8').split(TTYStream.SEPARATOR.toString('utf8')); +const delay = () => new Promise(resolve => setTimeout(resolve, 1000)); + +(async () => { + while (lines.length > 0) { + process.stdout.write(lines.shift()); + await delay(); + } +})(); diff --git a/test/helper/report-worker.js b/test/helper/report-worker.js new file mode 100644 index 000000000..08dcd2544 --- /dev/null +++ b/test/helper/report-worker.js @@ -0,0 +1,3 @@ +'use strict'; +require('./fix-reporter-env').onlyColors(); +require('../../lib/test-worker'); // eslint-disable-line import/no-unassigned-import diff --git a/test/helper/report.js b/test/helper/report.js new file mode 100644 index 000000000..82e9197f7 --- /dev/null +++ b/test/helper/report.js @@ -0,0 +1,151 @@ +'use strict'; +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const globby = require('globby'); +const proxyquire = require('proxyquire'); +const replaceString = require('replace-string'); +const Logger = require('../../lib/logger'); + +let _Api = null; +const createApi = options => { + if (!_Api) { + _Api = proxyquire('../../api', { + './lib/fork': proxyquire('../../lib/fork', { + child_process: Object.assign({}, childProcess, { // eslint-disable-line camelcase + fork(filename, argv, options) { + return childProcess.fork(path.join(__dirname, 'report-worker.js'), argv, options); + } + }) + }) + }); + } + + return new _Api(options); +}; + +// At least in Appveyor with Node.js 6, IPC can overtake stdout/stderr +let hasReliableStdIO = true; +exports.captureStdIOReliability = () => { + if (process.platform === 'win32' && parseInt(process.versions.node, 10) < 8) { + hasReliableStdIO = false; + } +}; + +exports.assert = (t, logFile, buffer, stripOptions) => { + let existing = null; + try { + existing = fs.readFileSync(logFile); + } catch (err) {} + if (existing === null || process.env.UPDATE_REPORTER_LOG) { + fs.writeFileSync(logFile, buffer); + existing = buffer; + } + + let expected = existing.toString('utf8'); + // At least in Appveyor with Node.js 6, IPC can overtake stdout/stderr. This + // causes the reporter to emit in a different order, resulting in a test + // failure. "Fix" by not asserting on the stdout/stderr reporting at all. + if (stripOptions.stripStdIO && !hasReliableStdIO) { + expected = expected.replace(/(---tty-stream-chunk-separator\n)(stderr|stdout)\n/g, stripOptions.alsoStripSeparator ? '' : '$1'); + } + + const actual = buffer.toString('utf8'); + if (actual === expected) { + t.pass(); + } else { + // Log the entire actual and expected values, so they can be diffed + // manually. TAP's diff output is really confusing in this situation. + console.dir({actual, expected}); + t.fail('Output did not match expectation'); + } +}; + +exports.sanitizers = { + cwd: str => replaceString(str, process.cwd(), '~'), + posix: str => replaceString(str, '\\', '/'), + slow: str => str.replace(/(slow.+?)\(\d+m?s\)/g, '$1 (000ms)'), + // At least in Appveyor with Node.js 6, IPC can overtake stdout/stderr. This + // causes the reporter to emit in a different order, resulting in a test + // failure. "Fix" by not asserting on the stdout/stderr reporting at all. + unreliableProcessIO(str) { + if (hasReliableStdIO) { + return str; + } + return str === 'stdout\n' || str === 'stderr\n' ? '' : str; + } +}; + +const run = (type, reporter) => { + const projectDir = path.join(__dirname, '../fixture/report', type.toLowerCase()); + + const api = createApi({ + failFast: type === 'failFast' || type === 'failFast2', + failWithoutAssertions: false, + serial: type === 'failFast' || type === 'failFast2', + require: [], + cacheEnable: true, + compileEnhancements: true, + explicitTitles: type === 'watch', + match: [], + babelConfig: {testOptions: {}}, + resolveTestsFrom: projectDir, + projectDir, + timeout: undefined, + concurrency: 1, + updateSnapshots: false, + snapshotDir: false, + color: true + }); + + reporter.api = api; + const logger = new Logger(reporter); + logger.start(); + + api.on('test-run', runStatus => { + reporter.api = runStatus; + runStatus.on('test', logger.test); + runStatus.on('error', logger.unhandledError); + + runStatus.on('stdout', logger.stdout); + runStatus.on('stderr', logger.stderr); + }); + + const files = globby.sync('*.js', {cwd: projectDir}).sort(); + if (type !== 'watch') { + return api.run(files).then(runStatus => { + logger.finish(runStatus); + }); + } + + // Mimick watch mode + return api.run(files).then(runStatus => { + logger.finish(runStatus); + + // Don't clear + logger.reset(); + logger.section(); + logger.reset(); + logger.start(); + + return api.run(files); + }).then(runStatus => { + runStatus.previousFailCount = 2; + logger.finish(runStatus); + + // Clear + logger.clear(); + logger.reset(); + logger.start(); + + return api.run(files); + }).then(runStatus => { + logger.finish(runStatus); + }); +}; + +exports.regular = reporter => run('regular', reporter); +exports.failFast = reporter => run('failFast', reporter); +exports.failFast2 = reporter => run('failFast2', reporter); +exports.only = reporter => run('only', reporter); +exports.watch = reporter => run('watch', reporter); diff --git a/test/helper/tty-stream.js b/test/helper/tty-stream.js new file mode 100644 index 000000000..d4e4dc31f --- /dev/null +++ b/test/helper/tty-stream.js @@ -0,0 +1,35 @@ +'use strict'; +const stream = require('stream'); + +class TTYStream extends stream.Writable { + constructor(options) { + super(); + + this.isTTY = true; + this.columns = options.columns; + + this.sanitizers = options.sanitizers || []; + this.chunks = []; + } + + _write(chunk, encoding, callback) { + const str = this.sanitizers.reduce((str, sanitizer) => sanitizer(str), chunk.toString('utf8')); + // Ignore the chunk if it was scrubbed completely. Still count 0-length + // chunks. + if (str !== '' || chunk.length === 0) { + this.chunks.push( + Buffer.from(str, 'utf8'), + TTYStream.SEPARATOR + ); + } + callback(); + } + + asBuffer() { + return Buffer.concat(this.chunks); + } +} + +TTYStream.SEPARATOR = Buffer.from('---tty-stream-chunk-separator\n', 'utf8'); + +module.exports = TTYStream; diff --git a/test/reporters/format-serialized-error.js b/test/reporters/format-serialized-error.js deleted file mode 100644 index bb971c11f..000000000 --- a/test/reporters/format-serialized-error.js +++ /dev/null @@ -1,130 +0,0 @@ -'use strict'; -const chalk = require('chalk'); -const concordance = require('concordance'); -const test = require('tap').test; -const formatSerializedError = require('../../lib/reporters/format-serialized-error'); - -test('indicates message should not be printed if it is empty', t => { - const err = { - message: '', - statements: [], - values: [{label: '', formatted: ''}] - }; - t.false(formatSerializedError(err).printMessage); - t.end(); -}); - -test('indicates message should not be printed if the first value label starts with the message', t => { - const err = { - message: 'foo', - statements: [], - values: [{label: 'foobar', formatted: ''}] - }; - t.false(formatSerializedError(err).printMessage); - t.end(); -}); - -test('indicates message should be printed if not empty and the first value label does not start with the message', t => { - const err = { - message: 'foo', - statements: [], - values: [{label: 'barfoo', formatted: ''}] - }; - t.true(formatSerializedError(err).printMessage); - t.end(); -}); - -test('print multiple values', t => { - const err = { - statements: [], - values: [ - { - label: 'Actual:', - formatted: concordance.format([1, 2, 3]) - }, - { - label: 'Expected:', - formatted: concordance.format({a: 1, b: 2, c: 3}) - } - ] - }; - - t.is(formatSerializedError(err).formatted, [ - 'Actual:\n', - `${err.values[0].formatted}\n`, - 'Expected:\n', - err.values[1].formatted - ].join('\n')); - t.end(); -}); - -test('print single value', t => { - const err = { - statements: [], - values: [ - { - label: 'Actual:', - formatted: concordance.format([1, 2, 3]) - } - ] - }; - - t.is(formatSerializedError(err).formatted, [ - 'Actual:\n', - err.values[0].formatted - ].join('\n')); - t.end(); -}); - -test('print multiple statements', t => { - const err = { - statements: [ - ['actual.a[0]', concordance.format(1)], - ['actual.a', concordance.format([1])], - ['actual', concordance.format({a: [1]})] - ], - values: [] - }; - - t.is(formatSerializedError(err).formatted, [ - `actual.a[0]\n${chalk.grey('=>')} ${concordance.format(1)}`, - `actual.a\n${chalk.grey('=>')} ${concordance.format([1])}`, - `actual\n${chalk.grey('=>')} ${concordance.format({a: [1]})}` - ].join('\n\n')); - t.end(); -}); - -test('print single statement', t => { - const err = { - statements: [ - ['actual.a[0]', concordance.format(1)] - ], - values: [] - }; - - t.is(formatSerializedError(err).formatted, [ - `actual.a[0]\n${chalk.grey('=>')} ${concordance.format(1)}` - ].join('\n\n')); - t.end(); -}); - -test('print statements after values', t => { - const err = { - statements: [ - ['actual.a[0]', concordance.format(1)] - ], - values: [ - { - label: 'Actual:', - formatted: concordance.format([1, 2, 3]) - } - ] - }; - - t.is(formatSerializedError(err).formatted, [ - 'Actual:', - `${err.values[0].formatted}`, - `actual.a[0]\n${chalk.grey('=>')} ${concordance.format(1)}` - ].join('\n\n')); - t.end(); -}); diff --git a/test/reporters/mini.failfast.log b/test/reporters/mini.failfast.log new file mode 100644 index 000000000..6f100294c --- /dev/null +++ b/test/reporters/mini.failfast.log @@ -0,0 +1,23 @@ +---tty-stream-chunk-separator + + a › fails + + 1 failed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 1 failed + + a › fails + + ~/test/fixture/report/failfast/a.js:3 + + 2: +  3: test('fails', t => t.fail()); + 4: + + Test failed via `t.fail()` + + + + `--fail-fast` is on. 1 test file was skipped. +---tty-stream-chunk-separator diff --git a/test/reporters/mini.failfast2.log b/test/reporters/mini.failfast2.log new file mode 100644 index 000000000..8d1fbb174 --- /dev/null +++ b/test/reporters/mini.failfast2.log @@ -0,0 +1,23 @@ +---tty-stream-chunk-separator + + a › fails + + 1 failed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 1 failed + + a › fails + + ~/test/fixture/report/failfast2/a.js:3 + + 2: +  3: test('fails', t => t.fail());  + 4: test('passes', t => t.pass()); + + Test failed via `t.fail()` + + + + `--fail-fast` is on. At least 1 test was skipped, as well as 1 test file. +---tty-stream-chunk-separator diff --git a/test/reporters/mini.js b/test/reporters/mini.js index 3d62a1d2e..800a550db 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -1,1068 +1,43 @@ 'use strict'; -require('../../lib/worker-options').set({}); +require('../helper/report').captureStdIOReliability(); +require('../helper/fix-reporter-env')(); -// These tests are run as a sub-process of the `tap` module, so the standard -// output stream will not be recognized as a text terminal. AVA internals are -// sensitive to this detail and respond by automatically disable output -// coloring. Because the tests are written verify AVA's behavior in text -// terminals, that environment should be simulated prior to loading any -// modules. -process.stdout.isTTY = true; - -const indentString = require('indent-string'); -const tempWrite = require('temp-write'); -const flatten = require('arr-flatten'); -const figures = require('figures'); -const sinon = require('sinon'); +const path = require('path'); const test = require('tap').test; -const cross = require('figures').cross; -const lolex = require('lolex'); -const AvaError = require('../../lib/ava-error'); -const AssertionError = require('../../lib/assert').AssertionError; +const TTYStream = require('../helper/tty-stream'); +const report = require('../helper/report'); const MiniReporter = require('../../lib/reporters/mini'); -const colors = require('../helper/colors'); -const compareLineOutput = require('../helper/compare-line-output'); -const errorFromWorker = require('../helper/error-from-worker'); -const codeExcerpt = require('../../lib/code-excerpt'); - -const graySpinner = colors.dimGray(process.platform === 'win32' ? '-' : '⠋'); -const stackLineRegex = /.+ \(.+:\d+:\d+\)/; - -// Needed because tap doesn't emulate a tty environment and thus this is -// `undefined`, making `cli-truncate` append '...' to test titles -process.stdout.columns = 5000; -const fullWidthLine = colors.dimGray('\u2500'.repeat(5000)); - -function miniReporter(options) { - if (options === undefined) { - options = {color: true}; - } - const reporter = new MiniReporter(options); - reporter.start = () => ''; - return reporter; -} - -function source(file, line) { - return { - file, - line: line || 1, - isWithinProject: true, - isDependency: false - }; -} - -process.stderr.setMaxListeners(50); - -test('start', t => { - const reporter = new MiniReporter({color: true}); - - t.is(reporter.start(), ' \n ' + graySpinner + ' '); - reporter.clearInterval(); - t.end(); -}); - -test('passing test', t => { - const reporter = miniReporter(); - - const actualOutput = reporter.test({ - title: 'passed' - }); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' passed', - '', - ' ' + colors.green('1 passed') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('known failure test', t => { - const reporter = miniReporter(); - - const actualOutput = reporter.test({ - title: 'known failure', - failing: true - }); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' ' + colors.red('known failure'), - '', - ' ' + colors.green('1 passed'), - ' ' + colors.red('1 known failure') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failing test', t => { - const reporter = miniReporter(); - const actualOutput = reporter.test({ - title: 'failed', - error: { - message: 'assertion failed' - } - }); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' ' + colors.red('failed'), - '', - ' ' + colors.red('1 failed') - ].join('\n'); +const run = type => t => { + t.plan(1); - t.is(actualOutput, expectedOutput); - t.end(); -}); + const logFile = path.join(__dirname, `mini.${type.toLowerCase()}.log`); -test('failed known failure test', t => { - const reporter = miniReporter(); - - const actualOutput = reporter.test({ - title: 'known failure', - failing: true, - error: { - message: 'Test was expected to fail, but succeeded, you should stop marking the test as failing' - } + const tty = new TTYStream({ + columns: 200, + sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.unreliableProcessIO] }); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' ' + colors.red('known failure'), - '', - ' ' + colors.red('1 failed') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('passing test after failing', t => { - const reporter = miniReporter(); - - reporter.test({ - title: 'failed', - error: { - message: 'assertion failed' + const reporter = Object.assign(new MiniReporter({color: true, watching: type === 'watch'}), { + stream: tty, + // Disable the spinner. + start() { + return ''; + }, + spinnerChar() { + return ' '; } }); - - const actualOutput = reporter.test({title: 'passed'}); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' passed', - '', - ' ' + colors.green('1 passed'), - ' ' + colors.red('1 failed') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failing test after passing', t => { - const reporter = miniReporter(); - - reporter.test({title: 'passed'}); - - const actualOutput = reporter.test({ - title: 'failed', - error: { - message: 'assertion failed' - } - }); - - const expectedOutput = [ - ' ', - ' ' + graySpinner + ' ' + colors.red('failed'), - '', - ' ' + colors.green('1 passed'), - ' ' + colors.red('1 failed') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('skipped test', t => { - const reporter = miniReporter(); - - const output = reporter.test({ - title: 'skipped', - skip: true - }); - - t.false(output); - t.end(); -}); - -test('todo test', t => { - const reporter = miniReporter(); - - const output = reporter.test({ - title: 'todo', - skip: true, - todo: true - }); - - t.false(output); - t.end(); -}); - -test('results with passing tests', t => { - const reporter = miniReporter(); - reporter.passCount = 1; - reporter.failCount = 0; - - const actualOutput = reporter.finish({}); - const expectedOutput = `\n ${colors.green('1 passed')}\n`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with passing known failure tests', t => { - const reporter = miniReporter(); - reporter.passCount = 1; - reporter.knownFailureCount = 1; - reporter.failCount = 0; - - const runStatus = { - knownFailures: [{ - title: 'known failure', - failing: true - }] - }; - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.green('1 passed'), - '\n ' + colors.red('1 known failure'), - '\n', - '\n ' + colors.boldWhite('known failure'), - '\n' - ].join(''); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with skipped tests', t => { - const reporter = miniReporter(); - reporter.passCount = 0; - reporter.skipCount = 1; - reporter.failCount = 0; - - const actualOutput = reporter.finish({}); - const expectedOutput = `\n ${colors.yellow('1 skipped')}\n`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with todo tests', t => { - const reporter = miniReporter(); - reporter.passCount = 0; - reporter.todoCount = 1; - reporter.failCount = 0; - - const actualOutput = reporter.finish({}); - const expectedOutput = `\n ${colors.blue('1 todo')}\n`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with passing skipped tests', t => { - const reporter = miniReporter(); - reporter.passCount = 1; - reporter.skipCount = 1; - - const output = reporter.finish({}).split('\n'); - - t.is(output[0], ''); - t.is(output[1], ' ' + colors.green('1 passed')); - t.is(output[2], ' ' + colors.yellow('1 skipped')); - t.is(output[3], ''); - t.end(); -}); - -test('results with passing tests and rejections', t => { - const reporter = miniReporter(); - reporter.passCount = 1; - reporter.rejectionCount = 1; - - const err1 = errorFromWorker(new Error('failure one'), { - file: 'test.js', - type: 'rejection' - }); - const err2 = errorFromWorker(new Error('failure two'), { - file: 'test.js', - type: 'rejection', - stack: 'Error: failure two\n at trailingWhitespace (test.js:1:1)\r\n' - }); - - const runStatus = { - errors: [err1, err2] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.green('1 passed'), - ' ' + colors.red('1 rejection'), - '', - ' ' + colors.boldWhite('Unhandled rejection in test.js'), - /Error: failure one/, - /test\/reporters\/mini\.js/, - compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - ' ' + colors.boldWhite('Unhandled rejection in test.js'), - /Error: failure two/, - /trailingWhitespace/, - '' - ]); - t.end(); -}); - -test('results with passing tests and exceptions', t => { - const reporter = miniReporter(); - reporter.passCount = 1; - reporter.exceptionCount = 2; - - const err = errorFromWorker(new Error('failure'), { - file: 'test.js', - type: 'exception' - }); - - const avaErr = errorFromWorker(new AvaError('A futuristic test runner'), { - file: 'test.js', - type: 'exception' - }); - - const runStatus = { - errors: [err, avaErr] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.green('1 passed'), - ' ' + colors.red('2 exceptions'), - '', - ' ' + colors.boldWhite('Uncaught exception in test.js'), - /Error: failure/, - /test\/reporters\/mini\.js/, - compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - ' ' + colors.red(cross + ' A futuristic test runner'), - '' - ]); - t.end(); -}); - -test('results with errors', t => { - const err1 = errorFromWorker(new AssertionError({message: 'failure one'})); - const err1Path = tempWrite.sync('a();'); - err1.source = source(err1Path); - err1.statements = []; - err1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc') + '\n'}, - {label: 'expected:', formatted: JSON.stringify('abd') + '\n'} - ]; - - const err2 = errorFromWorker(new AssertionError({message: 'failure two'}), { - stack: 'error message\nTest.fn (test.js:1:1)' - }); - const err2Path = tempWrite.sync('b();'); - err2.source = source(err2Path); - err2.statements = []; - err2.values = [ - {label: 'actual:', formatted: JSON.stringify([1]) + '\n'}, - {label: 'expected:', formatted: JSON.stringify([2]) + '\n'} - ]; - - const err3 = errorFromWorker(new AssertionError({message: 'failure three'}), { - stack: 'error message\nTest.fn (test.js:1:1)' - }); - const err3Path = tempWrite.sync('c();'); - err3.source = source(err3Path); - err3.statements = []; - err3.values = [ - {label: 'failure three:', formatted: JSON.stringify([1]) + '\n'} - ]; - - const reporter = miniReporter(); - reporter.failCount = 1; - - const runStatus = { - errors: [{ - title: 'failed one', - error: err1 - }, { - title: 'failed two', - error: err2 - }, { - title: 'failed three', - error: err3 - }] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 failed'), - '', - ' ' + colors.boldWhite('failed one'), - ' ' + colors.gray(`${err1.source.file}:${err1.source.line}`), - '', - indentString(codeExcerpt(err1.source), 2).split('\n'), - '', - /failure one/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('failed two'), - ' ' + colors.gray(`${err2.source.file}:${err2.source.line}`), - '', - indentString(codeExcerpt(err2.source), 2).split('\n'), - '', - /failure two/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '', - '', - '', - ' ' + colors.boldWhite('failed three'), - ' ' + colors.gray(`${err3.source.file}:${err3.source.line}`), - '', - indentString(codeExcerpt(err3.source), 2).split('\n'), - '', - ' failure three:', - '', - ' [1]', - '' - ])); - t.end(); -}); - -test('results with errors and disabled code excerpts', t => { - const err1 = errorFromWorker(new AssertionError({message: 'failure one'})); - delete err1.source; - err1.statements = []; - err1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const err2 = errorFromWorker(new AssertionError({message: 'failure two'}), { - stack: 'error message\nTest.fn (test.js:1:1)' - }); - const err2Path = tempWrite.sync('b();'); - err2.source = source(err2Path); - err2.statements = []; - err2.values = [ - {label: 'actual:', formatted: JSON.stringify([1])}, - {label: 'expected:', formatted: JSON.stringify([2])} - ]; - - const reporter = miniReporter({color: true}); - reporter.failCount = 1; - - const runStatus = { - errors: [{ - title: 'failed one', - error: err1 - }, { - title: 'failed two', - error: err2 - }] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 failed'), - '', - ' ' + colors.boldWhite('failed one'), - '', - /failure one/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('failed two'), - ' ' + colors.gray(`${err2.source.file}:${err2.source.line}`), - '', - indentString(codeExcerpt(err2.source), 2).split('\n'), - '', - /failure two/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '' - ])); - t.end(); -}); - -test('results with errors and broken code excerpts', t => { - const err1 = errorFromWorker(new AssertionError({message: 'failure one'})); - const err1Path = tempWrite.sync('a();'); - err1.source = source(err1Path, 10); - err1.statements = []; - err1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const err2 = errorFromWorker(new AssertionError({message: 'failure two'}), { - stack: 'error message\nTest.fn (test.js:1:1)' - }); - const err2Path = tempWrite.sync('b();'); - err2.source = source(err2Path); - err2.statements = []; - err2.values = [ - {label: 'actual:', formatted: JSON.stringify([1])}, - {label: 'expected:', formatted: JSON.stringify([2])} - ]; - - const reporter = miniReporter({color: true}); - reporter.failCount = 1; - - const runStatus = { - errors: [{ - title: 'failed one', - error: err1 - }, { - title: 'failed two', - error: err2 - }] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 failed'), - '', - ' ' + colors.boldWhite('failed one'), - ' ' + colors.gray(`${err1.source.file}:${err1.source.line}`), - '', - /failure one/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('failed two'), - ' ' + colors.gray(`${err2.source.file}:${err2.source.line}`), - '', - indentString(codeExcerpt(err2.source), 2).split('\n'), - '', - /failure two/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '' - ])); - t.end(); -}); - -test('results with unhandled errors', t => { - const reporter = miniReporter(); - reporter.failCount = 2; - - const err = errorFromWorker(new Error('failure one')); - delete err.source; - - const runStatus = { - errors: [ - {title: 'failed one', error: err}, - {title: 'failed two'} - ] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.red('2 failed'), - '', - ' ' + colors.boldWhite('failed one'), - '', - /failure one/, - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '' - ]); - t.end(); -}); - -test('results when fail-fast is enabled', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 1, - failCount: 1, - failFastEnabled: true, - fileCount: 1, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.magenta('`--fail-fast` is on. At least 1 test was skipped.'), - '' - ]); - t.end(); -}); - -test('results when fail-fast is enabled with multiple skipped tests', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 2, - failCount: 1, - failFastEnabled: true, - fileCount: 1, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.magenta('`--fail-fast` is on. At least 2 tests were skipped.'), - '' - ]); - t.end(); -}); - -test('results when fail-fast is enabled with skipped test file', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 0, - failCount: 1, - failFastEnabled: true, - fileCount: 2, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.magenta('`--fail-fast` is on. 1 test file was skipped.'), - '' - ]); - t.end(); -}); - -test('results when fail-fast is enabled with multiple skipped test files', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 0, - failCount: 1, - failFastEnabled: true, - fileCount: 3, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.magenta('`--fail-fast` is on. 2 test files were skipped.'), - '' - ]); - t.end(); -}); - -test('results when fail-fast is enabled with skipped tests and files', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 1, - failCount: 1, - failFastEnabled: true, - fileCount: 3, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.magenta('`--fail-fast` is on. At least 1 test was skipped, as well as 2 test files.'), - '' - ]); - t.end(); -}); - -test('results without fail-fast if no failing tests', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 1, - failCount: 0, - failFastEnabled: true, - fileCount: 1, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - t.is(output, '\n\n'); - t.end(); -}); - -test('results without fail-fast if no skipped tests', t => { - const reporter = miniReporter(); - const runStatus = { - remainingCount: 0, - failCount: 1, - failFastEnabled: true, - fileCount: 1, - observationCount: 1 - }; - - const output = reporter.finish(runStatus); - t.is(output, '\n\n'); - t.end(); -}); - -test('results with 1 previous failure', t => { - const reporter = miniReporter(); - reporter.todoCount = 1; - - const runStatus = { - previousFailCount: 1 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.blue('1 todo'), - ' ' + colors.red('1 previous failure in test files that were not rerun'), - '' - ]); - t.end(); -}); - -test('results with 2 previous failures', t => { - const reporter = miniReporter(); - reporter.todoCount = 1; - - const runStatus = { - previousFailCount: 2 - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.blue('1 todo'), - ' ' + colors.red('2 previous failures in test files that were not rerun'), - '' - ]); - t.end(); -}); - -test('empty results after reset', t => { - const reporter = miniReporter(); - - reporter.failCount = 1; - reporter.reset(); - - const output = reporter.finish({}); - t.is(output, '\n\n'); - t.end(); -}); - -test('full-width line when sectioning', t => { - const reporter = miniReporter(); - - const output = reporter.section(); - t.is(output, '\n' + fullWidthLine); - t.end(); -}); - -test('results with watching enabled', t => { - lolex.install({ - now: new Date(2014, 11, 19, 17, 19, 12, 200).getTime(), - toFake: [ - 'Date' - ] - }); - const time = ' ' + colors.dimGray('[17:19:12]'); - - const reporter = miniReporter({color: true, watching: true}); - reporter.passCount = 1; - reporter.failCount = 0; - - const actualOutput = reporter.finish({}); - const expectedOutput = `\n ${colors.green('1 passed') + time}\n`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('increases number of rejections', t => { - const reporter = miniReporter(); - reporter.passCount = 0; - reporter.rejectionCount = 0; - const err = new Error('failure one'); - err.type = 'rejection'; - reporter.unhandledError(err); - t.is(reporter.rejectionCount, 1); - t.end(); -}); - -test('increases number of exceptions', t => { - const reporter = miniReporter(); - reporter.passCount = 0; - reporter.exceptionCount = 0; - const err = new Error('failure one'); - err.type = 'exception'; - reporter.unhandledError(err); - t.is(reporter.exceptionCount, 1); - t.end(); -}); - -test('silently handles errors without body', t => { - const reporter = miniReporter(); - reporter.failCount = 1; - const runStatus = { - errors: [{}, {}] - }; - const actualOutput = reporter.finish(runStatus); - const expectedOutput = `\n ${colors.red('1 failed')}\n`; - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('does not handle errors with body in rejections', t => { - const reporter = miniReporter(); - reporter.rejectionCount = 1; - const runStatus = { - errors: [{ - title: 'failed test' - }] - }; - const actualOutput = reporter.finish(runStatus); - const expectedOutput = `\n ${colors.red('1 rejection')}\n`; - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('returns description based on error itself if no stack available', t => { - const reporter = miniReporter(); - reporter.exceptionCount = 1; - const thrownValue = {message: 'failure one'}; - const err1 = errorFromWorker(thrownValue, {file: 'test.js'}); - const runStatus = { - errors: [err1] - }; - const actualOutput = reporter.finish(runStatus); - console.log(actualOutput); - const expectedOutput = [ - '\n ' + colors.red('1 exception'), - '\n', - '\n ' + colors.boldWhite('Uncaught exception in test.js'), - '\n Threw non-error: ' + JSON.stringify(thrownValue), - '\n' - ].join(''); - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('shows "non-error" hint for invalid throws', t => { - const reporter = miniReporter(); - reporter.exceptionCount = 1; - const err = errorFromWorker({type: 'exception', message: 'function fooFn() {}', stack: 'function fooFn() {}'}, {file: 'test.js'}); - const runStatus = { - errors: [err] - }; - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 exception'), - '\n', - '\n ' + colors.boldWhite('Uncaught exception in test.js'), - '\n Threw non-error: function fooFn() {}', - '\n' - ].join(''); - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('returns empty string (used in watcher in order to separate different test runs)', t => { - const reporter = miniReporter(); - t.is(reporter.clear(), ''); - t.end(); -}); - -test('stderr and stdout should call _update', t => { - const reporter = miniReporter(); - const spy = sinon.spy(reporter, '_update'); - reporter.stdout(); - reporter.stderr(); - t.is(spy.callCount, 2); - reporter._update.restore(); - t.end(); -}); - -test('results when hasExclusive is enabled, but there are no known remaining tests', t => { - const reporter = miniReporter(); - const runStatus = { - hasExclusive: true - }; - - const output = reporter.finish(runStatus); - t.is(output, '\n\n'); - t.end(); -}); - -test('results when hasExclusive is enabled, but there is one remaining tests', t => { - const reporter = miniReporter(); - - const runStatus = { - hasExclusive: true, - testCount: 2, - passCount: 1, - remainingCount: 1 - }; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = '\n' + - ' ' + colors.magenta('The .only() modifier is used in some tests. 1 test was not run') + - '\n'; - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results when hasExclusive is enabled, but there are multiple remaining tests', t => { - const reporter = miniReporter(); - - const runStatus = { - hasExclusive: true, - testCount: 3, - passCount: 1, - remainingCount: 2 - }; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = '\n' + - ' ' + colors.magenta('The .only() modifier is used in some tests. 2 tests were not run') + - '\n'; - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('result when no-color flag is set', t => { - const reporter = miniReporter({ - color: false - }); - - const runStatus = { - hasExclusive: true, - testCount: 3, - passCount: 1, - failCount: 0, - remainingCount: 2 - }; - - const output = reporter.finish(runStatus); - const expectedOutput = '\n' + - ' The .only() modifier is used in some tests. 2 tests were not run' + - '\n'; - t.is(output, expectedOutput); - t.end(); -}); - -test('results with errors and logs', t => { - const err1 = errorFromWorker(new AssertionError({message: 'failure one'})); - const err1Path = tempWrite.sync('a();'); - err1.source = source(err1Path); - err1.statements = []; - err1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc') + '\n'}, - {label: 'expected:', formatted: JSON.stringify('abd') + '\n'} - ]; - - const reporter = miniReporter(); - reporter.failCount = 1; - - const runStatus = { - errors: [{ - title: 'failed one', - logs: ['log from a failed test\nwith a newline', 'another log from failed test'], - error: err1 - }] - }; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 failed'), - '', - ' ' + colors.boldWhite('failed one'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log from a failed test'), - ' ' + colors.gray('with a newline'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('another log from failed test'), - '', - ' ' + colors.gray(`${err1.source.file}:${err1.source.line}`), - '', - indentString(codeExcerpt(err1.source), 2).split('\n'), - '', - /failure one/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '' - ])); - t.end(); -}); + return report[type](reporter) + .then(() => { + tty.end(); + return tty.asBuffer(); + }) + .then(buffer => report.assert(t, logFile, buffer, {stripStdIO: true, alsoStripSeparator: false})) + .catch(t.threw); +}; + +test('mini reporter - regular run', run('regular')); +test('mini reporter - failFast run', run('failFast')); +test('mini reporter - second failFast run', run('failFast2')); +test('mini reporter - only run', run('only')); +test('mini reporter - watch mode run', run('watch')); diff --git a/test/reporters/mini.only.log b/test/reporters/mini.only.log new file mode 100644 index 000000000..ad6823449 --- /dev/null +++ b/test/reporters/mini.only.log @@ -0,0 +1,14 @@ +---tty-stream-chunk-separator + + a › only + + 1 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + b › passes + + 2 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 2 passed +---tty-stream-chunk-separator diff --git a/test/reporters/mini.regular.log b/test/reporters/mini.regular.log new file mode 100644 index 000000000..a241042cc --- /dev/null +++ b/test/reporters/mini.regular.log @@ -0,0 +1,285 @@ +---tty-stream-chunk-separator + + unhandled-rejection › passes + + 1 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + unhandled-rejection › unhandled non-error rejection + + 2 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + uncaught-exception › passes + + 3 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator +stdout +---tty-stream-chunk-separator + + uncaught-exception › passes + + 3 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator +stderr +---tty-stream-chunk-separator + + uncaught-exception › passes + + 3 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › passes + + 4 passed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › fails + + 4 passed + 1 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › known failure + + 5 passed + 1 known failure + 1 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › no longer failing + + 5 passed + 1 known failure + 2 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › logs + + 5 passed + 1 known failure + 3 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › formatted + + 5 passed + 1 known failure + 4 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › power-assert + + 5 passed + 1 known failure + 5 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › bad throws + + 5 passed + 1 known failure + 6 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › bad notThrows + + 5 passed + 1 known failure + 7 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › implementation throws non-error + + 5 passed + 1 known failure + 8 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + slow › slow + + 6 passed + 1 known failure + 8 failed + 1 skipped + 1 todo---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 6 passed + 1 known failure + 8 failed + 1 skipped + 1 todo + 2 rejections + 3 exceptions + + test › known failure + + test › fails + + ~/test/fixture/report/regular/test.js:12 + + 11: +  12: test('fails', t => t.fail()); + 13: + + Test failed via `t.fail()` + + + + test › no longer failing + + + Test was expected to fail, but succeeded, you should stop marking the test as failing + + + + test › logs + ℹ hello + ℹ world + + ~/test/fixture/report/regular/test.js:21 + + 20: t.log('world'); +  21: t.fail();  + 22: }); + + Test failed via `t.fail()` + + + + test › formatted + + ~/test/fixture/report/regular/test.js:25 + + 24: test('formatted', t => { +  25: t.deepEqual('foo', 'bar'); + 26: }); + + Difference: + + - 'foo' + + 'bar' + + + + test › power-assert + + ~/test/fixture/report/regular/test.js:30 + + 29: const foo = 'bar'; +  30: t.falsy(foo);  + 31: }); + + Value is not falsy: + + 'bar' + + foo + => 'bar' + + + + test › bad throws + + ~/test/fixture/report/regular/test.js:37 + + 36: }; +  37: t.throws(fn()); + 38: }); + + Improper usage of `t.throws()` detected + + The following error was thrown, possibly before `t.throws()` could be called: + + Error { + message: 'err', + } + + Try wrapping the first argument to `t.throws()` in a function: + + t.throws(() => { /* your code here */ }) + + Visit the following URL for more details: + + https://github.com/avajs/ava#throwsfunctionpromise-error-message + + fn (test.js:35:9) + Test.t [as fn] (test.js:37:11) + + + + test › bad notThrows + + ~/test/fixture/report/regular/test.js:44 + + 43: }; +  44: t.notThrows(fn()); + 45: }); + + Improper usage of `t.notThrows()` detected + + The following error was thrown, possibly before `t.notThrows()` could be called: + + Error { + message: 'err', + } + + Try wrapping the first argument to `t.notThrows()` in a function: + + t.notThrows(() => { /* your code here */ }) + + Visit the following URL for more details: + + https://github.com/avajs/ava#throwsfunctionpromise-error-message + + fn (test.js:42:9) + Test.t [as fn] (test.js:44:14) + + + + test › implementation throws non-error + + + Error thrown in test: + + null + + + + Uncaught exception in test/fixture/report/regular/bad-test-chain.js + TypeError: _.default.serial.test is not a function + Object. (bad-test-chain.js:3:13) + + ✖ No tests found in test/fixture/report/regular/bad-test-chain.js + + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js + Error: Can't catch me + Test.t [as fn] (unhandled-rejection.js:4:17) + + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js + Threw non-error: {"message":"null"} + Uncaught exception in test/fixture/report/regular/uncaught-exception.js + Error: Can't catch me + Timeout.setTimeout (uncaught-exception.js:5:9) +---tty-stream-chunk-separator diff --git a/test/reporters/mini.watch.log b/test/reporters/mini.watch.log new file mode 100644 index 000000000..a2479aab8 --- /dev/null +++ b/test/reporters/mini.watch.log @@ -0,0 +1,31 @@ +---tty-stream-chunk-separator + + test › passes + + 1 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 1 passed [17:19:12] +---tty-stream-chunk-separator + +────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › passes + + 1 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 1 passed [17:19:12] + 2 previous failures in test files that were not rerun +---tty-stream-chunk-separator +---tty-stream-chunk-separator +---tty-stream-chunk-separator + + test › passes + + 1 passed---tty-stream-chunk-separator +---tty-stream-chunk-separator + + 1 passed [17:19:12] +---tty-stream-chunk-separator diff --git a/test/reporters/tap.failfast.log b/test/reporters/tap.failfast.log new file mode 100644 index 000000000..8dfb187f1 --- /dev/null +++ b/test/reporters/tap.failfast.log @@ -0,0 +1,18 @@ +TAP version 13 +---tty-stream-chunk-separator +# a › fails +not ok 1 - a › fails + --- + name: AssertionError + message: Test failed via `t.fail()` + assertion: fail + at: 'Test.t [as fn] (a.js:3:22)' + ... +---tty-stream-chunk-separator + +1..1 +# tests 1 +# pass 0 +# fail 1 + +---tty-stream-chunk-separator diff --git a/test/reporters/tap.failfast2.log b/test/reporters/tap.failfast2.log new file mode 100644 index 000000000..8dfb187f1 --- /dev/null +++ b/test/reporters/tap.failfast2.log @@ -0,0 +1,18 @@ +TAP version 13 +---tty-stream-chunk-separator +# a › fails +not ok 1 - a › fails + --- + name: AssertionError + message: Test failed via `t.fail()` + assertion: fail + at: 'Test.t [as fn] (a.js:3:22)' + ... +---tty-stream-chunk-separator + +1..1 +# tests 1 +# pass 0 +# fail 1 + +---tty-stream-chunk-separator diff --git a/test/reporters/tap.js b/test/reporters/tap.js index 6305b91fc..4046baeb9 100644 --- a/test/reporters/tap.js +++ b/test/reporters/tap.js @@ -1,316 +1,35 @@ 'use strict'; -const sinon = require('sinon'); +require('../helper/report').captureStdIOReliability(); +require('../helper/fix-reporter-env')(); + +const path = require('path'); const test = require('tap').test; -const hasAnsi = require('has-ansi'); -const colors = require('../helper/colors'); +const TTYStream = require('../helper/tty-stream'); +const report = require('../helper/report'); const TapReporter = require('../../lib/reporters/tap'); -test('start', t => { - const reporter = new TapReporter(); - - t.is(reporter.start(), 'TAP version 13'); - t.end(); -}); - -test('passing test', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'passing' - }); - - const expectedOutput = [ - '# passing', - 'ok 1 - passing' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failing test', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'failing', - error: { - name: 'AssertionError', - message: 'false == true', - avaAssertionError: true, - assertion: 'true', - operator: '==', - values: [{label: 'expected:', formatted: 'true'}, {label: 'actual:', formatted: 'false'}], - stack: 'Test.fn (test.js:1:2)' - } - }); - - const expectedOutput = `# failing -not ok 1 - failing - --- - name: AssertionError - message: false == true - assertion: 'true' - operator: == - values: - 'expected:': 'true' - 'actual:': 'false' - at: 'Test.fn (test.js:1:2)' - ...`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('multiline strings in YAML block', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'multiline', - error: { - object: { - foo: 'hello\nworld' - } - } - }); - - const expectedOutput = `# multiline -not ok 1 - multiline - --- - foo: |- - hello - world - ...`; +const run = type => t => { + t.plan(1); - t.is(actualOutput, expectedOutput); - t.end(); -}); + const logFile = path.join(__dirname, `tap.${type.toLowerCase()}.log`); -test('strips ANSI from actual and expected values', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'strip ansi', - error: { - avaAssertionError: true, - values: [{label: 'value', formatted: '\u001B[31mhello\u001B[39m'}] - } - }); - - const expectedOutput = `# strip ansi -not ok 1 - strip ansi - --- - values: - value: hello - ...`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('unhandled error', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.unhandledError({ - message: 'unhandled', - name: 'TypeError', - stack: 'Test.fn (test.js:1:2)' + const tty = new TTYStream({ + columns: 200, + sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.unreliableProcessIO] }); - - const expectedOutput = `# unhandled -not ok 1 - unhandled - --- - name: TypeError - at: 'Test.fn (test.js:1:2)' - ...`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('ava error', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.unhandledError({ - type: 'exception', - name: 'AvaError', - message: 'A futuristic test runner' + const reporter = Object.assign(new TapReporter(), { + streams: {stderr: tty, stdout: tty} }); + return report[type](reporter) + .then(() => { + tty.end(); + return tty.asBuffer(); + }) + .then(buffer => report.assert(t, logFile, buffer, {stripStdIO: true, alsoStripSeparator: true})) + .catch(t.threw); +}; - const expectedOutput = [ - '# A futuristic test runner', - 'not ok 1 - A futuristic test runner' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results', t => { - const reporter = new TapReporter(); - const runStatus = { - passCount: 1, - failCount: 2, - skipCount: 1, - rejectionCount: 3, - exceptionCount: 4 - }; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - '1..' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount), - '# tests ' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount), - '# pass ' + runStatus.passCount, - '# skip ' + runStatus.skipCount, - '# fail ' + (runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results does not show skipped tests if there are none', t => { - const reporter = new TapReporter(); - const runStatus = { - passCount: 1, - failCount: 2, - skipCount: 0, - rejectionCount: 3, - exceptionCount: 4 - }; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - '1..' + (runStatus.passCount + runStatus.failCount), - '# tests ' + (runStatus.passCount + runStatus.failCount), - '# pass ' + runStatus.passCount, - '# fail ' + (runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('todo test', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'should think about doing this', - passed: false, - skip: true, - todo: true - }); - - const expectedOutput = [ - '# should think about doing this', - 'not ok 1 - should think about doing this # TODO' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('skip test', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'skipped', - passed: true, - skip: true - }); - - const expectedOutput = [ - '# skipped', - 'ok 1 - skipped # SKIP' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('reporter strips ANSI characters', t => { - const reporter = new TapReporter(); - - const output = reporter.test({ - title: `test ${colors.dimGray('›')} my test`, - type: 'test', - file: 'test.js' - }); - - t.notOk(hasAnsi(output.title)); - t.end(); -}); - -test('write should call console.log', t => { - const reporter = new TapReporter(); - const stub = sinon.stub(console, 'log'); - - reporter.write('result'); - - t.true(stub.called); - console.log.restore(); - t.end(); -}); - -test('stdout and stderr should call process.stderr.write', t => { - const reporter = new TapReporter(); - const stub = sinon.stub(process.stderr, 'write'); - - reporter.stdout('result'); - reporter.stderr('result'); - - process.stderr.write.restore(); - t.is(stub.callCount, 2); - t.end(); -}); - -test('successful test with logs', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'passing', - logs: ['log message 1\nwith a newline', 'log message 2'] - }); - - const expectedOutput = [ - '# passing', - 'ok 1 - passing', - ' * log message 1', - ' with a newline', - ' * log message 2' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failing test with logs', t => { - const reporter = new TapReporter(); - - const actualOutput = reporter.test({ - title: 'failing', - error: { - name: 'AssertionError', - message: 'false == true' - }, - logs: ['log message 1\nwith a newline', 'log message 2'] - }); - - const expectedOutput = [ - '# failing', - 'not ok 1 - failing', - ' * log message 1', - ' with a newline', - ' * log message 2', - ' ---', - ' name: AssertionError', - ' message: false == true', - ' ...' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); +test('verbose reporter - regular run', run('regular')); +test('verbose reporter - failFast run', run('failFast')); +test('verbose reporter - second failFast run', run('failFast2')); +test('verbose reporter - only run', run('only')); diff --git a/test/reporters/tap.only.log b/test/reporters/tap.only.log new file mode 100644 index 000000000..4df4b8c60 --- /dev/null +++ b/test/reporters/tap.only.log @@ -0,0 +1,15 @@ +TAP version 13 +---tty-stream-chunk-separator +# a › only +ok 1 - a › only +---tty-stream-chunk-separator +# b › passes +ok 2 - b › passes +---tty-stream-chunk-separator + +1..2 +# tests 2 +# pass 2 +# fail 0 + +---tty-stream-chunk-separator diff --git a/test/reporters/tap.regular.log b/test/reporters/tap.regular.log new file mode 100644 index 000000000..dc1bac384 --- /dev/null +++ b/test/reporters/tap.regular.log @@ -0,0 +1,157 @@ +TAP version 13 +---tty-stream-chunk-separator +# _.default.serial.test is not a function +not ok 1 - _.default.serial.test is not a function + --- + name: TypeError + at: 'Object. (bad-test-chain.js:3:13)' + ... +---tty-stream-chunk-separator +# No tests found in test/fixture/report/regular/bad-test-chain.js +not ok 2 - No tests found in test/fixture/report/regular/bad-test-chain.js +---tty-stream-chunk-separator +# unhandled-rejection › passes +ok 3 - unhandled-rejection › passes +---tty-stream-chunk-separator +# unhandled-rejection › unhandled non-error rejection +ok 4 - unhandled-rejection › unhandled non-error rejection +---tty-stream-chunk-separator +# Can't catch me +not ok 5 - Can't catch me + --- + name: Error + at: 'Test.t [as fn] (unhandled-rejection.js:4:17)' + ... +---tty-stream-chunk-separator +# null +not ok 6 - null + --- + {} + ... +---tty-stream-chunk-separator +# uncaught-exception › passes +ok 7 - uncaught-exception › passes +---tty-stream-chunk-separator +# Can't catch me +not ok 8 - Can't catch me + --- + name: Error + at: 'Timeout.setTimeout (uncaught-exception.js:5:9)' + ... +---tty-stream-chunk-separator +stdout +---tty-stream-chunk-separator +stderr +---tty-stream-chunk-separator +# test › skip +ok 9 - test › skip # SKIP +---tty-stream-chunk-separator +# test › todo +not ok 10 - test › todo # TODO +---tty-stream-chunk-separator +# test › passes +ok 11 - test › passes +---tty-stream-chunk-separator +# test › fails +not ok 12 - test › fails + --- + name: AssertionError + message: Test failed via `t.fail()` + assertion: fail + at: 'Test.t [as fn] (test.js:12:22)' + ... +---tty-stream-chunk-separator +# test › known failure +ok 13 - test › known failure +---tty-stream-chunk-separator +# test › no longer failing +not ok 14 - test › no longer failing + --- + name: Error + message: >- + Test was expected to fail, but succeeded, you should stop marking the test as + failing + ... +---tty-stream-chunk-separator +# test › logs +not ok 15 - test › logs + * hello + * world + --- + name: AssertionError + message: Test failed via `t.fail()` + assertion: fail + at: 'Test.t [as fn] (test.js:21:4)' + ... +---tty-stream-chunk-separator +# test › formatted +not ok 16 - test › formatted + --- + name: AssertionError + assertion: deepEqual + values: + 'Difference:': |- + - 'foo' + + 'bar' + at: 'Test.t [as fn] (test.js:25:4)' + ... +---tty-stream-chunk-separator +# test › power-assert +not ok 17 - test › power-assert + --- + name: AssertionError + assertion: falsy + operator: '!' + values: + 'Value is not falsy:': '''bar''' + at: 'Test.t [as fn] (test.js:30:4)' + ... +---tty-stream-chunk-separator +# test › bad throws +not ok 18 - test › bad throws + --- + name: AssertionError + message: Improper usage of `t.throws()` detected + assertion: throws + values: + 'The following error was thrown, possibly before `t.throws()` could be called:': |- + Error { + message: 'err', + } + at: 'fn (test.js:35:9)' + ... +---tty-stream-chunk-separator +# test › bad notThrows +not ok 19 - test › bad notThrows + --- + name: AssertionError + message: Improper usage of `t.notThrows()` detected + assertion: notThrows + values: + 'The following error was thrown, possibly before `t.notThrows()` could be called:': |- + Error { + message: 'err', + } + at: 'fn (test.js:42:9)' + ... +---tty-stream-chunk-separator +# test › implementation throws non-error +not ok 20 - test › implementation throws non-error + --- + name: AssertionError + message: Error thrown in test + values: + 'Error thrown in test:': 'null' + ... +---tty-stream-chunk-separator +# slow › slow +ok 21 - slow › slow +---tty-stream-chunk-separator + +1..14 +# tests 14 +# pass 5 +# skip 1 +# fail 13 + +---tty-stream-chunk-separator diff --git a/test/reporters/verbose.failfast.log b/test/reporters/verbose.failfast.log new file mode 100644 index 000000000..b8755ea88 --- /dev/null +++ b/test/reporters/verbose.failfast.log @@ -0,0 +1,22 @@ + +---tty-stream-chunk-separator + ✖ a › fails Test failed via `t.fail()` +---tty-stream-chunk-separator + + 1 test failed + + a › fails + + ~/test/fixture/report/failfast/a.js:3 + + 2: +  3: test('fails', t => t.fail()); + 4: + + Test failed via `t.fail()` + + + + `--fail-fast` is on. 1 test file was skipped. + +---tty-stream-chunk-separator diff --git a/test/reporters/verbose.failfast2.log b/test/reporters/verbose.failfast2.log new file mode 100644 index 000000000..f1ec5bcd8 --- /dev/null +++ b/test/reporters/verbose.failfast2.log @@ -0,0 +1,22 @@ + +---tty-stream-chunk-separator + ✖ a › fails Test failed via `t.fail()` +---tty-stream-chunk-separator + + 1 test failed + + a › fails + + ~/test/fixture/report/failfast2/a.js:3 + + 2: +  3: test('fails', t => t.fail());  + 4: test('passes', t => t.pass()); + + Test failed via `t.fail()` + + + + `--fail-fast` is on. At least 1 test was skipped, as well as 1 test file. + +---tty-stream-chunk-separator diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index c12073060..95dd4e849 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -1,1004 +1,36 @@ 'use strict'; -require('../../lib/worker-options').set({}); +require('../helper/report').captureStdIOReliability(); +require('../helper/fix-reporter-env')(); -// These tests are run as a sub-process of the `tap` module, so the standard -// output stream will not be recognized as a text terminal. AVA internals are -// sensitive to this detail and respond by automatically disable output -// coloring. Because the tests are written verify AVA's behavior in text -// terminals, that environment should be simulated prior to loading any -// modules. -process.stdout.isTTY = true; - -const indentString = require('indent-string'); -const flatten = require('arr-flatten'); -const tempWrite = require('temp-write'); -const figures = require('figures'); -const sinon = require('sinon'); +const path = require('path'); const test = require('tap').test; -const lolex = require('lolex'); -const AssertionError = require('../../lib/assert').AssertionError; -const colors = require('../helper/colors'); +const TTYStream = require('../helper/tty-stream'); +const report = require('../helper/report'); const VerboseReporter = require('../../lib/reporters/verbose'); -const compareLineOutput = require('../helper/compare-line-output'); -const errorFromWorker = require('../helper/error-from-worker'); -const codeExcerpt = require('../../lib/code-excerpt'); - -const stackLineRegex = /.+ \(.+:\d+:\d+\)/; - -lolex.install({ - now: new Date(2014, 11, 19, 17, 19, 12, 200).getTime(), - toFake: [ - 'Date' - ] -}); -const time = ' ' + colors.dimGray('[17:19:12]'); - -function createReporter(options) { - if (options === undefined) { - options = {color: true}; - } - const reporter = new VerboseReporter(options); - return reporter; -} - -function createRunStatus() { - return { - fileCount: 1, - testCount: 1 - }; -} - -function source(file, line) { - return { - file, - line: line || 1, - isWithinProject: true, - isDependency: false - }; -} - -test('start', t => { - const reporter = createReporter(); - - t.is(reporter.start(createRunStatus()), ''); - t.end(); -}); - -test('passing test and duration less than threshold', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'passed', - duration: 90 - }); - - const expectedOutput = ' ' + colors.green(figures.tick) + ' passed'; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('passing test and duration greater than threshold', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'passed', - duration: 150 - }); - - const expectedOutput = ' ' + colors.green(figures.tick) + ' passed' + colors.dimGray(' (150ms)'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('known failure test', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'known failure', - failing: true - }); - - const expectedOutput = ' ' + colors.red(figures.tick) + ' ' + colors.red('known failure'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failing test', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'failed', - error: { - message: 'assertion failed' - } - }); - - const expectedOutput = ' ' + colors.red(figures.cross) + ' failed ' + colors.red('assertion failed'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('skipped test', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'skipped', - skip: true - }); - - const expectedOutput = ' ' + colors.yellow('- skipped'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('todo test', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'todo', - skip: true, - todo: true - }); - - const expectedOutput = ' ' + colors.blue('- todo'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('uncaught exception', t => { - const reporter = createReporter(); - - const error = errorFromWorker(new Error('Unexpected token'), { - type: 'exception', - file: 'test.js' - }); - - const output = reporter.unhandledError(error, createRunStatus()).split('\n'); - - t.is(output[0].trim(), colors.boldWhite('Uncaught exception in test.js')); - t.match(output[1], /Error: Unexpected token/); - t.match(output[2], /test\/reporters\/verbose\.js/); - t.end(); -}); - -test('ava error', t => { - const reporter = createReporter(); - - const output = reporter.unhandledError({ - type: 'exception', - file: 'test.js', - name: 'AvaError', - message: 'A futuristic test runner' - }, createRunStatus()).split('\n'); - - t.is(output[0], colors.red(' ' + figures.cross + ' A futuristic test runner')); - t.end(); -}); - -test('unhandled rejection', t => { - const reporter = createReporter(); - - const error = errorFromWorker(new Error('Unexpected token'), { - file: 'test.js', - type: 'rejection' - }); - - const output = reporter.unhandledError(error, createRunStatus()).split('\n'); - - t.is(output[0].trim(), colors.boldWhite('Unhandled rejection in test.js')); - t.match(output[1], /Error: Unexpected token/); - t.match(output[2], /test\/reporters\/verbose\.js/); - t.end(); -}); - -test('unhandled error without stack', t => { - const reporter = createReporter(); - - const err = errorFromWorker({message: 'test'}, { - file: 'test.js', - type: 'exception' - }); - - const output = reporter.unhandledError(err, createRunStatus()).split('\n'); - - t.is(output[0].trim(), colors.boldWhite('Uncaught exception in test.js')); - t.is(output[1], ' Threw non-error: ' + JSON.stringify({message: 'test'})); - t.end(); -}); - -test('results with passing tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with passing known failure tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.knownFailureCount = 1; - runStatus.knownFailures = [{ - title: 'known failure', - failing: true - }]; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 known failure'), - '', - '', - ' ' + colors.red('known failure'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with skipped tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.skipCount = 1; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.yellow('1 test skipped'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with todo tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.todoCount = 1; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.blue('1 test todo'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with passing tests and rejections', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.rejectionCount = 1; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 unhandled rejection'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with passing tests and exceptions', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.exceptionCount = 1; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 uncaught exception'), - '' - ].join('\n'); - t.is(actualOutput, expectedOutput); - t.end(); -}); +const run = type => t => { + t.plan(1); -test('results with passing tests, rejections and exceptions', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.exceptionCount = 1; - runStatus.rejectionCount = 1; + const logFile = path.join(__dirname, `verbose.${type.toLowerCase()}.log`); - const actualOutput = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 unhandled rejection'), - ' ' + colors.red('1 uncaught exception'), - '' - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with errors', t => { - const error1 = errorFromWorker(new AssertionError({message: 'error one message'})); - const err1Path = tempWrite.sync('a()'); - error1.source = source(err1Path); - error1.statements = []; - error1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const error2 = errorFromWorker(new AssertionError({message: 'error two message'}), { - stack: 'error message\nTest.fn (test.js:1:1)' - }); - const err2Path = tempWrite.sync('b()'); - error2.source = source(err2Path); - error2.statements = []; - error2.values = [ - {label: 'actual:', formatted: JSON.stringify([1])}, - {label: 'expected:', formatted: JSON.stringify([2])} - ]; - - const error3 = errorFromWorker(new AssertionError({message: 'error three message'}), { - stack: 'error message\nTest.fn (test.js:1:1)' + const tty = new TTYStream({ + columns: 200, + sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.slow, report.sanitizers.unreliableProcessIO] }); - const err3Path = tempWrite.sync('b()'); - error3.source = source(err3Path); - error3.statements = []; - error3.values = [ - {label: 'error three message:', formatted: JSON.stringify([1])} - ]; - - const reporter = createReporter({color: true}); - const runStatus = createRunStatus(); - runStatus.failCount = 1; - runStatus.tests = [{ - title: 'fail one', - error: error1 - }, { - title: 'fail two', - error: error2 - }, { - title: 'fail three', - error: error3 - }]; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 test failed'), - '', - ' ' + colors.boldWhite('fail one'), - ' ' + colors.gray(`${error1.source.file}:${error1.source.line}`), - '', - indentString(codeExcerpt(error1.source), 2).split('\n'), - '', - /error one message/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('fail two'), - ' ' + colors.gray(`${error2.source.file}:${error2.source.line}`), - '', - indentString(codeExcerpt(error2.source), 2).split('\n'), - '', - /error two message/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '', - '', - '', - ' ' + colors.boldWhite('fail three'), - ' ' + colors.gray(`${error3.source.file}:${error3.source.line}`), - '', - indentString(codeExcerpt(error3.source), 2).split('\n'), - '', - ' error three message:', - '', - ' [1]', - '' - ])); - t.end(); -}); - -test('results with errors and disabled code excerpts', t => { - const error1 = errorFromWorker(new AssertionError({message: 'error one message'})); - delete error1.source; - error1.statements = []; - error1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const error2 = errorFromWorker(new AssertionError({message: 'error two message'}), { - stack: 'error message\nTest.fn (test.js:1:1)\n' + const reporter = Object.assign(new VerboseReporter({color: true, watching: type === 'watch'}), { + stream: tty }); - const err2Path = tempWrite.sync('b()'); - error2.source = source(err2Path); - error2.statements = []; - error2.values = [ - {label: 'actual:', formatted: JSON.stringify([1])}, - {label: 'expected:', formatted: JSON.stringify([2])} - ]; - - const reporter = createReporter({color: true}); - const runStatus = createRunStatus(); - runStatus.failCount = 1; - runStatus.tests = [{ - title: 'fail one', - error: error1 - }, { - title: 'fail two', - error: error2 - }]; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 test failed'), - '', - ' ' + colors.boldWhite('fail one'), - '', - /error one message/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('fail two'), - ' ' + colors.gray(`${error2.source.file}:${error2.source.line}`), - '', - indentString(codeExcerpt(error2.source), 2).split('\n'), - '', - /error two message/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '' - ])); - t.end(); -}); - -test('results with errors and disabled code excerpts', t => { - const error1 = errorFromWorker(new AssertionError({message: 'error one message'})); - const err1Path = tempWrite.sync('a();'); - error1.source = source(err1Path, 10); - error1.statements = []; - error1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const error2 = errorFromWorker(new AssertionError({message: 'error two message'}), { - stack: 'error message\nTest.fn (test.js:1:1)\n' - }); - const err2Path = tempWrite.sync('b()'); - error2.source = source(err2Path); - error2.statements = []; - error2.values = [ - {label: 'actual:', formatted: JSON.stringify([1])}, - {label: 'expected:', formatted: JSON.stringify([2])} - ]; - - const reporter = createReporter({color: true}); - const runStatus = createRunStatus(); - runStatus.failCount = 1; - runStatus.tests = [{ - title: 'fail one', - error: error1 - }, { - title: 'fail two', - error: error2 - }]; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 test failed'), - '', - ' ' + colors.boldWhite('fail one'), - ' ' + colors.gray(`${error1.source.file}:${error1.source.line}`), - '', - /error one message/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + colors.boldWhite('fail two'), - ' ' + colors.gray(`${error2.source.file}:${error2.source.line}`), - '', - indentString(codeExcerpt(error2.source), 2).split('\n'), - '', - /error two message/, - '', - ' actual:', - '', - ' [1]', - '', - ' expected:', - '', - ' [2]', - '' - ])); - t.end(); -}); - -test('results when fail-fast is enabled', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 1; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 1; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 test failed'), - '\n', - '\n ' + colors.magenta('`--fail-fast` is on. At least 1 test was skipped.'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when fail-fast is enabled with multiple skipped tests', t => { - const reporter = new VerboseReporter({color: true}); - const runStatus = createRunStatus(); - runStatus.remainingCount = 2; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 1; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 test failed'), - '\n', - '\n ' + colors.magenta('`--fail-fast` is on. At least 2 tests were skipped.'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when fail-fast is enabled with skipped test file', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 0; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 2; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 test failed'), - '\n', - '\n ' + colors.magenta('`--fail-fast` is on. 1 test file was skipped.'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when fail-fast is enabled with multiple skipped test files', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 0; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 3; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 test failed'), - '\n', - '\n ' + colors.magenta('`--fail-fast` is on. 2 test files were skipped.'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when fail-fast is enabled with skipped tests and files', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 1; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 3; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.red('1 test failed'), - '\n', - '\n ' + colors.magenta('`--fail-fast` is on. At least 1 test was skipped, as well as 2 test files.'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results without fail-fast if no failing tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 1; - runStatus.failCount = 0; - runStatus.passCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 1; - runStatus.observationCount = 1; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - '' - ].join('\n'); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results without fail-fast if no skipped tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.remainingCount = 0; - runStatus.failCount = 1; - runStatus.failFastEnabled = true; - runStatus.fileCount = 1; - runStatus.observationCount = 1; - runStatus.tests = [{ - title: 'failed test' - }]; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.red('1 test failed'), - '' - ].join('\n'); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results with 1 previous failure', t => { - const reporter = createReporter(); - - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.exceptionCount = 1; - runStatus.previousFailCount = 1; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 uncaught exception'), - ' ' + colors.red('1 previous failure in test files that were not rerun'), - '' - ]); - t.end(); -}); - -test('results with 2 previous failures', t => { - const reporter = createReporter(); - - const runStatus = createRunStatus(); - runStatus.passCount = 1; - runStatus.exceptionCount = 1; - runStatus.previousFailCount = 2; - - const output = reporter.finish(runStatus); - compareLineOutput(t, output, [ - '', - ' ' + colors.green('1 test passed'), - ' ' + colors.red('1 uncaught exception'), - ' ' + colors.red('2 previous failures in test files that were not rerun'), - '' - ]); - t.end(); -}); - -test('full-width line when sectioning', t => { - const reporter = createReporter(); - - const prevColumns = process.stdout.columns; - process.stdout.columns = 80; - const output = reporter.section(); - process.stdout.columns = prevColumns; - - t.is(output, colors.dimGray('\u2500'.repeat(80))); - t.end(); -}); - -test('write calls console.error', t => { - const stub = sinon.stub(console, 'error'); - const reporter = createReporter(); - reporter.write('result'); - t.true(stub.called); - console.error.restore(); - t.end(); -}); - -test('reporter.stdout and reporter.stderr both use process.stderr.write', t => { - const reporter = createReporter(); - const stub = sinon.stub(process.stderr, 'write'); - reporter.stdout('result'); - reporter.stderr('result'); - t.is(stub.callCount, 2); - process.stderr.write.restore(); - t.end(); -}); - -test('results when hasExclusive is enabled, but there are no known remaining tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.hasExclusive = true; - runStatus.passCount = 1; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '', - ' ' + colors.green('1 test passed'), - '' - ].join('\n'); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when hasExclusive is enabled, but there is one remaining tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.hasExclusive = true; - runStatus.testCount = 2; - runStatus.passCount = 1; - runStatus.failCount = 0; - runStatus.remainingCount = 1; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.green('1 test passed'), - '\n', - '\n ' + colors.magenta('The .only() modifier is used in some tests. 1 test was not run'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('results when hasExclusive is enabled, but there are multiple remaining tests', t => { - const reporter = createReporter(); - const runStatus = createRunStatus(); - runStatus.hasExclusive = true; - runStatus.testCount = 3; - runStatus.passCount = 1; - runStatus.failCount = 0; - runStatus.remainingCount = 2; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n ' + colors.green('1 test passed'), - '\n', - '\n ' + colors.magenta('The .only() modifier is used in some tests. 2 tests were not run'), - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('result when no-color flag is set', t => { - const reporter = new VerboseReporter({color: false, watching: true}); - const runStatus = createRunStatus(); - runStatus.hasExclusive = true; - runStatus.testCount = 3; - runStatus.passCount = 1; - runStatus.failCount = 0; - runStatus.remainingCount = 2; - - const output = reporter.finish(runStatus); - const expectedOutput = [ - '\n 1 test passed [17:19:12]', - '\n', - '\n The .only() modifier is used in some tests. 2 tests were not run', - '\n' - ].join(''); - - t.is(output, expectedOutput); - t.end(); -}); - -test('timestamp added when watching is enabled', t => { - const reporter = new VerboseReporter({color: true, watching: true}); - const runStatus = createRunStatus(); - runStatus.testCount = 1; - runStatus.passCount = 1; - runStatus.failCount = 0; - - const actualOutput = reporter.finish(runStatus); - const expectedOutput = `\n ${colors.green('1 test passed') + time}\n`; - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('successful test with logs', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'successful test', - logs: ['log message 1\nwith a newline', 'log message 2'] - }, {}); - - const expectedOutput = [ - ' ' + colors.green(figures.tick) + ' successful test', - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log message 1'), - ' ' + colors.gray('with a newline'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log message 2') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('failed test with logs', t => { - const reporter = createReporter(); - - const actualOutput = reporter.test({ - title: 'failed test', - error: new Error('failure'), - logs: ['log message 1\nwith a newline', 'log message 2'] - }, {}); - - const expectedOutput = [ - ' ' + colors.red(figures.cross) + ' failed test ' + colors.red('failure'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log message 1'), - ' ' + colors.gray('with a newline'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log message 2') - ].join('\n'); - - t.is(actualOutput, expectedOutput); - t.end(); -}); - -test('results with errors and logs', t => { - const error1 = errorFromWorker(new AssertionError({message: 'error one message'})); - const err1Path = tempWrite.sync('a()'); - error1.source = source(err1Path); - error1.statements = []; - error1.values = [ - {label: 'actual:', formatted: JSON.stringify('abc')}, - {label: 'expected:', formatted: JSON.stringify('abd')} - ]; - - const reporter = createReporter({color: true}); - const runStatus = createRunStatus(); - runStatus.failCount = 1; - runStatus.tests = [{ - title: 'fail one', - logs: ['log from failed test\nwith a newline', 'another log from failed test'], - error: error1 - }]; + return report[type](reporter) + .then(() => { + tty.end(); + return tty.asBuffer(); + }) + .then(buffer => report.assert(t, logFile, buffer, {stripStdIO: true, alsoStripSeparator: true})) + .catch(t.threw); +}; - const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ - '', - ' ' + colors.red('1 test failed'), - '', - ' ' + colors.boldWhite('fail one'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('log from failed test'), - ' ' + colors.gray('with a newline'), - ' ' + colors.magenta(figures.info) + ' ' + colors.gray('another log from failed test'), - '', - ' ' + colors.gray(`${error1.source.file}:${error1.source.line}`), - '', - indentString(codeExcerpt(error1.source), 2).split('\n'), - '', - /error one message/, - '', - ' actual:', - '', - ' "abc"', - '', - ' expected:', - '', - ' "abd"', - '', - stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '' - ])); - t.end(); -}); +test('verbose reporter - regular run', run('regular')); +test('verbose reporter - failFast run', run('failFast')); +test('verbose reporter - second failFast run', run('failFast2')); +test('verbose reporter - only run', run('only')); +test('verbose reporter - watch mode run', run('watch')); diff --git a/test/reporters/verbose.only.log b/test/reporters/verbose.only.log new file mode 100644 index 000000000..67c5e210e --- /dev/null +++ b/test/reporters/verbose.only.log @@ -0,0 +1,10 @@ + +---tty-stream-chunk-separator + ✔ a › only +---tty-stream-chunk-separator + ✔ b › passes +---tty-stream-chunk-separator + + 2 tests passed + +---tty-stream-chunk-separator diff --git a/test/reporters/verbose.regular.log b/test/reporters/verbose.regular.log new file mode 100644 index 000000000..fc15c2672 --- /dev/null +++ b/test/reporters/verbose.regular.log @@ -0,0 +1,210 @@ + +---tty-stream-chunk-separator + Uncaught exception in test/fixture/report/regular/bad-test-chain.js + TypeError: _.default.serial.test is not a function + Object. (bad-test-chain.js:3:13) + + + +---tty-stream-chunk-separator + ✖ No tests found in test/fixture/report/regular/bad-test-chain.js +---tty-stream-chunk-separator + ✔ unhandled-rejection › passes +---tty-stream-chunk-separator + ✔ unhandled-rejection › unhandled non-error rejection +---tty-stream-chunk-separator + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js + Error: Can't catch me + Test.t [as fn] (unhandled-rejection.js:4:17) + + + +---tty-stream-chunk-separator + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js + Threw non-error: {"message":"null"} + + +---tty-stream-chunk-separator + ✔ uncaught-exception › passes +---tty-stream-chunk-separator + Uncaught exception in test/fixture/report/regular/uncaught-exception.js + Error: Can't catch me + Timeout.setTimeout (uncaught-exception.js:5:9) + + + +---tty-stream-chunk-separator +stdout +---tty-stream-chunk-separator +stderr +---tty-stream-chunk-separator + - test › skip +---tty-stream-chunk-separator + - test › todo +---tty-stream-chunk-separator + ✔ test › passes +---tty-stream-chunk-separator + ✖ test › fails Test failed via `t.fail()` +---tty-stream-chunk-separator + ✔ test › known failure +---tty-stream-chunk-separator + ✖ test › no longer failing Test was expected to fail, but succeeded, you should stop marking the test as failing +---tty-stream-chunk-separator + ✖ test › logs Test failed via `t.fail()` + ℹ hello + ℹ world +---tty-stream-chunk-separator + ✖ test › formatted +---tty-stream-chunk-separator + ✖ test › power-assert +---tty-stream-chunk-separator + ✖ test › bad throws Improper usage of `t.throws()` detected +---tty-stream-chunk-separator + ✖ test › bad notThrows Improper usage of `t.notThrows()` detected +---tty-stream-chunk-separator + ✖ test › implementation throws non-error Error thrown in test +---tty-stream-chunk-separator + ✔ slow › slow (000ms) +---tty-stream-chunk-separator + + 8 tests failed + 1 known failure + 1 test skipped + 1 test todo + 2 unhandled rejections + 3 uncaught exceptions + + + test › known failure + + test › fails + + ~/test/fixture/report/regular/test.js:12 + + 11: +  12: test('fails', t => t.fail()); + 13: + + Test failed via `t.fail()` + + + + test › no longer failing + + + Test was expected to fail, but succeeded, you should stop marking the test as failing + + + + test › logs + ℹ hello + ℹ world + + ~/test/fixture/report/regular/test.js:21 + + 20: t.log('world'); +  21: t.fail();  + 22: }); + + Test failed via `t.fail()` + + + + test › formatted + + ~/test/fixture/report/regular/test.js:25 + + 24: test('formatted', t => { +  25: t.deepEqual('foo', 'bar'); + 26: }); + + Difference: + + - 'foo' + + 'bar' + + + + test › power-assert + + ~/test/fixture/report/regular/test.js:30 + + 29: const foo = 'bar'; +  30: t.falsy(foo);  + 31: }); + + Value is not falsy: + + 'bar' + + foo + => 'bar' + + + + test › bad throws + + ~/test/fixture/report/regular/test.js:37 + + 36: }; +  37: t.throws(fn()); + 38: }); + + Improper usage of `t.throws()` detected + + The following error was thrown, possibly before `t.throws()` could be called: + + Error { + message: 'err', + } + + Try wrapping the first argument to `t.throws()` in a function: + + t.throws(() => { /* your code here */ }) + + Visit the following URL for more details: + + https://github.com/avajs/ava#throwsfunctionpromise-error-message + + fn (test.js:35:9) + Test.t [as fn] (test.js:37:11) + + + + test › bad notThrows + + ~/test/fixture/report/regular/test.js:44 + + 43: }; +  44: t.notThrows(fn()); + 45: }); + + Improper usage of `t.notThrows()` detected + + The following error was thrown, possibly before `t.notThrows()` could be called: + + Error { + message: 'err', + } + + Try wrapping the first argument to `t.notThrows()` in a function: + + t.notThrows(() => { /* your code here */ }) + + Visit the following URL for more details: + + https://github.com/avajs/ava#throwsfunctionpromise-error-message + + fn (test.js:42:9) + Test.t [as fn] (test.js:44:14) + + + + test › implementation throws non-error + + + Error thrown in test: + + null + +---tty-stream-chunk-separator diff --git a/test/reporters/verbose.watch.log b/test/reporters/verbose.watch.log new file mode 100644 index 000000000..b437964a2 --- /dev/null +++ b/test/reporters/verbose.watch.log @@ -0,0 +1,27 @@ + +---tty-stream-chunk-separator + ✔ test › passes +---tty-stream-chunk-separator + + 1 test passed [17:19:12] + +---tty-stream-chunk-separator +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +---tty-stream-chunk-separator + +---tty-stream-chunk-separator + ✔ test › passes +---tty-stream-chunk-separator + + 1 test passed [17:19:12] + 2 previous failures in test files that were not rerun + +---tty-stream-chunk-separator + +---tty-stream-chunk-separator + ✔ test › passes +---tty-stream-chunk-separator + + 1 test passed [17:19:12] + +---tty-stream-chunk-separator From 4469c11c45ae51d6c33409f103e1c478f12d42d2 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 15 Feb 2018 15:03:45 +0000 Subject: [PATCH 05/18] Instantiate runner in test-worker --- lib/main.js | 16 +--- lib/test-worker.js | 179 ++++++++++++++++++++++++--------------------- 2 files changed, 95 insertions(+), 100 deletions(-) diff --git a/lib/main.js b/lib/main.js index 0b7c325ab..ec576f00c 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,21 +1,7 @@ 'use strict'; const worker = require('./test-worker'); -const Runner = require('./runner'); -const opts = require('./worker-options').get(); -const runner = new Runner({ - failFast: opts.failFast, - failWithoutAssertions: opts.failWithoutAssertions, - file: opts.file, - match: opts.match, - projectDir: opts.projectDir, - runOnlyExclusive: opts.runOnlyExclusive, - serial: opts.serial, - snapshotDir: opts.snapshotDir, - updateSnapshots: opts.updateSnapshots -}); - -worker.setRunner(runner); +const runner = worker.getRunner(); const makeCjsExport = () => { function test() { diff --git a/lib/test-worker.js b/lib/test-worker.js index b7f45b9dc..d19ae0a25 100644 --- a/lib/test-worker.js +++ b/lib/test-worker.js @@ -22,13 +22,102 @@ const isObj = require('is-obj'); const adapter = require('./process-adapter'); const serializeError = require('./serialize-error'); const opts = require('./worker-options').get(); +const Runner = require('./runner'); + +const runner = new Runner({ + failFast: opts.failFast, + failWithoutAssertions: opts.failWithoutAssertions, + file: opts.file, + match: opts.match, + projectDir: opts.projectDir, + runOnlyExclusive: opts.runOnlyExclusive, + serial: opts.serial, + snapshotDir: opts.snapshotDir, + updateSnapshots: opts.updateSnapshots +}); + +let accessedRunner = false; +exports.getRunner = () => { + accessedRunner = true; + return runner; +}; // Store details about the test run, to be sent to the parent process later. const dependencies = new Set(); const touchedFiles = new Set(); +runner.on('dependency', file => { + dependencies.add(file); +}); +runner.on('touched', files => { + for (const file of files) { + touchedFiles.add(file); + } +}); + +runner.on('start', started => { + adapter.send('stats', { + testCount: started.stats.testCount, + hasExclusive: started.stats.hasExclusive + }); + + for (const partial of started.skippedTests) { + adapter.send('test', { + duration: null, + error: null, + failing: partial.failing, + logs: [], + skip: true, + title: partial.title, + todo: false, + type: 'test' + }); + } + for (const title of started.todoTitles) { + adapter.send('test', { + duration: null, + error: null, + failing: false, + logs: [], + skip: true, + title, + todo: true, + type: 'test' + }); + } + + started.ended.then(() => { + runner.saveSnapshotState(); + return exit(); + }).catch(err => { + handleUncaughtException(err); + }); +}); + +runner.on('hook-failed', result => { + adapter.send('test', { + duration: result.duration, + error: serializeError(result.error), + failing: result.metadata.failing, + logs: result.logs, + skip: result.metadata.skip, + title: result.title, + todo: result.metadata.todo, + type: result.metadata.type + }); +}); -// Set when main.js is required (since test files should have `require('ava')`). -let runner = null; +runner.on('test', result => { + adapter.send('test', { + duration: result.duration, + error: result.passed ? null : serializeError(result.error), + failing: result.metadata.failing, + logs: result.logs, + skip: result.metadata.skip, + title: result.title, + todo: result.metadata.todo, + type: result.metadata.type + }); +}); // Track when exiting begins, to avoid repeatedly sending stats, or sending // individual test results once stats have been sent. This is necessary since @@ -54,85 +143,7 @@ function exit() { adapter.send('results', {stats}); } -exports.setRunner = newRunner => { - runner = newRunner; - runner.on('dependency', file => { - dependencies.add(file); - }); - runner.on('touched', files => { - for (const file of files) { - touchedFiles.add(file); - } - }); - runner.on('start', started => { - adapter.send('stats', { - testCount: started.stats.testCount, - hasExclusive: started.stats.hasExclusive - }); - - for (const partial of started.skippedTests) { - adapter.send('test', { - duration: null, - error: null, - failing: partial.failing, - logs: [], - skip: true, - title: partial.title, - todo: false, - type: 'test' - }); - } - for (const title of started.todoTitles) { - adapter.send('test', { - duration: null, - error: null, - failing: false, - logs: [], - skip: true, - title, - todo: true, - type: 'test' - }); - } - - started.ended.then(() => { - runner.saveSnapshotState(); - return exit(); - }).catch(err => { - handleUncaughtException(err); - }); - }); - runner.on('hook-failed', result => { - adapter.send('test', { - duration: result.duration, - error: serializeError(result.error), - failing: result.metadata.failing, - logs: result.logs, - skip: result.metadata.skip, - title: result.title, - todo: result.metadata.todo, - type: result.metadata.type - }); - }); - runner.on('test', result => { - adapter.send('test', { - duration: result.duration, - error: result.passed ? null : serializeError(result.error), - failing: result.metadata.failing, - logs: result.logs, - skip: result.metadata.skip, - title: result.title, - todo: result.metadata.todo, - type: result.metadata.type - }); - }); -}; - function attributeLeakedError(err) { - if (!runner) { - return false; - } - return runner.attributeLeakedError(err); } @@ -218,9 +229,7 @@ process.on('ava-init-exit', () => { }); process.on('ava-peer-failed', () => { - if (runner) { - runner.interrupt(); - } + runner.interrupt(); }); // Store value in case to prevent required modules from modifying it. @@ -247,9 +256,9 @@ try { } catch (err) { handleUncaughtException(err); } finally { - adapter.send('loaded-file', {avaRequired: Boolean(runner)}); + adapter.send('loaded-file', {avaRequired: accessedRunner}); - if (runner) { + if (accessedRunner) { // Unreference the IPC channel if the test file required AVA. This stops it // from keeping the event loop busy, which means the `beforeExit` event can be // used to detect when tests stall. From f57e856c366d5ed6710c5d4ccd609c04950c3fcf Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 15 Feb 2018 15:17:36 +0000 Subject: [PATCH 06/18] Ensure 'this' in test implementations is null We shouldn't expose the test instance through 'this'. --- lib/test.js | 2 +- test/reporters/mini.regular.log | 6 +++--- test/reporters/tap.failfast.log | 2 +- test/reporters/tap.failfast2.log | 2 +- test/reporters/tap.regular.log | 10 +++++----- test/reporters/verbose.regular.log | 6 +++--- test/test.js | 7 +++++++ 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/test.js b/lib/test.js index d84e25082..13404e4cc 100644 --- a/lib/test.js +++ b/lib/test.js @@ -312,7 +312,7 @@ class Test { try { return { ok: true, - retval: this.fn(this.createExecutionContext()) + retval: this.fn.call(null, this.createExecutionContext()) }; } catch (err) { return { diff --git a/test/reporters/mini.regular.log b/test/reporters/mini.regular.log index a241042cc..183a9de3f 100644 --- a/test/reporters/mini.regular.log +++ b/test/reporters/mini.regular.log @@ -225,7 +225,7 @@ stderr https://github.com/avajs/ava#throwsfunctionpromise-error-message fn (test.js:35:9) - Test.t [as fn] (test.js:37:11) + t (test.js:37:11) @@ -254,7 +254,7 @@ stderr https://github.com/avajs/ava#throwsfunctionpromise-error-message fn (test.js:42:9) - Test.t [as fn] (test.js:44:14) + t (test.js:44:14) @@ -275,7 +275,7 @@ stderr Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js Error: Can't catch me - Test.t [as fn] (unhandled-rejection.js:4:17) + t (unhandled-rejection.js:4:17) Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js Threw non-error: {"message":"null"} diff --git a/test/reporters/tap.failfast.log b/test/reporters/tap.failfast.log index 8dfb187f1..58a85fe98 100644 --- a/test/reporters/tap.failfast.log +++ b/test/reporters/tap.failfast.log @@ -6,7 +6,7 @@ not ok 1 - a › fails name: AssertionError message: Test failed via `t.fail()` assertion: fail - at: 'Test.t [as fn] (a.js:3:22)' + at: 't (a.js:3:22)' ... ---tty-stream-chunk-separator diff --git a/test/reporters/tap.failfast2.log b/test/reporters/tap.failfast2.log index 8dfb187f1..58a85fe98 100644 --- a/test/reporters/tap.failfast2.log +++ b/test/reporters/tap.failfast2.log @@ -6,7 +6,7 @@ not ok 1 - a › fails name: AssertionError message: Test failed via `t.fail()` assertion: fail - at: 'Test.t [as fn] (a.js:3:22)' + at: 't (a.js:3:22)' ... ---tty-stream-chunk-separator diff --git a/test/reporters/tap.regular.log b/test/reporters/tap.regular.log index dc1bac384..f433f913b 100644 --- a/test/reporters/tap.regular.log +++ b/test/reporters/tap.regular.log @@ -20,7 +20,7 @@ ok 4 - unhandled-rejection › unhandled non-error rejection not ok 5 - Can't catch me --- name: Error - at: 'Test.t [as fn] (unhandled-rejection.js:4:17)' + at: 't (unhandled-rejection.js:4:17)' ... ---tty-stream-chunk-separator # null @@ -58,7 +58,7 @@ not ok 12 - test › fails name: AssertionError message: Test failed via `t.fail()` assertion: fail - at: 'Test.t [as fn] (test.js:12:22)' + at: 't (test.js:12:22)' ... ---tty-stream-chunk-separator # test › known failure @@ -81,7 +81,7 @@ not ok 15 - test › logs name: AssertionError message: Test failed via `t.fail()` assertion: fail - at: 'Test.t [as fn] (test.js:21:4)' + at: 't (test.js:21:4)' ... ---tty-stream-chunk-separator # test › formatted @@ -93,7 +93,7 @@ not ok 16 - test › formatted 'Difference:': |- - 'foo' + 'bar' - at: 'Test.t [as fn] (test.js:25:4)' + at: 't (test.js:25:4)' ... ---tty-stream-chunk-separator # test › power-assert @@ -104,7 +104,7 @@ not ok 17 - test › power-assert operator: '!' values: 'Value is not falsy:': '''bar''' - at: 'Test.t [as fn] (test.js:30:4)' + at: 't (test.js:30:4)' ... ---tty-stream-chunk-separator # test › bad throws diff --git a/test/reporters/verbose.regular.log b/test/reporters/verbose.regular.log index fc15c2672..a1ede0001 100644 --- a/test/reporters/verbose.regular.log +++ b/test/reporters/verbose.regular.log @@ -15,7 +15,7 @@ ---tty-stream-chunk-separator Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js Error: Can't catch me - Test.t [as fn] (unhandled-rejection.js:4:17) + t (unhandled-rejection.js:4:17) @@ -167,7 +167,7 @@ stderr https://github.com/avajs/ava#throwsfunctionpromise-error-message fn (test.js:35:9) - Test.t [as fn] (test.js:37:11) + t (test.js:37:11) @@ -196,7 +196,7 @@ stderr https://github.com/avajs/ava#throwsfunctionpromise-error-message fn (test.js:42:9) - Test.t [as fn] (test.js:44:14) + t (test.js:44:14) diff --git a/test/test.js b/test/test.js index ccf91f3c2..947f612da 100644 --- a/test/test.js +++ b/test/test.js @@ -758,3 +758,10 @@ test('snapshot assertion cannot be skipped when updating snapshots', t => { t.is(result.error.message, 'Snapshot assertions cannot be skipped when updating snapshots'); }); }); + +test('implementation runs with null scope', t => { + return ava(function (a) { + a.pass(); + t.is(this, null); + }).run(); +}); From 8f3006b4733eb592606b180c24a75c70afdb8fa2 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 16 Feb 2018 13:36:39 +0000 Subject: [PATCH 07/18] Remove time-require Users can add it themselves if they want to measure the loading performance of their own code. It's not particularly useful during day-to-day AVA development. The code in `process-adapter.js` seems rather vestigial too. --- cli.js | 4 --- lib/process-adapter.js | 11 -------- package-lock.json | 59 ++---------------------------------------- package.json | 1 - 4 files changed, 2 insertions(+), 73 deletions(-) diff --git a/cli.js b/cli.js index 6c9203d62..58a13bab2 100755 --- a/cli.js +++ b/cli.js @@ -7,10 +7,6 @@ const importLocal = require('import-local'); if (importLocal(__filename)) { debug('Using local install of AVA'); } else { - if (debug.enabled) { - require('@ladjs/time-require'); // eslint-disable-line import/no-unassigned-import - } - try { require('./lib/cli').run(); } catch (err) { diff --git a/lib/process-adapter.js b/lib/process-adapter.js index 700fdbcd1..9d82d37dd 100644 --- a/lib/process-adapter.js +++ b/lib/process-adapter.js @@ -1,7 +1,6 @@ 'use strict'; const fs = require('fs'); const path = require('path'); -const debug = require('debug')('ava'); const sourceMapSupport = require('source-map-support'); const installPrecompiler = require('require-precompiled'); @@ -60,16 +59,6 @@ if (opts.tty) { }; } -if (debug.enabled) { - // Forward the `@ladjs/time-require` `--sorted` flag. - // Intended for internal optimization tests only. - if (opts._sorted) { - process.argv.push('--sorted'); - } - - require('@ladjs/time-require'); // eslint-disable-line import/no-unassigned-import -} - const sourceMapCache = new Map(); const cacheDir = opts.cacheDir; diff --git a/package-lock.json b/package-lock.json index d3cb0d614..cd58299a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -343,52 +343,6 @@ "arrify": "1.0.1" } }, - "@ladjs/time-require": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@ladjs/time-require/-/time-require-0.1.4.tgz", - "integrity": "sha512-weIbJqTMfQ4r1YX85u54DKfjLZs2jwn1XZ6tIOP/pFgMwhIN5BAtaCp/1wn9DzyLsDR9tW0R2NIePcVJ45ivQQ==", - "requires": { - "chalk": "0.4.0", - "date-time": "0.1.1", - "pretty-ms": "0.2.2", - "text-table": "0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=" - }, - "chalk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "requires": { - "ansi-styles": "1.0.0", - "has-color": "0.1.7", - "strip-ansi": "0.1.1" - } - }, - "parse-ms": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-0.1.2.tgz", - "integrity": "sha1-3T+iXtbC78e93hKtm0bBY6opIk4=" - }, - "pretty-ms": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-0.2.2.tgz", - "integrity": "sha1-2oeaaC/zOjcBEEbxPWJ/Z8c7hPY=", - "requires": { - "parse-ms": "0.1.2" - } - }, - "strip-ansi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" - } - } - }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -2080,11 +2034,6 @@ } } }, - "date-time": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-0.1.1.tgz", - "integrity": "sha1-7S9tk9l5DOL9ZtW1/z7dW7y/Owc=" - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -3778,11 +3727,6 @@ } } }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -8901,7 +8845,8 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true }, "the-argv": { "version": "1.0.0", diff --git a/package.json b/package.json index b4a934708..4bf393487 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,6 @@ "@babel/plugin-syntax-async-generators": "7.0.0-beta.44", "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.44", "@concordance/react": "^1.0.0", - "@ladjs/time-require": "^0.1.4", "ansi-escapes": "^3.1.0", "ansi-styles": "^3.2.1", "arr-flatten": "^1.1.0", From a4d962f3668c8ab19c8ec92b9eb4bcd6373eaf3f Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Fri, 16 Feb 2018 15:59:20 +0000 Subject: [PATCH 08/18] Explicitly exit CLI with a message rather than throwing an error Centralize the logging of these startup-related errors. Remove unnecessary handling of watcher errors. --- lib/babel-config.js | 4 +--- lib/cli.js | 39 ++++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/babel-config.js b/lib/babel-config.js index 7e7f715a2..7db885096 100644 --- a/lib/babel-config.js +++ b/lib/babel-config.js @@ -2,11 +2,9 @@ const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); -const figures = require('figures'); const configManager = require('hullabaloo-config-manager'); const md5Hex = require('md5-hex'); const makeDir = require('make-dir'); -const colors = require('./colors'); const stage4Path = require.resolve('../stage-4'); const syntaxAsyncGeneratorsPath = require.resolve('@babel/plugin-syntax-async-generators'); @@ -23,7 +21,7 @@ function validate(conf) { } if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions) || Object.keys(conf).length > 1) { - throw new Error(`${colors.error(figures.cross)} Unexpected Babel configuration for AVA. See ${chalk.underline('https://github.com/avajs/ava/blob/master/docs/recipes/babel.md')} for allowed values.`); + throw new Error(`Unexpected Babel configuration for AVA. See ${chalk.underline('https://github.com/avajs/ava/blob/master/docs/recipes/babel.md')} for allowed values.`); } return conf; diff --git a/lib/cli.js b/lib/cli.js index 3f978b090..00e8cbfdc 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,5 +1,6 @@ 'use strict'; const path = require('path'); +const chalk = require('chalk'); const updateNotifier = require('update-notifier'); const figures = require('figures'); const arrify = require('arrify'); @@ -8,7 +9,6 @@ const Promise = require('bluebird'); const pkgConf = require('pkg-conf'); const isCi = require('is-ci'); const Api = require('../api'); -const colors = require('./colors'); const VerboseReporter = require('./reporters/verbose'); const MiniReporter = require('./reporters/mini'); const TapReporter = require('./reporters/tap'); @@ -19,6 +19,11 @@ const babelConfigHelper = require('./babel-config'); // Bluebird specific Promise.longStackTraces(); +function exit(message) { + console.error(`\n ${chalk.red(figures.cross)} ${message}`); + process.exit(1); // eslint-disable-line unicorn/no-process-exit +} + exports.run = () => { const conf = pkgConf.sync('ava'); @@ -112,22 +117,29 @@ exports.run = () => { updateNotifier({pkg: cli.pkg}).notify(); if (cli.flags.watch && cli.flags.tap && !conf.tap) { - throw new Error(`${colors.error(figures.cross)} The TAP reporter is not available when using watch mode.`); + exit('The TAP reporter is not available when using watch mode.'); } if (cli.flags.watch && isCi) { - throw new Error(`${colors.error(figures.cross)} Watch mode is not available in CI, as it prevents AVA from terminating.`); + exit('Watch mode is not available in CI, as it prevents AVA from terminating.'); } if ( cli.flags.concurrency === '' || (cli.flags.concurrency && (!Number.isInteger(Number.parseFloat(cli.flags.concurrency)) || parseInt(cli.flags.concurrency, 10) < 0)) ) { - throw new Error(`${colors.error(figures.cross)} The --concurrency or -c flag must be provided with a nonnegative integer.`); + exit('The --concurrency or -c flag must be provided with a nonnegative integer.'); } if ('source' in conf) { - throw new Error(`${colors.error(figures.cross)} The 'source' option has been renamed. Use 'sources' instead.`); + exit('The \'source\' option has been renamed. Use \'sources\' instead.'); + } + + let babelConfig = null; + try { + babelConfig = babelConfigHelper.validate(conf.babel); + } catch (err) { + exit(err.message); } // Copy resultant cli.flags into conf for use with Api and elsewhere @@ -142,7 +154,7 @@ exports.run = () => { compileEnhancements: conf.compileEnhancements !== false, explicitTitles: conf.watch, match: arrify(conf.match), - babelConfig: babelConfigHelper.validate(conf.babel), + babelConfig, resolveTestsFrom: cli.input.length === 0 ? projectDir : process.cwd(), projectDir, timeout: conf.timeout, @@ -180,19 +192,8 @@ exports.run = () => { const files = cli.input.length ? cli.input : arrify(conf.files); if (conf.watch) { - try { - const watcher = new Watcher(logger, api, files, arrify(conf.sources)); - watcher.observeStdin(process.stdin); - } catch (err) { - if (err.name === 'AvaError') { - // An AvaError may be thrown if `chokidar` is not installed. Log it nicely. - console.error(` ${colors.error(figures.cross)} ${err.message}`); - logger.exit(1); - } else { - // Rethrow so it becomes an uncaught exception - throw err; - } - } + const watcher = new Watcher(logger, api, files, arrify(conf.sources)); + watcher.observeStdin(process.stdin); } else { api.run(files) .then(runStatus => { From 4c526b1cec5b20a8bc481e6ce86865c95117c44e Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Feb 2018 15:40:40 +0000 Subject: [PATCH 09/18] Reorganize worker code --- index.js | 2 +- lib/beautify-stack.js | 2 +- lib/concordance-options.js | 2 +- lib/fork.js | 4 +- lib/process-adapter.js | 107 ------------------ lib/worker/consume-argv.js | 5 + lib/worker/dependency-tracker.js | 32 ++++++ lib/worker/ensure-forked.js | 14 +++ lib/worker/fake-tty.js | 16 +++ lib/worker/ipc.js | 34 ++++++ lib/worker/load-chalk.js | 3 + lib/{ => worker}/main.js | 4 +- lib/{worker-options.js => worker/options.js} | 0 lib/worker/precompiler-hook.js | 40 +++++++ lib/{test-worker.js => worker/subprocess.js} | 108 ++++++++----------- profile.js | 4 +- test/assert.js | 2 +- test/beautify-stack.js | 2 +- test/fixture/worker-argv.js | 2 +- test/helper/report-worker.js | 2 +- test/hooks.js | 2 +- test/observable.js | 2 +- test/promise.js | 2 +- test/runner.js | 2 +- test/serialize-error.js | 2 +- test/test.js | 2 +- 26 files changed, 209 insertions(+), 188 deletions(-) delete mode 100644 lib/process-adapter.js create mode 100644 lib/worker/consume-argv.js create mode 100644 lib/worker/dependency-tracker.js create mode 100644 lib/worker/ensure-forked.js create mode 100644 lib/worker/fake-tty.js create mode 100644 lib/worker/ipc.js create mode 100644 lib/worker/load-chalk.js rename lib/{ => worker}/main.js (89%) rename lib/{worker-options.js => worker/options.js} (100%) create mode 100644 lib/worker/precompiler-hook.js rename lib/{test-worker.js => worker/subprocess.js} (70%) diff --git a/index.js b/index.js index 536279f20..24f28865b 100644 --- a/index.js +++ b/index.js @@ -4,5 +4,5 @@ if (process.env.AVA_PATH && process.env.AVA_PATH !== __dirname) { module.exports = require(process.env.AVA_PATH); } else { - module.exports = require('./lib/main'); + module.exports = require('./lib/worker/main'); } diff --git a/lib/beautify-stack.js b/lib/beautify-stack.js index 84ef8b00f..52959b593 100644 --- a/lib/beautify-stack.js +++ b/lib/beautify-stack.js @@ -6,7 +6,7 @@ const debug = require('debug')('ava'); // Ignore unimportant stack trace lines let ignoreStackLines = []; -const avaInternals = /\/ava\/(?:lib\/)?[\w-]+\.js:\d+:\d+\)?$/; +const avaInternals = /\/ava\/(?:lib\/|lib\/worker\/)?[\w-]+\.js:\d+:\d+\)?$/; const avaDependencies = /\/node_modules\/(?:append-transform|bluebird|empower-core|nyc|require-precompiled|(?:ava\/node_modules\/)?(?:babel-runtime|core-js))\//; const stackFrameLine = /^.+( \(.+:\d+:\d+\)|:\d+:\d+)$/; diff --git a/lib/concordance-options.js b/lib/concordance-options.js index 113d07335..831085a48 100644 --- a/lib/concordance-options.js +++ b/lib/concordance-options.js @@ -4,7 +4,7 @@ const chalk = require('chalk'); const stripAnsi = require('strip-ansi'); const cloneDeepWith = require('lodash.clonedeepwith'); const reactPlugin = require('@concordance/react'); -const options = require('./worker-options').get(); +const options = require('./worker/options').get(); // Wrap Concordance's React plugin. Change the name to avoid collisions if in // the future users can register plugins themselves. diff --git a/lib/fork.js b/lib/fork.js index e24340c8c..d224b169e 100644 --- a/lib/fork.js +++ b/lib/fork.js @@ -24,6 +24,8 @@ if (env.NODE_PATH) { // the presence of this variable allows it to require this one instead env.AVA_PATH = path.resolve(__dirname, '..'); +const workerPath = require.resolve('./worker/subprocess'); + module.exports = (file, opts, execArgv) => { opts = Object.assign({ file, @@ -36,7 +38,7 @@ module.exports = (file, opts, execArgv) => { const args = [JSON.stringify(opts), opts.color ? '--color' : '--no-color'].concat(opts.workerArgv); - const ps = childProcess.fork(path.join(__dirname, 'test-worker.js'), args, { + const ps = childProcess.fork(workerPath, args, { cwd: opts.projectDir, silent: true, env, diff --git a/lib/process-adapter.js b/lib/process-adapter.js deleted file mode 100644 index 9d82d37dd..000000000 --- a/lib/process-adapter.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); -const sourceMapSupport = require('source-map-support'); -const installPrecompiler = require('require-precompiled'); - -// Parse and re-emit AVA messages -process.on('message', message => { - if (!message.ava) { - return; - } - - process.emit(message.name, message.data); -}); - -exports.send = (name, data) => { - process.send({ - name: `ava-${name}`, - data, - ava: true - }); -}; - -// `process.channel` was added in Node.js 7.1.0, but the channel was available -// through an undocumented API as `process._channel`. -const ipcChannel = process.channel || process._channel; -let allowUnref = true; -exports.unrefChannel = () => { - if (allowUnref) { - ipcChannel.unref(); - } -}; -exports.forceRefChannel = () => { - allowUnref = false; - ipcChannel.ref(); -}; - -const opts = JSON.parse(process.argv[2]); -require('./worker-options').set(opts); - -// Remove arguments received from fork.js and leave those specified by the user. -process.argv.splice(2, 2); - -// Fake TTY support -if (opts.tty) { - process.stdout.isTTY = true; - process.stdout.columns = opts.tty.columns || 80; - process.stdout.rows = opts.tty.rows; - - const tty = require('tty'); - const isatty = tty.isatty; - - tty.isatty = function (fd) { - if (fd === 1 || fd === process.stdout) { - return true; - } - - return isatty(fd); - }; -} - -const sourceMapCache = new Map(); -const cacheDir = opts.cacheDir; - -exports.installSourceMapSupport = () => { - sourceMapSupport.install({ - environment: 'node', - handleUncaughtExceptions: false, - retrieveSourceMap(source) { - if (sourceMapCache.has(source)) { - return { - url: source, - map: fs.readFileSync(sourceMapCache.get(source), 'utf8') - }; - } - } - }); -}; - -exports.installPrecompilerHook = () => { - installPrecompiler(filename => { - const precompiled = opts.precompiled[filename]; - - if (precompiled) { - sourceMapCache.set(filename, path.join(cacheDir, `${precompiled}.js.map`)); - return fs.readFileSync(path.join(cacheDir, `${precompiled}.js`), 'utf8'); - } - - return null; - }); -}; - -/* eslint-disable node/no-deprecated-api */ -exports.installDependencyTracking = (dependencies, testPath) => { - Object.keys(require.extensions).forEach(ext => { - const wrappedHandler = require.extensions[ext]; - - require.extensions[ext] = (module, filename) => { - if (filename !== testPath) { - dependencies.add(filename); - } - - wrappedHandler(module, filename); - }; - }); -}; -/* eslint-enable node/no-deprecated-api */ diff --git a/lib/worker/consume-argv.js b/lib/worker/consume-argv.js new file mode 100644 index 000000000..a6ef18f94 --- /dev/null +++ b/lib/worker/consume-argv.js @@ -0,0 +1,5 @@ +'use strict'; +require('./options').set(JSON.parse(process.argv[2])); + +// Remove arguments received from fork.js and leave those specified by the user. +process.argv.splice(2, 2); diff --git a/lib/worker/dependency-tracker.js b/lib/worker/dependency-tracker.js new file mode 100644 index 000000000..524d2297b --- /dev/null +++ b/lib/worker/dependency-tracker.js @@ -0,0 +1,32 @@ +'use strict'; +/* eslint-disable node/no-deprecated-api */ + +const seenDependencies = new Set(); +function getAll() { + return Array.from(seenDependencies); +} +exports.getAll = getAll; + +function track(filename) { + if (seenDependencies.has(filename)) { + return; + } + + seenDependencies.add(filename); +} +exports.track = track; + +function install(testPath) { + Object.keys(require.extensions).forEach(ext => { + const wrappedHandler = require.extensions[ext]; + + require.extensions[ext] = (module, filename) => { + if (filename !== testPath) { + track(filename); + } + + wrappedHandler(module, filename); + }; + }); +} +exports.install = install; diff --git a/lib/worker/ensure-forked.js b/lib/worker/ensure-forked.js new file mode 100644 index 000000000..f8c4a9fe6 --- /dev/null +++ b/lib/worker/ensure-forked.js @@ -0,0 +1,14 @@ +'use strict'; +const path = require('path'); +const chalk = require('chalk'); // Use default Chalk instance. + +// Check if the test is being run without AVA cli +const isForked = typeof process.send === 'function'; +if (!isForked) { + const fp = path.relative('.', process.argv[1]); + + console.log(); + console.error(`Test files must be run with the AVA CLI:\n\n ${chalk.grey.dim('$')} ${chalk.cyan('ava ' + fp)}\n`); + + process.exit(1); // eslint-disable-line unicorn/no-process-exit +} diff --git a/lib/worker/fake-tty.js b/lib/worker/fake-tty.js new file mode 100644 index 000000000..8f3a45bdb --- /dev/null +++ b/lib/worker/fake-tty.js @@ -0,0 +1,16 @@ +'use strict'; +const tty = require('tty'); +const options = require('./options').get(); + +if (options.tty) { + Object.assign(process.stdout, {isTTY: true}, options.tty); + + const isatty = tty.isatty; + tty.isatty = function (fd) { + if (fd === 1 || fd === process.stdout) { + return true; + } + + return isatty(fd); + }; +} diff --git a/lib/worker/ipc.js b/lib/worker/ipc.js new file mode 100644 index 000000000..2164d3ffa --- /dev/null +++ b/lib/worker/ipc.js @@ -0,0 +1,34 @@ +'use strict'; + +// `process.channel` was added in Node.js 7.1.0, but the channel was available +// through an undocumented API as `process._channel`. +const channel = process.channel || process._channel; + +// Parse and re-emit AVA messages +process.on('message', message => { + if (!message.ava) { + return; + } + + process.emit(message.name, message.data); +}); + +exports.send = (name, data) => { + process.send({ + name: `ava-${name}`, + data, + ava: true + }); +}; + +let allowUnref = true; +exports.unrefChannel = () => { + if (allowUnref) { + channel.unref(); + } +}; + +exports.forceRefChannel = () => { + allowUnref = false; + channel.ref(); +}; diff --git a/lib/worker/load-chalk.js b/lib/worker/load-chalk.js new file mode 100644 index 000000000..904b602f3 --- /dev/null +++ b/lib/worker/load-chalk.js @@ -0,0 +1,3 @@ +'use strict'; +/* eslint-disable import/no-unassigned-import */ +require('chalk'); // This processes the --color/--no-color argument passed by fork.js diff --git a/lib/main.js b/lib/worker/main.js similarity index 89% rename from lib/main.js rename to lib/worker/main.js index ec576f00c..d1f2bbe2e 100644 --- a/lib/main.js +++ b/lib/worker/main.js @@ -1,7 +1,5 @@ 'use strict'; -const worker = require('./test-worker'); - -const runner = worker.getRunner(); +const runner = require('./subprocess').getRunner(); const makeCjsExport = () => { function test() { diff --git a/lib/worker-options.js b/lib/worker/options.js similarity index 100% rename from lib/worker-options.js rename to lib/worker/options.js diff --git a/lib/worker/precompiler-hook.js b/lib/worker/precompiler-hook.js new file mode 100644 index 000000000..9229ee8ea --- /dev/null +++ b/lib/worker/precompiler-hook.js @@ -0,0 +1,40 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const sourceMapSupport = require('source-map-support'); +const installPrecompiler = require('require-precompiled'); +const options = require('./options').get(); + +const sourceMapCache = new Map(); +const cacheDir = options.cacheDir; + +function installSourceMapSupport() { + sourceMapSupport.install({ + environment: 'node', + handleUncaughtExceptions: false, + retrieveSourceMap(source) { + if (sourceMapCache.has(source)) { + return { + url: source, + map: fs.readFileSync(sourceMapCache.get(source), 'utf8') + }; + } + } + }); +} + +function install() { + installSourceMapSupport(); + + installPrecompiler(filename => { + const precompiled = options.precompiled[filename]; + + if (precompiled) { + sourceMapCache.set(filename, path.join(cacheDir, `${precompiled}.js.map`)); + return fs.readFileSync(path.join(cacheDir, `${precompiled}.js`), 'utf8'); + } + + return null; + }); +} +exports.install = install; diff --git a/lib/test-worker.js b/lib/worker/subprocess.js similarity index 70% rename from lib/test-worker.js rename to lib/worker/subprocess.js index d19ae0a25..8b2dabfdb 100644 --- a/lib/test-worker.js +++ b/lib/worker/subprocess.js @@ -1,39 +1,31 @@ 'use strict'; - -// Check if the test is being run without AVA cli -{ - const path = require('path'); - const chalk = require('chalk'); // This processes the --color/--no-color argument passed by fork.js - - const isForked = typeof process.send === 'function'; - if (!isForked) { - const fp = path.relative('.', process.argv[1]); - - console.log(); - console.error('Test files must be run with the AVA CLI:\n\n ' + chalk.grey.dim('$') + ' ' + chalk.cyan('ava ' + fp) + '\n'); - - process.exit(1); // eslint-disable-line unicorn/no-process-exit - } -} - const currentlyUnhandled = require('currently-unhandled')(); const isObj = require('is-obj'); -const adapter = require('./process-adapter'); -const serializeError = require('./serialize-error'); -const opts = require('./worker-options').get(); -const Runner = require('./runner'); +/* eslint-disable unicorn/no-process-exit */ +/* eslint-disable import/no-unassigned-import */ +require('./ensure-forked'); +require('./load-chalk'); +require('./consume-argv'); +require('./fake-tty'); + +const Runner = require('../runner'); +const serializeError = require('../serialize-error'); +const dependencyTracking = require('./dependency-tracker'); +const ipc = require('./ipc'); +const options = require('./options').get(); +const precompilerHook = require('./precompiler-hook'); const runner = new Runner({ - failFast: opts.failFast, - failWithoutAssertions: opts.failWithoutAssertions, - file: opts.file, - match: opts.match, - projectDir: opts.projectDir, - runOnlyExclusive: opts.runOnlyExclusive, - serial: opts.serial, - snapshotDir: opts.snapshotDir, - updateSnapshots: opts.updateSnapshots + failFast: options.failFast, + failWithoutAssertions: options.failWithoutAssertions, + file: options.file, + match: options.match, + projectDir: options.projectDir, + runOnlyExclusive: options.runOnlyExclusive, + serial: options.serial, + snapshotDir: options.snapshotDir, + updateSnapshots: options.updateSnapshots }); let accessedRunner = false; @@ -42,12 +34,9 @@ exports.getRunner = () => { return runner; }; -// Store details about the test run, to be sent to the parent process later. -const dependencies = new Set(); +runner.on('dependency', dependencyTracking.track); + const touchedFiles = new Set(); -runner.on('dependency', file => { - dependencies.add(file); -}); runner.on('touched', files => { for (const file of files) { touchedFiles.add(file); @@ -55,13 +44,13 @@ runner.on('touched', files => { }); runner.on('start', started => { - adapter.send('stats', { + ipc.send('stats', { testCount: started.stats.testCount, hasExclusive: started.stats.hasExclusive }); for (const partial of started.skippedTests) { - adapter.send('test', { + ipc.send('test', { duration: null, error: null, failing: partial.failing, @@ -73,7 +62,7 @@ runner.on('start', started => { }); } for (const title of started.todoTitles) { - adapter.send('test', { + ipc.send('test', { duration: null, error: null, failing: false, @@ -94,7 +83,7 @@ runner.on('start', started => { }); runner.on('hook-failed', result => { - adapter.send('test', { + ipc.send('test', { duration: result.duration, error: serializeError(result.error), failing: result.metadata.failing, @@ -107,7 +96,7 @@ runner.on('hook-failed', result => { }); runner.on('test', result => { - adapter.send('test', { + ipc.send('test', { duration: result.duration, error: result.passed ? null : serializeError(result.error), failing: result.metadata.failing, @@ -130,7 +119,7 @@ function exit() { exiting = true; // Reference the IPC channel so the exit sequence can be completed. - adapter.forceRefChannel(); + ipc.forceRefChannel(); const stats = { failCount: runner.stats.failCount + runner.stats.failedHookCount, @@ -140,15 +129,11 @@ function exit() { testCount: runner.stats.testCount, todoCount: runner.stats.todoCount }; - adapter.send('results', {stats}); -} - -function attributeLeakedError(err) { - return runner.attributeLeakedError(err); + ipc.send('results', {stats}); } function handleUncaughtException(exception) { - if (attributeLeakedError(exception)) { + if (runner.attributeLeakedError(exception)) { return; } @@ -168,14 +153,14 @@ function handleUncaughtException(exception) { // Ensure the IPC channel is referenced. The uncaught exception will kick off // the teardown sequence, for which the messages must be received. - adapter.forceRefChannel(); + ipc.forceRefChannel(); - adapter.send('uncaughtException', {exception: serialized}); + ipc.send('uncaughtException', {exception: serialized}); } const attributedRejections = new Set(); process.on('unhandledRejection', (reason, promise) => { - if (attributeLeakedError(reason)) { + if (runner.attributeLeakedError(reason)) { attributedRejections.add(promise); } }); @@ -191,7 +176,7 @@ process.on('ava-teardown', () => { tearingDown = true; // Reference the IPC channel so the teardown sequence can be completed. - adapter.forceRefChannel(); + ipc.forceRefChannel(); let rejections = currentlyUnhandled() .filter(rejection => !attributedRejections.has(rejection.promise)); @@ -207,15 +192,15 @@ process.on('ava-teardown', () => { return serializeError(reason); }); - adapter.send('unhandledRejections', {rejections}); + ipc.send('unhandledRejections', {rejections}); } // Include dependencies in the final teardown message. This ensures the full // set of dependencies is included no matter how the process exits, unless // it flat out crashes. 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), + ipc.send('teardown', { + dependencies: dependencyTracking.getAll(), touchedFiles: Array.from(touchedFiles) }); }); @@ -233,16 +218,15 @@ process.on('ava-peer-failed', () => { }); // Store value in case to prevent required modules from modifying it. -const testPath = opts.file; +const testPath = options.file; -// Install before processing opts.require, so if helpers are added to the +// Install before processing options.require, so if helpers are added to the // require configuration the *compiled* helper will be loaded. -adapter.installDependencyTracking(dependencies, testPath); -adapter.installSourceMapSupport(); -adapter.installPrecompilerHook(); +dependencyTracking.install(testPath); +precompilerHook.install(); try { - (opts.require || []).forEach(x => { + (options.require || []).forEach(x => { const required = require(x); try { @@ -256,7 +240,7 @@ try { } catch (err) { handleUncaughtException(err); } finally { - adapter.send('loaded-file', {avaRequired: accessedRunner}); + ipc.send('loaded-file', {avaRequired: accessedRunner}); if (accessedRunner) { // Unreference the IPC channel if the test file required AVA. This stops it @@ -264,6 +248,6 @@ try { // used to detect when tests stall. // If AVA was not required then the parent process will initiated a teardown // sequence, for which this process ought to stay active. - adapter.unrefChannel(); + ipc.unrefChannel(); } } diff --git a/profile.js b/profile.js index 35acdce80..549406fbb 100644 --- a/profile.js +++ b/profile.js @@ -154,7 +154,7 @@ babelConfigHelper.build(process.cwd(), cacheDir, babelConfigHelper.validate(conf console.log(stack); }); - // `test-worker` will read process.argv[2] for options + // The "subprocess" will read process.argv[2] for options process.argv[2] = JSON.stringify(opts); process.argv.length = 3; @@ -163,6 +163,6 @@ babelConfigHelper.build(process.cwd(), cacheDir, babelConfigHelper.validate(conf } setImmediate(() => { - require('./lib/test-worker'); // eslint-disable-line import/no-unassigned-import + require('./lib/worker/subprocess'); // eslint-disable-line import/no-unassigned-import }); }); diff --git a/test/assert.js b/test/assert.js index 34c3d3af3..69f9587f0 100644 --- a/test/assert.js +++ b/test/assert.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({color: false}); +require('../lib/worker/options').set({color: false}); const path = require('path'); const stripAnsi = require('strip-ansi'); diff --git a/test/beautify-stack.js b/test/beautify-stack.js index e777baeaf..c248cb350 100644 --- a/test/beautify-stack.js +++ b/test/beautify-stack.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({}); +require('../lib/worker/options').set({}); const proxyquire = require('proxyquire').noPreserveCache(); const test = require('tap').test; diff --git a/test/fixture/worker-argv.js b/test/fixture/worker-argv.js index 6af882fa4..5e14e3cc1 100644 --- a/test/fixture/worker-argv.js +++ b/test/fixture/worker-argv.js @@ -1,5 +1,5 @@ import test from '../..'; test('argv', t => { - t.deepEqual(process.argv, [process.execPath, require.resolve('../../lib/test-worker.js'), '--hello', 'world']); + t.deepEqual(process.argv, [process.execPath, require.resolve('../../lib/worker/subprocess.js'), '--hello', 'world']); }); diff --git a/test/helper/report-worker.js b/test/helper/report-worker.js index 08dcd2544..1971fe8ab 100644 --- a/test/helper/report-worker.js +++ b/test/helper/report-worker.js @@ -1,3 +1,3 @@ 'use strict'; require('./fix-reporter-env').onlyColors(); -require('../../lib/test-worker'); // eslint-disable-line import/no-unassigned-import +require('../../lib/worker/subprocess'); // eslint-disable-line import/no-unassigned-import diff --git a/test/hooks.js b/test/hooks.js index 78229f77c..02e922982 100644 --- a/test/hooks.js +++ b/test/hooks.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({}); +require('../lib/worker/options').set({}); const path = require('path'); const test = require('tap').test; diff --git a/test/observable.js b/test/observable.js index f19a1e7d8..129773970 100644 --- a/test/observable.js +++ b/test/observable.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({}); +require('../lib/worker/options').set({}); const test = require('tap').test; const Test = require('../lib/test'); diff --git a/test/promise.js b/test/promise.js index 86d2283b1..fed496f48 100644 --- a/test/promise.js +++ b/test/promise.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({color: false}); +require('../lib/worker/options').set({color: false}); const Promise = require('bluebird'); const test = require('tap').test; diff --git a/test/runner.js b/test/runner.js index 4ed269dd0..21485b6e3 100644 --- a/test/runner.js +++ b/test/runner.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({}); +require('../lib/worker/options').set({}); const test = require('tap').test; const Runner = require('../lib/runner'); diff --git a/test/serialize-error.js b/test/serialize-error.js index 6fc4efcb0..52750cf9d 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({}); +require('../lib/worker/options').set({}); const fs = require('fs'); const path = require('path'); diff --git a/test/test.js b/test/test.js index 947f612da..730d827c2 100644 --- a/test/test.js +++ b/test/test.js @@ -1,5 +1,5 @@ 'use strict'; -require('../lib/worker-options').set({color: false}); +require('../lib/worker/options').set({color: false}); const path = require('path'); const React = require('react'); From 2f9e093e93336a6fe75a7f25f6e9b20a2be24c1a Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Feb 2018 18:25:32 +0000 Subject: [PATCH 10/18] Use an AVA-specific Chalk instance --- lib/babel-config.js | 2 +- lib/chalk.js | 16 ++++++++++++++++ lib/cli.js | 19 ++++++++++--------- lib/code-excerpt.js | 2 +- lib/colors.js | 2 +- lib/concordance-options.js | 5 ++--- lib/reporters/format-serialized-error.js | 2 +- lib/reporters/improper-usage-messages.js | 2 +- lib/reporters/mini.js | 2 +- lib/reporters/verbose.js | 2 +- lib/run-status.js | 2 +- lib/worker/load-chalk.js | 3 +-- profile.js | 1 + test/api.js | 2 ++ test/assert.js | 1 + test/babel-config.js | 2 ++ test/beautify-stack.js | 1 + test/code-excerpt.js | 2 ++ test/helper/fix-reporter-env.js | 1 + test/hooks.js | 1 + test/observable.js | 1 + test/promise.js | 1 + test/reporters/improper-usage-messages.js | 2 ++ test/run-status.js | 2 ++ test/runner.js | 1 + test/serialize-error.js | 1 + test/test.js | 1 + 27 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 lib/chalk.js diff --git a/lib/babel-config.js b/lib/babel-config.js index 7db885096..c087d5b41 100644 --- a/lib/babel-config.js +++ b/lib/babel-config.js @@ -1,10 +1,10 @@ 'use strict'; const fs = require('fs'); const path = require('path'); -const chalk = require('chalk'); const configManager = require('hullabaloo-config-manager'); const md5Hex = require('md5-hex'); const makeDir = require('make-dir'); +const chalk = require('./chalk').get(); const stage4Path = require.resolve('../stage-4'); const syntaxAsyncGeneratorsPath = require.resolve('@babel/plugin-syntax-async-generators'); diff --git a/lib/chalk.js b/lib/chalk.js new file mode 100644 index 000000000..ae27dfe9e --- /dev/null +++ b/lib/chalk.js @@ -0,0 +1,16 @@ +'use strict'; +const Chalk = require('chalk').constructor; + +let ctx = null; +exports.get = () => { + if (!ctx) { + throw new Error('Chalk has not yet been configured'); + } + return ctx; +}; +exports.set = options => { + if (ctx) { + throw new Error('Chalk has already been configured'); + } + ctx = new Chalk(options); +}; diff --git a/lib/cli.js b/lib/cli.js index 00e8cbfdc..c4e18428f 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,6 +1,5 @@ 'use strict'; const path = require('path'); -const chalk = require('chalk'); const updateNotifier = require('update-notifier'); const figures = require('figures'); const arrify = require('arrify'); @@ -8,19 +7,12 @@ const meow = require('meow'); const Promise = require('bluebird'); const pkgConf = require('pkg-conf'); const isCi = require('is-ci'); -const Api = require('../api'); -const VerboseReporter = require('./reporters/verbose'); -const MiniReporter = require('./reporters/mini'); -const TapReporter = require('./reporters/tap'); -const Logger = require('./logger'); -const Watcher = require('./watcher'); -const babelConfigHelper = require('./babel-config'); // Bluebird specific Promise.longStackTraces(); function exit(message) { - console.error(`\n ${chalk.red(figures.cross)} ${message}`); + console.error(`\n ${require('./chalk').get().red(figures.cross)} ${message}`); process.exit(1); // eslint-disable-line unicorn/no-process-exit } @@ -115,6 +107,7 @@ exports.run = () => { }); updateNotifier({pkg: cli.pkg}).notify(); + require('./chalk').set({enabled: cli.flags.color}); if (cli.flags.watch && cli.flags.tap && !conf.tap) { exit('The TAP reporter is not available when using watch mode.'); @@ -135,6 +128,14 @@ exports.run = () => { exit('The \'source\' option has been renamed. Use \'sources\' instead.'); } + const Api = require('../api'); + const VerboseReporter = require('./reporters/verbose'); + const MiniReporter = require('./reporters/mini'); + const TapReporter = require('./reporters/tap'); + const Logger = require('./logger'); + const Watcher = require('./watcher'); + const babelConfigHelper = require('./babel-config'); + let babelConfig = null; try { babelConfig = babelConfigHelper.validate(conf.babel); diff --git a/lib/code-excerpt.js b/lib/code-excerpt.js index aa619a0b2..7f2a4623f 100644 --- a/lib/code-excerpt.js +++ b/lib/code-excerpt.js @@ -3,7 +3,7 @@ const fs = require('fs'); const equalLength = require('equal-length'); const codeExcerpt = require('code-excerpt'); const truncate = require('cli-truncate'); -const chalk = require('chalk'); +const chalk = require('./chalk').get(); const formatLineNumber = (lineNumber, maxLineNumber) => ' '.repeat(Math.max(0, String(maxLineNumber).length - String(lineNumber).length)) + lineNumber; diff --git a/lib/colors.js b/lib/colors.js index 75fb4d8aa..79eb145d1 100644 --- a/lib/colors.js +++ b/lib/colors.js @@ -1,5 +1,5 @@ 'use strict'; -const chalk = require('chalk'); +const chalk = require('./chalk').get(); module.exports = { log: chalk.gray, diff --git a/lib/concordance-options.js b/lib/concordance-options.js index 831085a48..b934ab58a 100644 --- a/lib/concordance-options.js +++ b/lib/concordance-options.js @@ -1,10 +1,9 @@ '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('./worker/options').get(); +const chalk = require('./chalk').get(); // Wrap Concordance's React plugin. Change the name to avoid collisions if in // the future users can register plugins themselves. @@ -124,7 +123,7 @@ const plainTheme = cloneDeepWith(colorTheme, value => { } }); -const theme = options.color === false ? plainTheme : colorTheme; +const theme = chalk.enabled ? colorTheme : plainTheme; exports.default = {maxDepth: 3, plugins, theme}; exports.diff = {maxDepth: 1, plugins, theme}; exports.snapshotManager = {plugins, theme: plainTheme}; diff --git a/lib/reporters/format-serialized-error.js b/lib/reporters/format-serialized-error.js index 6ab59e47c..a5af5e9ea 100644 --- a/lib/reporters/format-serialized-error.js +++ b/lib/reporters/format-serialized-error.js @@ -1,6 +1,6 @@ 'use strict'; -const chalk = require('chalk'); const trimOffNewlines = require('trim-off-newlines'); +const chalk = require('../chalk').get(); function formatSerializedError(error) { const printMessage = error.values.length === 0 ? diff --git a/lib/reporters/improper-usage-messages.js b/lib/reporters/improper-usage-messages.js index 014a4bf0d..a7e72191b 100644 --- a/lib/reporters/improper-usage-messages.js +++ b/lib/reporters/improper-usage-messages.js @@ -1,5 +1,5 @@ 'use strict'; -const chalk = require('chalk'); +const chalk = require('../chalk').get(); exports.forError = error => { if (!error.improperUsage) { diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index 1968d0977..ac092636b 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -4,13 +4,13 @@ const cliCursor = require('cli-cursor'); const lastLineTracker = require('last-line-stream/tracker'); const plur = require('plur'); const spinners = require('cli-spinners'); -const chalk = require('chalk'); const figures = require('figures'); const cliTruncate = require('cli-truncate'); const cross = require('figures').cross; const indentString = require('indent-string'); const ansiEscapes = require('ansi-escapes'); const trimOffNewlines = require('trim-off-newlines'); +const chalk = require('../chalk').get(); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); const formatSerializedError = require('./format-serialized-error'); diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index 4850fc901..3ff68e78c 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -2,9 +2,9 @@ const indentString = require('indent-string'); const prettyMs = require('pretty-ms'); const figures = require('figures'); -const chalk = require('chalk'); const plur = require('plur'); const trimOffNewlines = require('trim-off-newlines'); +const chalk = require('../chalk').get(); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); const formatSerializedError = require('./format-serialized-error'); diff --git a/lib/run-status.js b/lib/run-status.js index aae0367c8..8e266e0e8 100644 --- a/lib/run-status.js +++ b/lib/run-status.js @@ -1,9 +1,9 @@ 'use strict'; const EventEmitter = require('events'); -const chalk = require('chalk'); const flatten = require('arr-flatten'); const figures = require('figures'); const autoBind = require('auto-bind'); +const chalk = require('./chalk').get(); const prefixTitle = require('./prefix-title'); function sum(arr, key) { diff --git a/lib/worker/load-chalk.js b/lib/worker/load-chalk.js index 904b602f3..585bface9 100644 --- a/lib/worker/load-chalk.js +++ b/lib/worker/load-chalk.js @@ -1,3 +1,2 @@ 'use strict'; -/* eslint-disable import/no-unassigned-import */ -require('chalk'); // This processes the --color/--no-color argument passed by fork.js +require('../chalk').set(); // Use default options, which means processing the --color/--no-color argument passed by fork.js diff --git a/profile.js b/profile.js index 549406fbb..7cb790665 100644 --- a/profile.js +++ b/profile.js @@ -1,4 +1,5 @@ 'use strict'; +require('./lib/worker/load-chalk'); // eslint-disable-line import/no-unassigned-import // Iron-node does not work with forked processes // This cli command will run a single file in the current process. diff --git a/test/api.js b/test/api.js index 4be2befb0..6b4a4cb5e 100644 --- a/test/api.js +++ b/test/api.js @@ -1,4 +1,6 @@ 'use strict'; +require('../lib/chalk').set(); + const path = require('path'); const fs = require('fs'); const figures = require('figures'); diff --git a/test/assert.js b/test/assert.js index 69f9587f0..4f86e93f5 100644 --- a/test/assert.js +++ b/test/assert.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({color: false}); const path = require('path'); diff --git a/test/babel-config.js b/test/babel-config.js index 2fbc05754..f74160258 100644 --- a/test/babel-config.js +++ b/test/babel-config.js @@ -1,4 +1,6 @@ 'use strict'; +require('../lib/chalk').set(); + const assert = require('assert'); const fs = require('fs'); const path = require('path'); diff --git a/test/beautify-stack.js b/test/beautify-stack.js index c248cb350..90c852e52 100644 --- a/test/beautify-stack.js +++ b/test/beautify-stack.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({}); const proxyquire = require('proxyquire').noPreserveCache(); diff --git a/test/code-excerpt.js b/test/code-excerpt.js index 534196c01..0c13c547a 100644 --- a/test/code-excerpt.js +++ b/test/code-excerpt.js @@ -1,4 +1,6 @@ 'use strict'; +require('../lib/chalk').set(); + const fs = require('fs'); const tempWrite = require('temp-write'); const chalk = require('chalk'); diff --git a/test/helper/fix-reporter-env.js b/test/helper/fix-reporter-env.js index 79c6c41cb..12c195647 100644 --- a/test/helper/fix-reporter-env.js +++ b/test/helper/fix-reporter-env.js @@ -17,6 +17,7 @@ module.exports = () => { }); fixColors(); + require('../../lib/chalk').set({enabled: true, level: 3}); }; module.exports.onlyColors = fixColors; diff --git a/test/hooks.js b/test/hooks.js index 02e922982..d5c7c5b58 100644 --- a/test/hooks.js +++ b/test/hooks.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({}); const path = require('path'); diff --git a/test/observable.js b/test/observable.js index 129773970..7ce179bc9 100644 --- a/test/observable.js +++ b/test/observable.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({}); const test = require('tap').test; diff --git a/test/promise.js b/test/promise.js index fed496f48..7d7a3d50b 100644 --- a/test/promise.js +++ b/test/promise.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({color: false}); const Promise = require('bluebird'); diff --git a/test/reporters/improper-usage-messages.js b/test/reporters/improper-usage-messages.js index e44458d86..190d87de0 100644 --- a/test/reporters/improper-usage-messages.js +++ b/test/reporters/improper-usage-messages.js @@ -1,4 +1,6 @@ 'use strict'; +require('../../lib/chalk').set(); + const test = require('tap').test; const improperUsageMessages = require('../../lib/reporters/improper-usage-messages'); diff --git a/test/run-status.js b/test/run-status.js index 5ad30c563..214d3439e 100644 --- a/test/run-status.js +++ b/test/run-status.js @@ -1,4 +1,6 @@ 'use strict'; +require('../lib/chalk').set(); + const path = require('path'); const test = require('tap').test; const chalk = require('chalk'); diff --git a/test/runner.js b/test/runner.js index 21485b6e3..9aafa11d3 100644 --- a/test/runner.js +++ b/test/runner.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({}); const test = require('tap').test; diff --git a/test/serialize-error.js b/test/serialize-error.js index 52750cf9d..ec2777617 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({}); const fs = require('fs'); diff --git a/test/test.js b/test/test.js index 730d827c2..1d0a548e7 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,5 @@ 'use strict'; +require('../lib/chalk').set(); require('../lib/worker/options').set({color: false}); const path = require('path'); From 832bc34436349f5cceb7d01185cac089ff72a42d Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Feb 2018 18:46:12 +0000 Subject: [PATCH 11/18] Smarter error serialization * Built-in recovery in case serialization fails * Handle non-error objects and format such values using Concordance * Update reporters to properly handle non-error objects --- lib/reporters/mini.js | 2 +- lib/reporters/tap.js | 5 ++ lib/reporters/verbose.js | 4 +- lib/serialize-error.js | 87 +++++++++++++++++++----------- lib/worker/subprocess.js | 37 +++---------- package.json | 1 - test/reporters/mini.regular.log | 2 +- test/reporters/tap.regular.log | 7 +-- test/reporters/verbose.regular.log | 2 +- test/serialize-error.js | 15 +++--- 10 files changed, 84 insertions(+), 78 deletions(-) diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index ac092636b..b00c945ea 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -243,7 +243,7 @@ class MiniReporter { status += indentString(colors.stack(err.summary), 2) + '\n'; status += indentString(colors.errorStack(err.stack), 2) + '\n\n'; } else { - status += ' Threw non-error: ' + err.summary + '\n'; + status += indentString(trimOffNewlines(err.formatted), 2) + '\n'; } } }); diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index b6c60fc2e..dbfa7f1a2 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -26,6 +26,11 @@ function dumpError(error, includeMessage) { } } + if (error.nonErrorObject) { + obj.message = 'Non-error object'; + obj.formatted = stripAnsi(error.formatted); + } + if (error.stack) { obj.at = error.stack.split('\n')[0]; } diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index 3ff68e78c..de08e9e55 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -70,8 +70,8 @@ class VerboseReporter { if (err.name) { output += indentString(colors.stack(err.summary), 2) + '\n'; output += indentString(colors.errorStack(err.stack), 2) + '\n\n'; - } else { - output += ' Threw non-error: ' + err.summary + '\n'; + } else if (err.nonErrorObject) { + output += indentString(trimOffNewlines(err.formatted), 2) + '\n'; } output += '\n'; diff --git a/lib/serialize-error.js b/lib/serialize-error.js index 969f78e02..502f129fe 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -1,9 +1,12 @@ 'use strict'; const path = require('path'); const cleanYamlObject = require('clean-yaml-object'); +const concordance = require('concordance'); +const isError = require('is-error'); const StackUtils = require('stack-utils'); const assert = require('./assert'); const beautifyStack = require('./beautify-stack'); +const concordanceOptions = require('./concordance-options').default; function isAvaAssertionError(source) { return source instanceof assert.AssertionError; @@ -46,52 +49,49 @@ function buildSource(source) { }; } -module.exports = error => { - const stack = typeof error.stack === 'string' ? - beautifyStack(error.stack) : - null; +function trySerializeError(err, shouldBeautifyStack) { + const stack = shouldBeautifyStack ? beautifyStack(err.stack) : err.stack; const retval = { - avaAssertionError: isAvaAssertionError(error), - source: buildSource(extractSource(stack)) + avaAssertionError: isAvaAssertionError(err), + nonErrorObject: false, + source: buildSource(extractSource(stack)), + stack }; - if (stack) { - retval.stack = stack; - } if (retval.avaAssertionError) { - retval.improperUsage = error.improperUsage; - retval.message = error.message; - retval.name = error.name; - retval.statements = error.statements; - retval.values = error.values; - - if (error.fixedSource) { - const source = buildSource(error.fixedSource); + retval.improperUsage = err.improperUsage; + retval.message = err.message; + retval.name = err.name; + retval.statements = err.statements; + retval.values = err.values; + + if (err.fixedSource) { + const source = buildSource(err.fixedSource); if (source) { retval.source = source; } } - if (error.assertion) { - retval.assertion = error.assertion; + if (err.assertion) { + retval.assertion = err.assertion; } - if (error.operator) { - retval.operator = error.operator; + if (err.operator) { + retval.operator = err.operator; } } else { - retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties - if (typeof error.message === 'string') { - retval.message = error.message; + retval.object = cleanYamlObject(err, filter); // Cleanly copy non-standard properties + if (typeof err.message === 'string') { + retval.message = err.message; } - if (typeof error.name === 'string') { - retval.name = error.name; + if (typeof err.name === 'string') { + retval.name = err.name; } } - if (typeof error.stack === 'string') { - const lines = error.stack.split('\n'); - if (error.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) { + if (typeof err.stack === 'string') { + const lines = err.stack.split('\n'); + if (err.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) { retval.summary = ''; for (const line of lines) { retval.summary += line + '\n'; @@ -103,9 +103,32 @@ module.exports = error => { } else { retval.summary = lines[0]; } - } else { - retval.summary = JSON.stringify(error); } return retval; -}; +} + +function serializeError(origin, shouldBeautifyStack, err) { + if (!isError(err)) { + return { + avaAssertionError: false, + nonErrorObject: true, + formatted: concordance.formatDescriptor(concordance.describe(err, concordanceOptions), concordanceOptions) + }; + } + + try { + return trySerializeError(err, shouldBeautifyStack); + } catch (_) { + const replacement = new Error(`${origin}: Could not serialize error`); + return { + avaAssertionError: false, + nonErrorObject: false, + name: replacement.name, + message: replacement.message, + stack: replacement.stack, + summary: replacement.message + }; + } +} +module.exports = serializeError; diff --git a/lib/worker/subprocess.js b/lib/worker/subprocess.js index 8b2dabfdb..3ddcfa7e0 100644 --- a/lib/worker/subprocess.js +++ b/lib/worker/subprocess.js @@ -1,6 +1,5 @@ 'use strict'; const currentlyUnhandled = require('currently-unhandled')(); -const isObj = require('is-obj'); /* eslint-disable unicorn/no-process-exit */ /* eslint-disable import/no-unassigned-import */ @@ -85,7 +84,7 @@ runner.on('start', started => { runner.on('hook-failed', result => { ipc.send('test', { duration: result.duration, - error: serializeError(result.error), + error: serializeError('Hook failure', true, result.error), failing: result.metadata.failing, logs: result.logs, skip: result.metadata.skip, @@ -98,7 +97,7 @@ runner.on('hook-failed', result => { runner.on('test', result => { ipc.send('test', { duration: result.duration, - error: result.passed ? null : serializeError(result.error), + error: result.passed ? null : serializeError('Test failure', true, result.error), failing: result.metadata.failing, logs: result.logs, skip: result.metadata.skip, @@ -137,25 +136,11 @@ function handleUncaughtException(exception) { return; } - let serialized; - try { - serialized = serializeError(exception); - } catch (ignore) { // eslint-disable-line unicorn/catch-error-name - // Avoid using serializeError - const err = new Error('Failed to serialize uncaught exception'); - serialized = { - avaAssertionError: false, - name: err.name, - message: err.message, - stack: err.stack - }; - } - // Ensure the IPC channel is referenced. The uncaught exception will kick off // the teardown sequence, for which the messages must be received. ipc.forceRefChannel(); - ipc.send('uncaughtException', {exception: serialized}); + ipc.send('uncaughtException', {exception: serializeError('Uncaught exception', true, exception)}); } const attributedRejections = new Set(); @@ -178,21 +163,11 @@ process.on('ava-teardown', () => { // Reference the IPC channel so the teardown sequence can be completed. ipc.forceRefChannel(); - let rejections = currentlyUnhandled() - .filter(rejection => !attributedRejections.has(rejection.promise)); - + const rejections = currentlyUnhandled().filter(rejection => !attributedRejections.has(rejection.promise)); if (rejections.length > 0) { - rejections = rejections.map(rejection => { - let reason = rejection.reason; - if (!isObj(reason) || typeof reason.message !== 'string') { - reason = { - message: String(reason) - }; - } - return serializeError(reason); + ipc.send('unhandledRejections', { + rejections: rejections.map(rejection => serializeError('Unhandled rejection', true, rejection.reason)) }); - - ipc.send('unhandledRejections', {rejections}); } // Include dependencies in the final teardown message. This ensures the full diff --git a/package.json b/package.json index 4bf393487..52edb3838 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,6 @@ "is-ci": "^1.1.0", "is-error": "^2.2.1", "is-generator-fn": "^1.0.0", - "is-obj": "^1.0.0", "is-observable": "^1.1.0", "is-promise": "^2.1.0", "last-line-stream": "^1.0.0", diff --git a/test/reporters/mini.regular.log b/test/reporters/mini.regular.log index 183a9de3f..dded51d50 100644 --- a/test/reporters/mini.regular.log +++ b/test/reporters/mini.regular.log @@ -278,7 +278,7 @@ stderr t (unhandled-rejection.js:4:17) Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js - Threw non-error: {"message":"null"} + null Uncaught exception in test/fixture/report/regular/uncaught-exception.js Error: Can't catch me Timeout.setTimeout (uncaught-exception.js:5:9) diff --git a/test/reporters/tap.regular.log b/test/reporters/tap.regular.log index f433f913b..0f2d3aaae 100644 --- a/test/reporters/tap.regular.log +++ b/test/reporters/tap.regular.log @@ -23,10 +23,11 @@ not ok 5 - Can't catch me at: 't (unhandled-rejection.js:4:17)' ... ---tty-stream-chunk-separator -# null -not ok 6 - null +# undefined +not ok 6 - undefined --- - {} + message: Non-error object + formatted: 'null' ... ---tty-stream-chunk-separator # uncaught-exception › passes diff --git a/test/reporters/verbose.regular.log b/test/reporters/verbose.regular.log index a1ede0001..f97b2f1b5 100644 --- a/test/reporters/verbose.regular.log +++ b/test/reporters/verbose.regular.log @@ -21,7 +21,7 @@ ---tty-stream-chunk-separator Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js - Threw non-error: {"message":"null"} + null ---tty-stream-chunk-separator diff --git a/test/serialize-error.js b/test/serialize-error.js index ec2777617..12c2cda3e 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -11,7 +11,9 @@ const uniqueTempDir = require('unique-temp-dir'); const test = require('tap').test; const avaAssert = require('../lib/assert'); const beautifyStack = require('../lib/beautify-stack'); -const serialize = require('../lib/serialize-error'); +const serializeError = require('../lib/serialize-error'); + +const serialize = err => serializeError('Test', true, err); // Needed to test stack traces from source map fixtures. sourceMapSupport.install({environment: 'node'}); @@ -20,8 +22,9 @@ test('serialize standard props', t => { const err = new Error('Hello'); const serializedErr = serialize(err); - t.is(Object.keys(serializedErr).length, 7); + t.is(Object.keys(serializedErr).length, 8); t.is(serializedErr.avaAssertionError, false); + t.is(serializedErr.nonErrorObject, false); t.deepEqual(serializedErr.object, {}); t.is(serializedErr.name, 'Error'); t.is(serializedErr.stack, beautifyStack(err.stack)); @@ -164,10 +167,10 @@ test('remove non-string error properties', t => { }); test('creates multiline summaries for syntax errors', t => { - const err = { - name: 'SyntaxError', - stack: 'Hello\nThere\nSyntaxError here\nIgnore me' - }; + const err = new SyntaxError(); + Object.defineProperty(err, 'stack', { + value: 'Hello\nThere\nSyntaxError here\nIgnore me' + }); const serializedErr = serialize(err); t.is(serializedErr.name, 'SyntaxError'); t.is(serializedErr.summary, 'Hello\nThere\nSyntaxError here'); From 4a9c9d7c3724f226a83761ece66c059e73360d8e Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 18 Feb 2018 19:05:30 +0000 Subject: [PATCH 12/18] Refactor IPC, status and reporters Simplify how test results flow from the worker processes to the API instance to the reporters by emitting "state changes" rather than specific events. Workers report (most) state changes as they happen, they're collated for each run, and reporters receive each state change and can write to the console in turn. Stats are computed centrally in `lib/run-status.js` and are emitted whenever they change, thus simplifying reporters. Because workers report state changes as they happen we can greatly simplify their coordination with the main process. We just need to make sure the last state change has been received before exiting. If the main process decides to exit a worker it can simplify do so, it'll already have received results from completed tests. Error conditions are either explicit or can be inferred from the computed stats. The API now emits its plan of the tests it'll run, which can then be sent to the reporters. This leads to better separation of concerns between the reporters, the run status, the API and the watch mode implementation. Uncaught exceptions and unhandled rejections are now shown with a code excerpt if possible. There are other minor variations in the reporter output, but I've tried to keep changes to a minimum. All reporters now write to `process.stdout`. The `stdout` and `stderr` output from workers is written to `process.stderr`. AVA will insert line breaks in `process.stdout` after writing a chunk to `process.stderr` that does not end in a line break. In watch mode, files being rerun are now excluded from the previous failure count. `profile.js` is now wired up to use the verbose reporter. It's still a terrible hack though. Porting the tests surfaced some integration-type tests that no longer made sense. They've been removed. Hopefully I haven't gone overboard with that. Ideally we'd rewrite the integration tests but that's a lot of work. Code coverage still looks good though. --- api.js | 152 +++-- lib/ava-error.js | 10 - lib/cli.js | 57 +- lib/emittery.js | 6 + lib/fork.js | 152 ++--- lib/logger.js | 92 --- lib/prefix-title.js | 21 - lib/{ => reporters}/colors.js | 2 +- lib/reporters/mini.js | 664 +++++++++++++-------- lib/reporters/prefix-title.js | 21 + lib/reporters/tap.js | 173 ++++-- lib/reporters/verbose.js | 459 ++++++++++----- lib/reporters/while-corked.js | 12 + lib/run-status.js | 246 ++++---- lib/runner.js | 178 +++--- lib/watcher.js | 115 ++-- lib/worker/dependency-tracker.js | 17 +- lib/worker/ipc.js | 54 +- lib/worker/subprocess.js | 202 ++----- package-lock.json | 57 +- package.json | 6 +- profile.js | 94 ++- test/api.js | 915 ++++++++++------------------- test/cli.js | 263 ++++----- test/fork.js | 153 ----- test/helper/fix-reporter-env.js | 4 + test/helper/report.js | 49 +- test/helper/tty-stream.js | 31 + test/hooks.js | 113 ++-- test/logger.js | 167 ------ test/reporters/mini.failfast.log | 16 +- test/reporters/mini.failfast2.log | 16 +- test/reporters/mini.js | 16 +- test/reporters/mini.only.log | 19 +- test/reporters/mini.regular.log | 184 +++--- test/reporters/mini.watch.log | 57 +- test/reporters/prefix-title.js | 50 ++ test/reporters/tap.failfast2.log | 5 +- test/reporters/tap.js | 5 +- test/reporters/tap.regular.log | 60 +- test/reporters/verbose.js | 6 +- test/reporters/verbose.regular.log | 37 +- test/reporters/verbose.watch.log | 2 + test/run-status.js | 90 --- test/runner.js | 239 ++++---- test/watcher.js | 529 +++++++++++------ 46 files changed, 2791 insertions(+), 3025 deletions(-) delete mode 100644 lib/ava-error.js create mode 100644 lib/emittery.js delete mode 100644 lib/logger.js delete mode 100644 lib/prefix-title.js rename lib/{ => reporters}/colors.js (87%) create mode 100644 lib/reporters/prefix-title.js create mode 100644 lib/reporters/while-corked.js delete mode 100644 test/fork.js delete mode 100644 test/logger.js create mode 100644 test/reporters/prefix-title.js delete mode 100644 test/run-status.js diff --git a/api.js b/api.js index 84dafc36a..acc05d2cb 100644 --- a/api.js +++ b/api.js @@ -1,5 +1,4 @@ 'use strict'; -const EventEmitter = require('events'); const path = require('path'); const fs = require('fs'); const os = require('os'); @@ -14,10 +13,11 @@ const arrify = require('arrify'); const ms = require('ms'); const babelConfigHelper = require('./lib/babel-config'); const CachingPrecompiler = require('./lib/caching-precompiler'); +const Emittery = require('./lib/emittery'); const RunStatus = require('./lib/run-status'); -const AvaError = require('./lib/ava-error'); const AvaFiles = require('./lib/ava-files'); const fork = require('./lib/fork'); +const serializeError = require('./lib/serialize-error'); function resolveModules(modules) { return arrify(modules).map(name => { @@ -31,7 +31,7 @@ function resolveModules(modules) { }); } -class Api extends EventEmitter { +class Api extends Emittery { constructor(options) { super(); @@ -46,19 +46,12 @@ class Api extends EventEmitter { // Each run will have its own status. It can only be created when test files // have been found. let runStatus; - // Irrespectively, perform some setup now, before finding test files. - const handleError = exception => { - runStatus.handleExceptions({ - exception, - file: exception.file ? path.relative(process.cwd(), exception.file) : undefined - }); - }; // Track active forks and manage timeouts. const failFast = apiOptions.failFast === true; let bailed = false; - const pendingForks = new Set(); + const pendingWorkers = new Set(); let restartTimer; if (apiOptions.timeout) { const timeout = ms(apiOptions.timeout); @@ -70,11 +63,11 @@ class Api extends EventEmitter { bailed = true; } - for (const fork of pendingForks) { - fork.exit(); + for (const worker of pendingWorkers) { + worker.exit(); } - handleError(new AvaError(`Exited because no new tests completed within the last ${timeout}ms of inactivity`)); + runStatus.emitStateChange({type: 'timeout', period: timeout}); }, timeout); } else { restartTimer = Object.assign(() => {}, {cancel() {}}); @@ -83,39 +76,45 @@ class Api extends EventEmitter { // Find all test files. return new AvaFiles({cwd: apiOptions.resolveTestsFrom, files}).findTestFiles() .then(files => { - runStatus = new RunStatus({ - runOnlyExclusive: runtimeOptions.runOnlyExclusive, - prefixTitles: apiOptions.explicitTitles || files.length > 1, - base: path.relative(process.cwd(), commonPathPrefix(files)) + path.sep, - failFast, - fileCount: files.length, - updateSnapshots: runtimeOptions.updateSnapshots + runStatus = new RunStatus(files.length); + + const emittedRun = this.emit('run', { + clearLogOnNextRun: runtimeOptions.clearLogOnNextRun === true, + failFastEnabled: failFast, + filePathPrefix: commonPathPrefix(files), + files, + matching: apiOptions.match.length > 0, + previousFailures: runtimeOptions.previousFailures || 0, + runOnlyExclusive: runtimeOptions.runOnlyExclusive === true, + runVector: runtimeOptions.runVector || 0, + status: runStatus }); - runStatus.on('test', restartTimer); - if (failFast) { - // Prevent new test files from running once a test has failed. - runStatus.on('test', test => { - if (test.error) { - bailed = true; - - for (const fork of pendingForks) { - fork.notifyOfPeerFailure(); - } - } + // Bail out early if no files were found. + if (files.length === 0) { + return emittedRun.then(() => { + return runStatus; }); } - this.emit('test-run', runStatus, files); + runStatus.on('stateChange', record => { + // Restart the timer whenever there is activity. + restartTimer(); - // Bail out early if no files were found. - if (files.length === 0) { - handleError(new AvaError('Couldn\'t find any files to test')); - return runStatus; - } + if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) { + // Prevent new test files from running once a test has failed. + bailed = true; + + // Try to stop currently scheduled tests. + for (const worker of pendingWorkers) { + worker.notifyOfPeerFailure(); + } + } + }); // Set up a fresh precompiler for each test run. - return this._setupPrecompiler() + return emittedRun + .then(() => this._setupPrecompiler()) .then(precompilation => { if (!precompilation) { return null; @@ -156,59 +155,44 @@ class Api extends EventEmitter { // No new files should be run once a test has timed out or failed, // and failFast is enabled. if (bailed) { - return null; + return; } - let forked; - return Bluebird.resolve( - this._computeForkExecArgv().then(execArgv => { - const options = Object.assign({}, apiOptions, { - // If we're looking for matches, run every single test process in exclusive-only mode - runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true - }); - if (precompilation) { - options.cacheDir = precompilation.cacheDir; - options.precompiled = precompilation.map; - } else { - options.precompiled = {}; - } - if (runtimeOptions.updateSnapshots) { - // Don't use in Object.assign() since it'll override options.updateSnapshots even when false. - options.updateSnapshots = true; - } - - forked = fork(file, options, execArgv); - pendingForks.add(forked); - runStatus.observeFork(forked); - restartTimer(); - return forked; - }).catch(err => { - // Prevent new test files from running. - if (failFast) { - bailed = true; - } - handleError(Object.assign(err, {file})); - return null; - }) - ).finally(() => { - pendingForks.delete(forked); + return this._computeForkExecArgv().then(execArgv => { + const options = Object.assign({}, apiOptions, { + // If we're looking for matches, run every single test process in exclusive-only mode + runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true + }); + if (precompilation) { + options.cacheDir = precompilation.cacheDir; + options.precompiled = precompilation.map; + } else { + options.precompiled = {}; + } + if (runtimeOptions.updateSnapshots) { + // Don't use in Object.assign() since it'll override options.updateSnapshots even when false. + options.updateSnapshots = true; + } + + const worker = fork(file, options, execArgv); + runStatus.observeWorker(worker, file); + + pendingWorkers.add(worker); + worker.promise.then(() => { // eslint-disable-line max-nested-callbacks + pendingWorkers.delete(worker); + }); + + restartTimer(); + + return worker.promise; }); }, {concurrency}); }) .catch(err => { - handleError(err); - return []; + runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, err)}); }) - .then(results => { + .then(() => { restartTimer.cancel(); - - // Filter out undefined results (e.g. for files that were skipped after a timeout) - results = results.filter(Boolean); - if (apiOptions.match.length > 0 && !runStatus.hasExclusive) { - handleError(new AvaError('Couldn\'t find any matching tests')); - } - - runStatus.processResults(results); return runStatus; }); }); diff --git a/lib/ava-error.js b/lib/ava-error.js deleted file mode 100644 index 05df6b349..000000000 --- a/lib/ava-error.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -class AvaError extends Error { - constructor(message) { - super(message); - this.name = 'AvaError'; - } -} - -module.exports = AvaError; diff --git a/lib/cli.js b/lib/cli.js index c4e18428f..bfb5fad74 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -16,7 +16,7 @@ function exit(message) { process.exit(1); // eslint-disable-line unicorn/no-process-exit } -exports.run = () => { +exports.run = () => { // eslint-disable-line complexity const conf = pkgConf.sync('ava'); const filepath = pkgConf.filepath(conf); @@ -132,7 +132,6 @@ exports.run = () => { const VerboseReporter = require('./reporters/verbose'); const MiniReporter = require('./reporters/mini'); const TapReporter = require('./reporters/tap'); - const Logger = require('./logger'); const Watcher = require('./watcher'); const babelConfigHelper = require('./babel-config'); @@ -146,6 +145,7 @@ exports.run = () => { // Copy resultant cli.flags into conf for use with Api and elsewhere Object.assign(conf, cli.flags); + const match = arrify(conf.match); const api = new Api({ failFast: conf.failFast, failWithoutAssertions: conf.failWithoutAssertions !== false, @@ -153,8 +153,7 @@ exports.run = () => { require: arrify(conf.require), cacheEnabled: conf.cache, compileEnhancements: conf.compileEnhancements !== false, - explicitTitles: conf.watch, - match: arrify(conf.match), + match, babelConfig, resolveTestsFrom: cli.input.length === 0 ? projectDir : process.cwd(), projectDir, @@ -167,46 +166,36 @@ exports.run = () => { }); let reporter; - if (conf.tap && !conf.watch) { - reporter = new TapReporter(); + reporter = new TapReporter({ + reportStream: process.stdout, + stdStream: process.stderr + }); } else if (conf.verbose || isCi) { - reporter = new VerboseReporter({color: conf.color, watching: conf.watch}); + reporter = new VerboseReporter({ + reportStream: process.stdout, + stdStream: process.stderr, + watching: conf.watch + }); } else { - reporter = new MiniReporter({color: conf.color, watching: conf.watch}); + reporter = new MiniReporter({ + reportStream: process.stdout, + stdStream: process.stderr, + watching: conf.watch + }); } - reporter.api = api; - const logger = new Logger(reporter); - - logger.start(); - - api.on('test-run', runStatus => { - reporter.api = runStatus; - runStatus.on('test', logger.test); - runStatus.on('error', logger.unhandledError); - - runStatus.on('stdout', logger.stdout); - runStatus.on('stderr', logger.stderr); - }); + api.on('run', plan => reporter.startRun(plan)); const files = cli.input.length ? cli.input : arrify(conf.files); if (conf.watch) { - const watcher = new Watcher(logger, api, files, arrify(conf.sources)); + const watcher = new Watcher(reporter, api, files, arrify(conf.sources)); watcher.observeStdin(process.stdin); } else { - api.run(files) - .then(runStatus => { - logger.finish(runStatus); - logger.exit(runStatus.failCount > 0 || runStatus.rejectionCount > 0 || runStatus.exceptionCount > 0 ? 1 : 0); - }) - .catch(err => { - // Don't swallow exceptions. Note that any expected error should already - // have been logged. - setImmediate(() => { - throw err; - }); - }); + api.run(files).then(runStatus => { + process.exitCode = runStatus.suggestExitCode({matching: match.length > 0}); + reporter.endRun(); + }); } }; diff --git a/lib/emittery.js b/lib/emittery.js new file mode 100644 index 000000000..57405d21d --- /dev/null +++ b/lib/emittery.js @@ -0,0 +1,6 @@ +'use strict'; +try { + module.exports = require('emittery'); +} catch (err) { + module.exports = require('emittery/legacy'); +} diff --git a/lib/fork.js b/lib/fork.js index d224b169e..8795c7538 100644 --- a/lib/fork.js +++ b/lib/fork.js @@ -3,8 +3,7 @@ const childProcess = require('child_process'); const path = require('path'); const fs = require('fs'); const Promise = require('bluebird'); -const debug = require('debug')('ava'); -const AvaError = require('./ava-error'); +const Emittery = require('./emittery'); if (fs.realpathSync(__filename) !== __filename) { console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.'); @@ -27,131 +26,100 @@ env.AVA_PATH = path.resolve(__dirname, '..'); const workerPath = require.resolve('./worker/subprocess'); module.exports = (file, opts, execArgv) => { + let finished = false; + + const emitter = new Emittery(); + const emitStateChange = evt => { + if (!finished) { + emitter.emit('stateChange', Object.assign(evt, {testFile: file})); + } + }; + opts = Object.assign({ file, baseDir: process.cwd(), tty: process.stdout.isTTY ? { - columns: process.stdout.columns, + columns: process.stdout.columns || 80, rows: process.stdout.rows } : false }, opts); const args = [JSON.stringify(opts), opts.color ? '--color' : '--no-color'].concat(opts.workerArgv); - const ps = childProcess.fork(workerPath, args, { + const subprocess = childProcess.fork(workerPath, args, { cwd: opts.projectDir, silent: true, env, execArgv: execArgv || process.execArgv }); - const relFile = path.relative('.', file); - - let exiting = false; - const send = (name, data) => { - if (!exiting) { - // This seems to trigger a Node bug which kills the AVA master process, at - // least while running AVA's tests. See - // for more details. - ps.send({ - name: `ava-${name}`, - data, - ava: true - }); + subprocess.stdout.on('data', chunk => { + emitStateChange({type: 'worker-stdout', chunk}); + }); + + subprocess.stderr.on('data', chunk => { + emitStateChange({type: 'worker-stderr', chunk}); + }); + + let forcedExit = false; + const send = evt => { + if (subprocess.connected && !finished && !forcedExit) { + subprocess.send({ava: evt}); } }; - let loadedFile = false; - const testResults = []; - let results; + const promise = new Promise(resolve => { + const finish = () => { + finished = true; + resolve(); + }; - const promise = new Promise((resolve, reject) => { - ps.on('error', reject); - - // Emit `test` and `stats` events - ps.on('message', event => { - if (!event.ava) { + subprocess.on('message', message => { + if (!message.ava) { return; } - event.name = event.name.replace(/^ava-/, ''); - event.data.file = relFile; - - debug('ipc %s:\n%o', event.name, event.data); - - ps.emit(event.name, event.data); - }); - - ps.on('test', props => { - testResults.push(props); + if (message.ava.type === 'ping') { + send({type: 'pong'}); + } else { + emitStateChange(message.ava); + } }); - ps.on('results', data => { - results = data; - data.tests = testResults; - send('teardown'); + subprocess.on('error', err => { + emitStateChange({type: 'worker-failed', err}); + finish(); }); - ps.on('exit', (code, signal) => { - if (code > 0) { - return reject(new AvaError(`${relFile} exited with a non-zero exit code: ${code}`)); - } - - if (code === null && signal) { - return reject(new AvaError(`${relFile} exited due to ${signal}`)); - } - - if (results) { - resolve(results); - } else if (loadedFile) { - reject(new AvaError(`No tests found in ${relFile}`)); + subprocess.on('exit', (code, signal) => { + if (forcedExit) { + emitStateChange({type: 'worker-finished', forcedExit}); + } else if (code > 0) { + emitStateChange({type: 'worker-failed', nonZeroExitCode: code}); + } else if (code === null && signal) { + emitStateChange({type: 'worker-failed', signal}); } else { - reject(new AvaError(`Test results were not received from ${relFile}`)); + emitStateChange({type: 'worker-finished', forcedExit}); } - }); - ps.on('loaded-file', data => { - loadedFile = true; - - if (!data.avaRequired) { - send('teardown'); - reject(new AvaError(`No tests found in ${relFile}, make sure to import "ava" at the top of your test file`)); - } + finish(); }); }); - // Teardown finished, now exit - ps.on('teardown', () => { - send('exit'); - exiting = true; - }); - - // Uncaught exception in fork, need to exit - ps.on('uncaughtException', () => { - send('teardown'); - }); - - ps.stdout.on('data', data => { - ps.emit('stdout', data); - }); - - ps.stderr.on('data', data => { - ps.emit('stderr', data); - }); + return { + exit() { + forcedExit = true; + subprocess.kill(); + }, - promise.on = function () { - ps.on.apply(ps, arguments); - return promise; - }; + notifyOfPeerFailure() { + send({type: 'peer-failed'}); + }, - promise.exit = () => { - send('init-exit'); - return promise; - }; + onStateChange(listener) { + return emitter.on('stateChange', listener); + }, - promise.notifyOfPeerFailure = () => { - send('peer-failed'); + promise }; - - return promise; }; diff --git a/lib/logger.js b/lib/logger.js deleted file mode 100644 index 9256f4979..000000000 --- a/lib/logger.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; -const autoBind = require('auto-bind'); - -class Logger { - constructor(reporter) { - this.reporter = reporter; - autoBind(this); - } - - start(runStatus) { - if (!this.reporter.start) { - return; - } - - this.write(this.reporter.start(runStatus), runStatus); - } - - reset(runStatus) { - if (!this.reporter.reset) { - return; - } - - this.write(this.reporter.reset(runStatus), runStatus); - } - - test(test, runStatus) { - this.write(this.reporter.test(test), runStatus); - } - - unhandledError(err, runStatus) { - if (!this.reporter.unhandledError) { - return; - } - - this.write(this.reporter.unhandledError(err, runStatus), runStatus); - } - - finish(runStatus) { - if (!this.reporter.finish) { - return; - } - - this.write(this.reporter.finish(runStatus), runStatus); - } - - section() { - if (!this.reporter.section) { - return; - } - - this.write(this.reporter.section()); - } - - clear() { - if (!this.reporter.clear) { - return false; - } - - this.write(this.reporter.clear()); - return true; - } - - write(str, runStatus) { - if (typeof str === 'undefined') { - return; - } - - this.reporter.write(str, runStatus); - } - - stdout(data, runStatus) { - if (!this.reporter.stdout) { - return; - } - - this.reporter.stdout(data, runStatus); - } - - stderr(data, runStatus) { - if (!this.reporter.stderr) { - return; - } - - this.reporter.stderr(data, runStatus); - } - - exit(code) { - process.exit(code); // eslint-disable-line unicorn/no-process-exit - } -} - -module.exports = Logger; diff --git a/lib/prefix-title.js b/lib/prefix-title.js deleted file mode 100644 index a1c7b4f3b..000000000 --- a/lib/prefix-title.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -const path = require('path'); - -module.exports = (file, base, separator) => { - let prefix = file - // Only replace this.base if it is found at the start of the path - .replace(base, (match, offset) => offset === 0 ? '' : match) - .replace(/\.spec/, '') - .replace(/\.test/, '') - .replace(/test-/g, '') - .replace(/\.js$/, '') - .split(path.sep) - .filter(p => p !== '__tests__') - .join(separator); - - if (prefix.length > 0) { - prefix += separator; - } - - return prefix; -}; diff --git a/lib/colors.js b/lib/reporters/colors.js similarity index 87% rename from lib/colors.js rename to lib/reporters/colors.js index 79eb145d1..adfa402c5 100644 --- a/lib/colors.js +++ b/lib/reporters/colors.js @@ -1,5 +1,5 @@ 'use strict'; -const chalk = require('./chalk').get(); +const chalk = require('../chalk').get(); module.exports = { log: chalk.gray, diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index b00c945ea..7e8a4d853 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -1,346 +1,494 @@ 'use strict'; -const StringDecoder = require('string_decoder').StringDecoder; +const os = require('os'); +const path = require('path'); +const stream = require('stream'); + const cliCursor = require('cli-cursor'); -const lastLineTracker = require('last-line-stream/tracker'); -const plur = require('plur'); -const spinners = require('cli-spinners'); const figures = require('figures'); -const cliTruncate = require('cli-truncate'); -const cross = require('figures').cross; const indentString = require('indent-string'); -const ansiEscapes = require('ansi-escapes'); +const ora = require('ora'); +const plur = require('plur'); const trimOffNewlines = require('trim-off-newlines'); +const trimRight = require('trim-right'); + const chalk = require('../chalk').get(); const codeExcerpt = require('../code-excerpt'); -const colors = require('../colors'); +const colors = require('./colors'); const formatSerializedError = require('./format-serialized-error'); const improperUsageMessages = require('./improper-usage-messages'); +const prefixTitle = require('./prefix-title'); +const whileCorked = require('./while-corked'); -class MiniReporter { - constructor(options) { - this.options = Object.assign({}, options); - - chalk.enabled = this.options.color; - for (const key of Object.keys(colors)) { - colors[key].enabled = this.options.color; - } - - const spinnerDef = spinners[process.platform === 'win32' ? 'line' : 'dots']; - this.spinnerFrames = spinnerDef.frames.map(c => chalk.gray.dim(c)); - this.spinnerInterval = spinnerDef.interval; +class LineWriter extends stream.Writable { + constructor(dest, spinner) { + super(); - this.reset(); - this.stream = process.stderr; - this.stringDecoder = new StringDecoder(); + this.dest = dest; + this.columns = dest.columns || 80; + this.spinner = spinner; + this.lastSpinnerText = ''; } - start() { - this.interval = setInterval(() => { - this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length; - this.write(this.prefix()); - }, this.spinnerInterval); + _write(chunk, encoding, callback) { + // Discard the current spinner output. Any lines that were meant to be + // preserved should be rewritten. + this.spinner.clear(); - return this.prefix(''); + this._writeWithSpinner(chunk.toString('utf8')); + callback(); } - reset() { - this.clearInterval(); - this.passCount = 0; - this.knownFailureCount = 0; - this.failCount = 0; - this.skipCount = 0; - this.todoCount = 0; - this.rejectionCount = 0; - this.exceptionCount = 0; - this.currentStatus = ''; - this.currentTest = ''; - this.statusLineCount = 0; - this.spinnerIndex = 0; - this.lastLineTracker = lastLineTracker(); - } + _writev(pieces, callback) { + // Discard the current spinner output. Any lines that were meant to be + // preserved should be rewritten. + this.spinner.clear(); - spinnerChar() { - return this.spinnerFrames[this.spinnerIndex]; + const last = pieces.pop(); + for (const piece of pieces) { + this.dest.write(piece.chunk); + } + this._writeWithSpinner(last.chunk.toString('utf8')); + callback(); } - clearInterval() { - clearInterval(this.interval); - this.interval = null; + _writeWithSpinner(str) { + if (!this.spinner.id) { + this.dest.write(str); + return; + } + + this.lastSpinnerText = str; + // Ignore whitespace at the end of the chunk. We're continiously rewriting + // the last line through the spinner. Also be careful to remove the indent + // as the spinner adds its own. + this.spinner.text = trimRight(str).slice(2); + this.spinner.render(); } - test(test) { - if (test.todo) { - this.todoCount++; - } else if (test.skip) { - this.skipCount++; - } else if (test.error) { - this.failCount++; + writeLine(str) { + if (str) { + this.write(indentString(str, 2) + os.EOL); } else { - this.passCount++; - if (test.failing) { - this.knownFailureCount++; - } + this.write(os.EOL); } + } +} - if (test.todo || test.skip) { - return; - } +class MiniReporter { + constructor(options) { + this.reportStream = options.reportStream; + this.stdStream = options.stdStream; + this.watching = options.watching; + + this.spinner = ora({ + enabled: true, + color: options.spinner ? options.spinner.color : 'gray', + hideCursor: false, + spinner: options.spinner || (process.platform === 'win32' ? 'line' : 'dots'), + stream: options.reportStream + }); + this.lineWriter = new LineWriter(this.reportStream, this.spinner); + + this.consumeStateChange = whileCorked(this.reportStream, whileCorked(this.lineWriter, this.consumeStateChange)); + this.endRun = whileCorked(this.reportStream, whileCorked(this.lineWriter, this.endRun)); - return this.prefix(this._test(test)); + this.reset(); } - prefix(str) { - str = str || this.currentTest; - this.currentTest = str; + reset() { + if (this.removePreviousListener) { + this.removePreviousListener(); + } - // The space before the newline is required for proper formatting - // TODO(jamestalmage): Figure out why it's needed and document it here - return ` \n ${this.spinnerChar()} ${str}`; + this.failFastEnabled = false; + this.failures = []; + this.filesWithMissingAvaImports = new Set(); + this.internalErrors = []; + this.knownFailures = []; + this.matching = false; + this.prefixTitle = (testFile, title) => title; + this.previousFailures = 0; + this.removePreviousListener = null; + this.stats = null; + this.uncaughtExceptions = []; + this.unhandledRejections = []; } - _test(test) { - const SPINNER_WIDTH = 3; - const PADDING = 1; - let title = cliTruncate(test.title, this.stream.columns - SPINNER_WIDTH - PADDING); + startRun(plan) { + this.reset(); + + this.failFastEnabled = plan.failFastEnabled; + this.matching = plan.matching; + this.previousFailures = plan.previousFailures; - if (test.error || test.failing) { - title = colors.error(test.title); + if (this.watching || plan.files.length > 1) { + this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title); } - return title + '\n' + this.reportCounts(); - } + this.removePreviousListener = plan.status.on('stateChange', evt => this.consumeStateChange(evt)); - unhandledError(err) { - if (err.type === 'exception') { - this.exceptionCount++; - } else { - this.rejectionCount++; + if (this.watching && plan.runVector > 1) { + this.reportStream.write(chalk.gray.dim('\u2500'.repeat(this.lineWriter.columns)) + os.EOL); } + + cliCursor.hide(this.reportStream); + this.lineWriter.writeLine(); + this.spinner.start(); } - reportCounts(time) { - const lines = [ - this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '', - this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '', - this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '', - this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '', - this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : '' - ].filter(Boolean); + consumeStateChange(evt) { // eslint-disable-line complexity + switch (evt.type) { + case 'declared-test': + // Ignore + break; + case 'hook-failed': + this.failures.push(evt); + this.writeTestSummary(evt); + break; + case 'internal-error': + this.internalErrors.push(evt); + if (evt.testFile) { + this.writeWithCounts(colors.error(`${figures.cross} Internal error when running ${path.relative('.', evt.testFile)}`)); + } else { + this.writeWithCounts(colors.error(`${figures.cross} Internal error`)); + } + break; + case 'missing-ava-import': + this.filesWithMissingAvaImports.add(evt.testFile); + this.writeWithCounts(colors.error(`${figures.cross} No tests found in ${path.relative('.', evt.testFile)}, make sure to import "ava" at the top of your test file`)); + break; + case 'selected-test': + // Ignore + break; + case 'stats': + this.stats = evt.stats; + break; + case 'test-failed': + this.failures.push(evt); + this.writeTestSummary(evt); + break; + case 'test-passed': + if (evt.knownFailing) { + this.knownFailures.push(evt); + } + this.writeTestSummary(evt); + break; + case 'timeout': + this.writeWithCounts(colors.error(`${figures.cross} Exited because no new tests completed within the last ${evt.period}ms of inactivity`)); + break; + case 'uncaught-exception': + this.uncaughtExceptions.push(evt); + break; + case 'unhandled-rejection': + this.unhandledRejections.push(evt); + break; + case 'worker-failed': + // Ignore + break; + case 'worker-finished': + // Ignore + break; + case 'worker-stderr': + case 'worker-stdout': + // Forcibly clear the spinner, writing the chunk corrupts the TTY. + this.spinner.clear(); + + this.stdStream.write(evt.chunk); + // If the chunk does not end with a linebreak, *forcibly* write one to + // ensure it remains visible in the TTY. + // Tests cannot assume their standard output is not interrupted. Indeed + // we multiplex stdout and stderr into a single stream. However as + // long as stdStream is different from reportStream users can read + // their original output by redirecting the streams. + if (evt.chunk[evt.chunk.length - 1] !== 0x0A) { + // Use write() rather than writeLine() so the (presumably corked) + // line writer will actually write the empty line before re-rendering + // the last spinner text below. + this.lineWriter.write(os.EOL); + } - if (time && lines.length > 0) { - lines[0] += ' ' + time; + this.lineWriter.write(this.lineWriter.lastSpinnerText); + break; + default: + break; } - - return lines.join(''); } - finish(runStatus) { - this.clearInterval(); - let time; + writeWithCounts(str) { + if (!this.stats) { + return this.lineWriter.writeLine(str); + } - if (this.options.watching) { - time = chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + str = str || ''; + if (str !== '') { + str += os.EOL; } - let status = this.reportCounts(time) + '\n'; + let firstLinePostfix = this.watching ? + ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') : + ''; - if (this.rejectionCount > 0) { - status += ' ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount)) + '\n'; + if (this.stats.passedTests > 0) { + str += os.EOL + colors.pass(`${this.stats.passedTests} passed`) + firstLinePostfix; + firstLinePostfix = ''; } - - if (this.exceptionCount > 0) { - status += ' ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount)) + '\n'; + if (this.stats.passedKnownFailingTests > 0) { + str += os.EOL + colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`); } - - if (runStatus.previousFailCount > 0) { - status += ' ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun') + '\n'; + if (this.stats.failedHooks > 0) { + str += os.EOL + colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix; + firstLinePostfix = ''; + } + if (this.stats.failedTests > 0) { + str += os.EOL + colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix; + firstLinePostfix = ''; + } + if (this.stats.skippedTests > 0) { + str += os.EOL + colors.skip(`${this.stats.skippedTests} skipped`); + } + if (this.stats.todoTests > 0) { + str += os.EOL + colors.todo(`${this.stats.todoTests} todo`); } - if (this.knownFailureCount > 0) { - for (const test of runStatus.knownFailures) { - const title = test.title; - status += '\n ' + colors.title(title) + '\n'; - // TODO: Output description with link - // status += colors.stack(description); + this.lineWriter.writeLine(str); + } + + writeErr(evt) { + if (evt.err.source) { + this.lineWriter.writeLine(colors.errorSource(`${evt.err.source.file}:${evt.err.source.line}`)); + const excerpt = codeExcerpt(evt.err.source, {maxWidth: this.lineWriter.columns - 2}); + if (excerpt) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(excerpt); } } - status += '\n'; - if (this.failCount > 0) { - runStatus.errors.forEach(test => { - if (!test.error) { - return; - } + if (evt.err.avaAssertionError) { + const result = formatSerializedError(evt.err); + if (result.printMessage) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(evt.err.message); + } - status += ' ' + colors.title(test.title) + '\n'; + if (result.formatted) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(result.formatted); + } - if (test.logs) { - test.logs.forEach(log => { - const logLines = indentString(colors.log(log), 6); - const logLinesWithLeadingFigure = logLines.replace( - /^ {6}/, - ` ${colors.information(figures.info)} ` - ); + const message = improperUsageMessages.forError(evt.err); + if (message) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(message); + } + } else if (evt.err.nonErrorObject) { + this.lineWriter.writeLine(trimOffNewlines(evt.err.formatted)); + } else { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(evt.err.summary); + } - status += logLinesWithLeadingFigure + '\n'; - }); + if (evt.err.stack) { + const stack = evt.err.stack; + if (stack.includes(os.EOL)) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(colors.errorStack(stack)); + } + } + } - status += '\n'; - } + writeLogs(evt) { + if (evt.logs) { + for (const log of evt.logs) { + const logLines = indentString(colors.log(log), 4); + const logLinesWithLeadingFigure = logLines.replace( + /^ {4}/, + ` ${colors.information(figures.info)} ` + ); + this.lineWriter.writeLine(logLinesWithLeadingFigure); + } + } + } - if (test.error.source) { - status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; + writeTestSummary(evt) { + if (evt.type === 'hook-failed' || evt.type === 'test-failed') { + this.writeWithCounts(`${this.prefixTitle(evt.testFile, evt.title)}`); + } else if (evt.knownFailing) { + this.writeWithCounts(`${colors.error(this.prefixTitle(evt.testFile, evt.title))}`); + } else { + this.writeWithCounts(`${this.prefixTitle(evt.testFile, evt.title)}`); + } + } - const excerpt = codeExcerpt(test.error.source, {maxWidth: this.stream.columns}); - if (excerpt) { - status += '\n' + indentString(excerpt, 2) + '\n'; - } - } + writeFailure(evt) { + this.lineWriter.writeLine(`${colors.title(this.prefixTitle(evt.testFile, evt.title))}`); + this.writeLogs(evt); + this.lineWriter.writeLine(); + this.writeErr(evt); + } - if (test.error.avaAssertionError) { - 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'; - } + endRun() { // eslint-disable-line complexity + this.spinner.stop(); + cliCursor.show(this.reportStream); - if (test.error.stack) { - const stack = test.error.stack; - if (stack.includes('\n')) { - status += '\n' + indentString(colors.errorStack(stack), 2) + '\n'; - } - } + if (!this.stats) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn't find any files to test`)); + this.lineWriter.writeLine(); + return; + } - status += '\n\n\n'; - }); + if (this.matching && this.stats.selectedTests === 0) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn't find any matching tests`)); + this.lineWriter.writeLine(); + return; } - if (this.rejectionCount > 0 || this.exceptionCount > 0) { - // TODO(sindresorhus): Figure out why this causes a test failure when switched to a for-of loop - runStatus.errors.forEach(err => { - if (err.title) { - return; - } + this.lineWriter.writeLine(); - if (err.type === 'exception' && err.name === 'AvaError') { - status += ' ' + colors.error(cross + ' ' + err.message) + '\n\n'; - } else { - const title = err.type === 'rejection' ? 'Unhandled rejection' : 'Uncaught exception'; - status += ' ' + colors.title(`${title} in ${err.file}`) + '\n'; - - if (err.name) { - status += indentString(colors.stack(err.summary), 2) + '\n'; - status += indentString(colors.errorStack(err.stack), 2) + '\n\n'; - } else { - status += indentString(trimOffNewlines(err.formatted), 2) + '\n'; - } - } - }); - } + let firstLinePostfix = this.watching ? + ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') : + ''; - if (runStatus.failFastEnabled === true && runStatus.failCount > 0 && (runStatus.remainingCount > 0 || runStatus.fileCount > runStatus.observationCount)) { - let remaining = ''; - if (runStatus.remainingCount > 0) { - remaining += `At least ${runStatus.remainingCount} ${plur('test was', 'tests were', runStatus.remainingCount)} skipped`; - if (runStatus.fileCount > runStatus.observationCount) { - remaining += ', as well as '; - } - } - if (runStatus.fileCount > runStatus.observationCount) { - const skippedFileCount = runStatus.fileCount - runStatus.observationCount; - remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`; - if (runStatus.remainingCount === 0) { - remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`; - } - } - status += ' ' + colors.information('`--fail-fast` is on. ' + remaining + '.') + '\n\n'; + if (this.stats.failedHooks > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.failedTests > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.failedHooks === 0 && this.stats.failedTests === 0 && this.stats.passedTests > 0) { + this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.passedKnownFailingTests > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`)); + } + if (this.stats.skippedTests > 0) { + this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`)); + } + if (this.stats.todoTests > 0) { + this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`)); + } + if (this.stats.unhandledRejections > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`)); + } + if (this.stats.uncaughtExceptions > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`)); + } + if (this.previousFailures > 0) { + this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`)); } - if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) { - status += ' ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run'); + if (this.stats.passedKnownFailingTests > 0) { + this.lineWriter.writeLine(); + for (const evt of this.knownFailures) { + this.lineWriter.writeLine(colors.error(this.prefixTitle(evt.testFile, evt.title))); + } } - return '\n' + trimOffNewlines(status) + '\n'; - } + const shouldWriteFailFastDisclaimer = this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers); - section() { - return '\n' + chalk.gray.dim('\u2500'.repeat(this.stream.columns || 80)); - } + if (this.failures.length > 0) { + const writeTrailingLines = shouldWriteFailFastDisclaimer || this.internalErrors.length > 0 || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0; + this.lineWriter.writeLine(); - clear() { - return ''; - } + const last = this.failures[this.failures.length - 1]; + for (const evt of this.failures) { + this.writeFailure(evt); + if (evt !== last || writeTrailingLines) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + } + } + } - write(str) { - cliCursor.hide(); - this.currentStatus = str; - this._update(); - this.statusLineCount = this.currentStatus.split('\n').length; - } + if (this.internalErrors.length > 0) { + const writeLeadingLine = this.failures.length === 0; + const writeTrailingLines = shouldWriteFailFastDisclaimer || this.uncaughtExceptions.length > 0 || this.unhandledRejections.length > 0; - stdout(data) { - this._update(data); - } + if (writeLeadingLine) { + this.lineWriter.writeLine(); + } - stderr(data) { - this._update(data); - } + const last = this.internalErrors[this.internalErrors.length - 1]; + for (const evt of this.internalErrors) { + if (evt.testFile) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${path.relative('.', evt.testFile)}`)); + } else { + this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`)); + } + this.lineWriter.writeLine(colors.stack(evt.err.summary)); + this.lineWriter.writeLine(colors.errorStack(evt.err.stack)); + if (evt !== last || writeTrailingLines) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + } + } + } - _update(data) { - let str = ''; - let ct = this.statusLineCount; - const columns = this.stream.columns; - let lastLine = this.lastLineTracker.lastLine(); + if (this.uncaughtExceptions.length > 0) { + const writeLeadingLine = this.failures.length === 0 && this.internalErrors.length === 0; + const writeTrailingLines = shouldWriteFailFastDisclaimer || this.unhandledRejections.length > 0; - // Terminals automatically wrap text. We only need the last log line as seen on the screen. - lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns)); + if (writeLeadingLine) { + this.lineWriter.writeLine(); + } - // Don't delete the last log line if it's completely empty. - if (lastLine.length > 0) { - ct++; + const last = this.uncaughtExceptions[this.uncaughtExceptions.length - 1]; + for (const evt of this.uncaughtExceptions) { + this.lineWriter.writeLine(colors.title(`Uncaught exception in ${path.relative('.', evt.testFile)}`)); + this.lineWriter.writeLine(); + this.writeErr(evt); + if (evt !== last || writeTrailingLines) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + } + } } - // Erase the existing status message, plus the last log line. - str += ansiEscapes.eraseLines(ct); + if (this.unhandledRejections.length > 0) { + const writeLeadingLine = this.failures.length === 0 && this.internalErrors.length === 0 && this.uncaughtExceptions.length === 0; + const writeTrailingLines = shouldWriteFailFastDisclaimer; - // Rewrite the last log line. - str += lastLine; - - if (str.length > 0) { - this.stream.write(str); - } + if (writeLeadingLine) { + this.lineWriter.writeLine(); + } - if (data) { - // Send new log data to the terminal, and update the last line status. - this.lastLineTracker.update(this.stringDecoder.write(data)); - this.stream.write(data); + const last = this.unhandledRejections[this.unhandledRejections.length - 1]; + for (const evt of this.unhandledRejections) { + this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${path.relative('.', evt.testFile)}`)); + this.lineWriter.writeLine(); + this.writeErr(evt); + if (evt !== last || writeTrailingLines) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + } + } } - let currentStatus = this.currentStatus; - - if (currentStatus.length > 0) { - lastLine = this.lastLineTracker.lastLine(); - // We need a newline at the end of the last log line, before the status message. - // However, if the last log line is the exact width of the terminal a newline is implied, - // and adding a second will cause problems. - if (lastLine.length % columns) { - currentStatus = '\n' + currentStatus; + if (shouldWriteFailFastDisclaimer) { + let remaining = ''; + if (this.stats.remainingTests > 0) { + remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`; + if (this.stats.files > this.stats.finishedWorkers) { + remaining += ', as well as '; + } } - // Rewrite the status message. - this.stream.write(currentStatus); + if (this.stats.files > this.stats.finishedWorkers) { + const skippedFileCount = this.stats.files - this.stats.finishedWorkers; + remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`; + if (this.stats.remainingTests === 0) { + remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`; + } + } + this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`)); } + + this.lineWriter.writeLine(); } } - module.exports = MiniReporter; diff --git a/lib/reporters/prefix-title.js b/lib/reporters/prefix-title.js new file mode 100644 index 000000000..83636ef02 --- /dev/null +++ b/lib/reporters/prefix-title.js @@ -0,0 +1,21 @@ +'use strict'; +const path = require('path'); +const figures = require('figures'); +const chalk = require('../chalk').get(); + +const SEPERATOR = ' ' + chalk.gray.dim(figures.pointerSmall) + ' '; + +module.exports = (base, file, title) => { + const prefix = file + // Only replace base if it is found at the start of the path + .replace(base, (match, offset) => offset === 0 ? '' : match) + .replace(/\.spec/, '') + .replace(/\.test/, '') + .replace(/test-/g, '') + .replace(/\.js$/, '') + .split(path.sep) + .filter(p => p !== '__tests__') + .join(SEPERATOR); + + return prefix + SEPERATOR + title; +}; diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index dbfa7f1a2..e8594059b 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -1,13 +1,19 @@ 'use strict'; -const supertap = require('supertap'); +const os = require('os'); +const path = require('path'); + +const plur = require('plur'); const stripAnsi = require('strip-ansi'); +const supertap = require('supertap'); -function dumpError(error, includeMessage) { +const prefixTitle = require('./prefix-title'); + +function dumpError(error) { const obj = Object.assign({}, error.object); if (error.name) { obj.name = error.name; } - if (includeMessage && error.message) { + if (error.message) { obj.message = error.message; } @@ -39,65 +45,138 @@ function dumpError(error, includeMessage) { } class TapReporter { - constructor() { + constructor(options) { this.i = 0; - this.streams = { - stderr: process.stderr, - stdout: process.stdout - }; - } + this.stdStream = options.stdStream; + this.reportStream = options.reportStream; - start() { - return supertap.start(); + this.crashCount = 0; + this.filesWithMissingAvaImports = new Set(); + this.prefixTitle = (testFile, title) => title; + this.stats = null; } - test(test) { - return supertap.test(test.title, { - passed: !test.error, - index: ++this.i, - todo: test.todo, - skip: test.skip, - comment: test.logs, - error: test.error ? dumpError(test.error, true) : null - }); - } - - unhandledError(err) { - let error; - - // AvaErrors don't have stack traces - if (err.type !== 'exception' || err.name !== 'AvaError') { - error = dumpError(err, false); + startRun(plan) { + if (plan.files.length > 1) { + this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title); } - return supertap.test(err.message, { - passed: false, - index: ++this.i, - error - }); + plan.status.on('stateChange', evt => this.consumeStateChange(evt)); + + this.reportStream.write(supertap.start() + os.EOL); } - finish(runStatus) { - return supertap.finish({ - passed: runStatus.passCount, - failed: runStatus.failCount, - skipped: runStatus.skipCount, - crashed: runStatus.rejectionCount + runStatus.exceptionCount - }); + endRun() { + if (this.stats) { + this.reportStream.write(supertap.finish({ + crashed: this.crashCount, + failed: this.stats.failedHooks + this.stats.failedTests, + passed: this.stats.passedTests + this.stats.passedKnownFailingTests, + skipped: this.stats.skippedTests, + todo: this.stats.todoTests + }) + os.EOL); + } else { + this.reportStream.write(supertap.finish({ + crashed: this.crashCount, + failed: 0, + passed: 0, + skipped: 0, + todo: 0 + }) + os.EOL); + } } - write(str) { - this.streams.stdout.write(str + '\n'); + writeTest(evt, flags) { + this.reportStream.write(supertap.test(this.prefixTitle(evt.testFile, evt.title), { + comment: evt.logs, + error: evt.err ? dumpError(evt.err) : null, + index: ++this.i, + passed: flags.passed, + skip: flags.skip, + todo: flags.todo + }) + os.EOL); } - stdout(data) { - this.streams.stderr.write(data); + writeCrash(evt, title) { + this.crashCount++; + this.reportStream.write(supertap.test(title || evt.err.summary || evt.type, { + comment: evt.logs, + error: evt.err ? dumpError(evt.err) : null, + index: ++this.i, + passed: false, + skip: false, + todo: false + }) + os.EOL); } - stderr(data) { - this.stdout(data); + consumeStateChange(evt) { // eslint-disable-line complexity + const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null; + + switch (evt.type) { + case 'declared-test': + // Ignore + break; + case 'hook-failed': + this.writeTest(evt, {passed: false, todo: false, skip: false}); + break; + case 'internal-error': + this.writeCrash(evt); + break; + case 'missing-ava-import': + this.filesWithMissingAvaImports.add(evt.testFile); + this.writeCrash(evt, `No tests found in ${path.relative('.', evt.testFile)}, make sure to import "ava" at the top of your test file`); + break; + case 'selected-test': + if (evt.skip) { + this.writeTest(evt, {passed: true, todo: false, skip: true}); + } else if (evt.todo) { + this.writeTest(evt, {passed: false, todo: true, skip: false}); + } + break; + case 'stats': + this.stats = evt.stats; + break; + case 'test-failed': + this.writeTest(evt, {passed: false, todo: false, skip: false}); + break; + case 'test-passed': + this.writeTest(evt, {passed: true, todo: false, skip: false}); + break; + case 'timeout': + this.writeCrash(evt, `Exited because no new tests completed within the last ${evt.period}ms of inactivity`); + break; + case 'uncaught-exception': + this.writeCrash(evt); + break; + case 'unhandled-rejection': + this.writeCrash(evt); + break; + case 'worker-failed': + if (!this.filesWithMissingAvaImports.has(evt.testFile)) { + if (evt.nonZeroExitCode) { + this.writeCrash(evt, `${path.relative('.', evt.testFile)} exited with a non-zero exit code: ${evt.nonZeroExitCode}`); + } else { + this.writeCrash(evt, `${path.relative('.', evt.testFile)} exited due to ${evt.signal}`); + } + } + break; + case 'worker-finished': + if (!evt.forcedExit && !this.filesWithMissingAvaImports.has(evt.testFile)) { + if (fileStats.declaredTests === 0) { + this.writeCrash(evt, `No tests found in ${path.relative('.', evt.testFile)}`); + } else if (!this.failFastEnabled && fileStats.remainingTests > 0) { + this.writeCrash(evt, `${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${path.relative('.', evt.testFile)}`); + } + } + break; + case 'worker-stderr': + case 'worker-stdout': + this.stdStream.write(evt.chunk); + break; + default: + break; + } } } - module.exports = TapReporter; diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index de08e9e55..c5e0a553d 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -1,212 +1,373 @@ 'use strict'; -const indentString = require('indent-string'); -const prettyMs = require('pretty-ms'); +const os = require('os'); +const path = require('path'); +const stream = require('stream'); + const figures = require('figures'); +const indentString = require('indent-string'); const plur = require('plur'); +const prettyMs = require('pretty-ms'); const trimOffNewlines = require('trim-off-newlines'); + const chalk = require('../chalk').get(); const codeExcerpt = require('../code-excerpt'); -const colors = require('../colors'); +const colors = require('./colors'); const formatSerializedError = require('./format-serialized-error'); const improperUsageMessages = require('./improper-usage-messages'); +const prefixTitle = require('./prefix-title'); +const whileCorked = require('./while-corked'); + +class LineWriter extends stream.Writable { + constructor(dest) { + super(); + + this.dest = dest; + this.columns = dest.columns || 80; + this.lastLineIsEmpty = false; + } + + _write(chunk, encoding, callback) { + this.dest.write(chunk); + callback(); + } + + writeLine(str) { + if (str) { + this.write(indentString(str, 2) + os.EOL); + this.lastLineIsEmpty = false; + } else { + this.write(os.EOL); + this.lastLineIsEmpty = true; + } + } + + ensureEmptyLine() { + if (!this.lastLineIsEmpty) { + this.writeLine(); + } + } +} class VerboseReporter { constructor(options) { - this.options = Object.assign({}, options); + this.reportStream = options.reportStream; + this.stdStream = options.stdStream; + this.watching = options.watching; - chalk.enabled = this.options.color; - for (const key of Object.keys(colors)) { - colors[key].enabled = this.options.color; - } + this.lineWriter = new LineWriter(this.reportStream); + this.consumeStateChange = whileCorked(this.reportStream, this.consumeStateChange); + this.endRun = whileCorked(this.reportStream, this.endRun); - this.stream = process.stderr; + this.reset(); } - start() { - return ''; + reset() { + if (this.removePreviousListener) { + this.removePreviousListener(); + } + + this.failFastEnabled = false; + this.failures = []; + this.filesWithMissingAvaImports = new Set(); + this.knownFailures = []; + this.lastLineIsEmpty = false; + this.matching = false; + this.prefixTitle = (testFile, title) => title; + this.previousFailures = 0; + this.removePreviousListener = null; + this.stats = null; } - test(test) { - const lines = []; - if (test.error) { - lines.push(' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message)); - } else if (test.todo) { - lines.push(' ' + colors.todo('- ' + test.title)); - } else if (test.skip) { - lines.push(' ' + colors.skip('- ' + test.title)); - } else if (test.failing) { - lines.push(' ' + colors.error(figures.tick) + ' ' + colors.error(test.title)); - } else { - // Display duration only over a threshold - const threshold = 100; - const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : ''; + startRun(plan) { + this.reset(); + + this.failFastEnabled = plan.failFastEnabled; + this.matching = plan.matching; + this.previousFailures = plan.previousFailures; - lines.push(' ' + colors.pass(figures.tick) + ' ' + test.title + duration); + if (this.watching || plan.files.length > 1) { + this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title); } - if (test.logs) { - test.logs.forEach(log => { - const logLines = indentString(colors.log(log), 6); - const logLinesWithLeadingFigure = logLines.replace( - /^ {6}/, - ` ${colors.information(figures.info)} ` - ); + this.removePreviousListener = plan.status.on('stateChange', evt => this.consumeStateChange(evt)); - lines.push(logLinesWithLeadingFigure); - }); + if (this.watching && plan.runVector > 1) { + this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.reportStream.columns || 80)) + os.EOL); } - return lines.length > 0 ? lines.join('\n') : undefined; + this.lineWriter.writeLine(); } - unhandledError(err) { - if (err.type === 'exception' && err.name === 'AvaError') { - return colors.error(' ' + figures.cross + ' ' + err.message); + consumeStateChange(evt) { // eslint-disable-line complexity + const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null; + + switch (evt.type) { + case 'declared-test': + // Ignore + break; + case 'hook-failed': + this.failures.push(evt); + this.writeTestSummary(evt); + break; + case 'internal-error': + if (evt.testFile) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${path.relative('.', evt.testFile)}`)); + } else { + this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`)); + } + this.lineWriter.writeLine(colors.stack(evt.err.summary)); + this.lineWriter.writeLine(colors.errorStack(evt.err.stack)); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + break; + case 'missing-ava-import': + this.filesWithMissingAvaImports.add(evt.testFile); + this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${path.relative('.', evt.testFile)}, make sure to import "ava" at the top of your test file`)); + break; + case 'selected-test': + if (evt.skip) { + this.lineWriter.writeLine(colors.skip(`- ${this.prefixTitle(evt.testFile, evt.title)}`)); + } else if (evt.todo) { + this.lineWriter.writeLine(colors.todo(`- ${this.prefixTitle(evt.testFile, evt.title)}`)); + } + break; + case 'stats': + this.stats = evt.stats; + break; + case 'test-failed': + this.failures.push(evt); + this.writeTestSummary(evt); + break; + case 'test-passed': + if (evt.knownFailing) { + this.knownFailures.push(evt); + } + this.writeTestSummary(evt); + break; + case 'timeout': + this.lineWriter.writeLine(colors.error(`${figures.cross} Exited because no new tests completed within the last ${evt.period}ms of inactivity`)); + break; + case 'uncaught-exception': + this.lineWriter.ensureEmptyLine(); + this.lineWriter.writeLine(colors.title(`Uncaught exception in ${path.relative('.', evt.testFile)}`)); + this.lineWriter.writeLine(); + this.writeErr(evt); + this.lineWriter.writeLine(); + break; + case 'unhandled-rejection': + this.lineWriter.ensureEmptyLine(); + this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${path.relative('.', evt.testFile)}`)); + this.lineWriter.writeLine(); + this.writeErr(evt); + this.lineWriter.writeLine(); + break; + case 'worker-failed': + if (!this.filesWithMissingAvaImports.has(evt.testFile)) { + if (evt.nonZeroExitCode) { + this.lineWriter.writeLine(colors.error(`${figures.cross} ${path.relative('.', evt.testFile)} exited with a non-zero exit code: ${evt.nonZeroExitCode}`)); + } else { + this.lineWriter.writeLine(colors.error(`${figures.cross} ${path.relative('.', evt.testFile)} exited due to ${evt.signal}`)); + } + } + break; + case 'worker-finished': + if (!evt.forcedExit && !this.filesWithMissingAvaImports.has(evt.testFile)) { + if (fileStats.declaredTests === 0) { + this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${path.relative('.', evt.testFile)}`)); + } else if (!this.failFastEnabled && fileStats.remainingTests > 0) { + this.lineWriter.writeLine(colors.error(`${figures.cross} ${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${path.relative('.', evt.testFile)}`)); + } + } + break; + case 'worker-stderr': + case 'worker-stdout': + this.stdStream.write(evt.chunk); + // If the chunk does not end with a linebreak, *forcibly* write one to + // ensure it remains visible in the TTY. + // Tests cannot assume their standard output is not interrupted. Indeed + // we multiplex stdout and stderr into a single stream. However as + // long as stdStream is different from reportStream users can read + // their original output by redirecting the streams. + if (evt.chunk[evt.chunk.length - 1] !== 0x0A) { + this.reportStream.write(os.EOL); + } + break; + default: + break; } + } - const title = err.type === 'rejection' ? 'Unhandled rejection' : 'Uncaught exception'; - let output = ' ' + colors.title(`${title} in ${err.file}`) + '\n'; - - if (err.name) { - output += indentString(colors.stack(err.summary), 2) + '\n'; - output += indentString(colors.errorStack(err.stack), 2) + '\n\n'; - } else if (err.nonErrorObject) { - output += indentString(trimOffNewlines(err.formatted), 2) + '\n'; + writeErr(evt) { + if (evt.err.source) { + this.lineWriter.writeLine(colors.errorSource(`${evt.err.source.file}:${evt.err.source.line}`)); + const excerpt = codeExcerpt(evt.err.source, {maxWidth: this.reportStream.columns - 2}); + if (excerpt) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(excerpt); + } } - output += '\n'; + if (evt.err.avaAssertionError) { + const result = formatSerializedError(evt.err); + if (result.printMessage) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(evt.err.message); + } + + if (result.formatted) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(result.formatted); + } + + const message = improperUsageMessages.forError(evt.err); + if (message) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(message); + } + } else if (evt.err.nonErrorObject) { + this.lineWriter.writeLine(trimOffNewlines(evt.err.formatted)); + } else { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(evt.err.summary); + } - return output; + if (evt.err.stack) { + const stack = evt.err.stack; + if (stack.includes('\n')) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(colors.errorStack(stack)); + } + } } - finish(runStatus) { - let output = ''; - - const lines = [ - runStatus.failCount > 0 ? - ' ' + colors.error(runStatus.failCount, plur('test', runStatus.failCount), 'failed') : - ' ' + colors.pass(runStatus.passCount, plur('test', runStatus.passCount), 'passed'), - runStatus.knownFailureCount > 0 ? ' ' + colors.error(runStatus.knownFailureCount, plur('known failure', runStatus.knownFailureCount)) : '', - runStatus.skipCount > 0 ? ' ' + colors.skip(runStatus.skipCount, plur('test', runStatus.skipCount), 'skipped') : '', - runStatus.todoCount > 0 ? ' ' + colors.todo(runStatus.todoCount, plur('test', runStatus.todoCount), 'todo') : '', - runStatus.rejectionCount > 0 ? ' ' + colors.error(runStatus.rejectionCount, 'unhandled', plur('rejection', runStatus.rejectionCount)) : '', - runStatus.exceptionCount > 0 ? ' ' + colors.error(runStatus.exceptionCount, 'uncaught', plur('exception', runStatus.exceptionCount)) : '', - runStatus.previousFailCount > 0 ? ' ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun') : '' - ].filter(Boolean); - - if (lines.length > 0) { - if (this.options.watching) { - lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + writeLogs(evt) { + if (evt.logs) { + for (const log of evt.logs) { + const logLines = indentString(colors.log(log), 4); + const logLinesWithLeadingFigure = logLines.replace( + /^ {4}/, + ` ${colors.information(figures.info)} ` + ); + this.lineWriter.writeLine(logLinesWithLeadingFigure); } - output += lines.join('\n') + '\n'; } + } + + writeTestSummary(evt) { + if (evt.type === 'hook-failed' || evt.type === 'test-failed') { + this.lineWriter.writeLine(`${colors.error(figures.cross)} ${this.prefixTitle(evt.testFile, evt.title)} ${colors.error(evt.err.message)}`); + } else if (evt.knownFailing) { + this.lineWriter.writeLine(`${colors.error(figures.tick)} ${colors.error(this.prefixTitle(evt.testFile, evt.title))}`); + } else { + // Display duration only over a threshold + const threshold = 100; + const duration = evt.duration > threshold ? colors.duration(' (' + prettyMs(evt.duration) + ')') : ''; - if (runStatus.knownFailureCount > 0) { - runStatus.knownFailures.forEach(test => { - output += '\n\n ' + colors.error(test.title) + '\n'; - }); + this.lineWriter.writeLine(`${colors.pass(figures.tick)} ${this.prefixTitle(evt.testFile, evt.title)}${duration}`); } - output += '\n'; - if (runStatus.failCount > 0) { - runStatus.tests.forEach(test => { - if (!test.error) { - return; - } + this.writeLogs(evt); + } - output += ' ' + colors.title(test.title) + '\n'; + writeFailure(evt) { + this.lineWriter.writeLine(`${colors.title(this.prefixTitle(evt.testFile, evt.title))}`); + this.writeLogs(evt); + this.lineWriter.writeLine(); + this.writeErr(evt); + } - if (test.logs) { - test.logs.forEach(log => { - const logLines = indentString(colors.log(log), 6); - const logLinesWithLeadingFigure = logLines.replace( - /^ {6}/, - ` ${colors.information(figures.info)} ` - ); + endRun() { // eslint-disable-line complexity + if (!this.stats) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn't find any files to test`)); + this.lineWriter.writeLine(); + return; + } - output += logLinesWithLeadingFigure + '\n'; - }); + if (this.matching && this.stats.selectedTests === 0) { + this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn't find any matching tests`)); + this.lineWriter.writeLine(); + return; + } - output += '\n'; - } + this.lineWriter.writeLine(); - if (test.error.source) { - output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; + let firstLinePostfix = this.watching ? + ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') : + ''; - const excerpt = codeExcerpt(test.error.source, {maxWidth: this.stream.columns}); - if (excerpt) { - output += '\n' + indentString(excerpt, 2) + '\n'; - } - } + if (this.stats.failedHooks > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.failedTests > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.failedHooks === 0 && this.stats.failedTests === 0 && this.stats.passedTests > 0) { + this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix); + firstLinePostfix = ''; + } + if (this.stats.passedKnownFailingTests > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`)); + } + if (this.stats.skippedTests > 0) { + this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`)); + } + if (this.stats.todoTests > 0) { + this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`)); + } + if (this.stats.unhandledRejections > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`)); + } + if (this.stats.uncaughtExceptions > 0) { + this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`)); + } + if (this.previousFailures > 0) { + this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`)); + } - if (test.error.avaAssertionError) { - const result = formatSerializedError(test.error); - if (result.printMessage) { - output += '\n' + indentString(test.error.message, 2) + '\n'; - } + if (this.stats.passedKnownFailingTests > 0) { + this.lineWriter.writeLine(); + for (const evt of this.knownFailures) { + this.lineWriter.writeLine(colors.error(this.prefixTitle(evt.testFile, evt.title))); + } + } - if (result.formatted) { - output += '\n' + indentString(result.formatted, 2) + '\n'; - } + const shouldWriteFailFastDisclaimer = this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers); - 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 (this.failures.length > 0) { + this.lineWriter.writeLine(); - if (test.error.stack) { - const stack = test.error.stack; - if (stack.includes('\n')) { - output += '\n' + indentString(colors.errorStack(stack), 2) + '\n'; - } + const lastFailure = this.failures[this.failures.length - 1]; + for (const evt of this.failures) { + this.writeFailure(evt); + if (evt !== lastFailure || shouldWriteFailFastDisclaimer) { + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); + this.lineWriter.writeLine(); } - - output += '\n\n\n'; - }); + } } - if (runStatus.failFastEnabled === true && runStatus.failCount > 0 && (runStatus.remainingCount > 0 || runStatus.fileCount > runStatus.observationCount)) { + if (shouldWriteFailFastDisclaimer) { let remaining = ''; - if (runStatus.remainingCount > 0) { - remaining += `At least ${runStatus.remainingCount} ${plur('test was', 'tests were', runStatus.remainingCount)} skipped`; - if (runStatus.fileCount > runStatus.observationCount) { + if (this.stats.remainingTests > 0) { + remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`; + if (this.stats.files > this.stats.finishedWorkers) { remaining += ', as well as '; } } - if (runStatus.fileCount > runStatus.observationCount) { - const skippedFileCount = runStatus.fileCount - runStatus.observationCount; + if (this.stats.files > this.stats.finishedWorkers) { + const skippedFileCount = this.stats.files - this.stats.finishedWorkers; remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`; - if (runStatus.remainingCount === 0) { + if (this.stats.remainingTests === 0) { remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`; } } - output += ' ' + colors.information('`--fail-fast` is on. ' + remaining + '.') + '\n\n'; - } - - if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) { - output += ' ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run'); + this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`)); } - return '\n' + trimOffNewlines(output) + '\n'; - } - - section() { - return chalk.gray.dim('\u2500'.repeat(this.stream.columns || 80)); - } - - write(str) { - this.stream.write(str + '\n'); - } - - stdout(data) { - this.stream.write(data); - } - - stderr(data) { - this.stream.write(data); + this.lineWriter.writeLine(); } } diff --git a/lib/reporters/while-corked.js b/lib/reporters/while-corked.js new file mode 100644 index 000000000..1c1e382b1 --- /dev/null +++ b/lib/reporters/while-corked.js @@ -0,0 +1,12 @@ +'use strict'; +function whileCorked(stream, fn) { + return function () { + stream.cork(); + try { + fn.apply(this, arguments); + } finally { + stream.uncork(); + } + }; +} +module.exports = whileCorked; diff --git a/lib/run-status.js b/lib/run-status.js index 8e266e0e8..e2fc64ab9 100644 --- a/lib/run-status.js +++ b/lib/run-status.js @@ -1,138 +1,148 @@ 'use strict'; -const EventEmitter = require('events'); -const flatten = require('arr-flatten'); -const figures = require('figures'); -const autoBind = require('auto-bind'); -const chalk = require('./chalk').get(); -const prefixTitle = require('./prefix-title'); +const cloneDeep = require('lodash.clonedeep'); +const Emittery = require('./emittery'); -function sum(arr, key) { - let result = 0; - - arr.forEach(item => { - result += item[key]; - }); - - return result; -} - -class RunStatus extends EventEmitter { - constructor(opts) { +class RunStatus extends Emittery { + constructor(files) { super(); - opts = opts || {}; - this.prefixTitles = opts.prefixTitles !== false; - this.hasExclusive = Boolean(opts.runOnlyExclusive); - this.base = opts.base || ''; - this.rejectionCount = 0; - this.exceptionCount = 0; - this.passCount = 0; - this.knownFailureCount = 0; - this.skipCount = 0; - this.todoCount = 0; - this.failCount = 0; - this.fileCount = opts.fileCount || 0; - this.testCount = 0; - this.remainingCount = 0; - this.previousFailCount = 0; - this.knownFailures = []; - this.errors = []; - this.stats = []; - this.tests = []; - this.failFastEnabled = opts.failFast || false; - this.updateSnapshots = opts.updateSnapshots || false; - this.observationCount = 0; - - autoBind(this); - } - - observeFork(emitter) { - this.observationCount++; - emitter - .on('teardown', this.handleTeardown) - .on('stats', this.handleStats) - .on('test', this.handleTest) - .on('unhandledRejections', this.handleRejections) - .on('uncaughtException', this.handleExceptions) - .on('stdout', this.handleOutput.bind(this, 'stdout')) - .on('stderr', this.handleOutput.bind(this, 'stderr')); + this.stats = { + byFile: new Map(), + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + failedWorkers: 0, + files, + finishedWorkers: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + timeouts: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 + }; } - handleRejections(data) { - this.rejectionCount += data.rejections.length; - - data.rejections.forEach(err => { - err.type = 'rejection'; - err.file = data.file; - this.emit('error', err, this); - this.errors.push(err); + observeWorker(worker, testFile) { + this.stats.byFile.set(testFile, { + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 }); + worker.onStateChange(data => this.emitStateChange(data)); } - handleExceptions(data) { - this.exceptionCount++; - const err = data.exception; - err.type = 'exception'; - err.file = data.file; - this.emit('error', err, this); - this.errors.push(err); - } - - handleTeardown(data) { - this.emit('dependencies', data.file, data.dependencies, this); - this.emit('touchedFiles', data.touchedFiles); - } - - handleStats(stats) { - this.emit('stats', stats, this); - - if (stats.hasExclusive) { - this.hasExclusive = true; + emitStateChange(evt) { + const stats = this.stats; + const fileStats = this.stats.byFile.get(evt.testFile); + + let changedStats = true; + switch (evt.type) { + case 'declared-test': + stats.declaredTests++; + fileStats.declaredTests++; + break; + case 'hook-failed': + stats.failedHooks++; + fileStats.failedHooks++; + break; + case 'internal-error': + stats.internalErrors++; + if (evt.testFile) { + fileStats.internalErrors++; + } + break; + case 'selected-test': + stats.selectedTests++; + fileStats.selectedTests++; + if (evt.skip) { + stats.skippedTests++; + fileStats.skippedTests++; + } else if (evt.todo) { + stats.todoTests++; + fileStats.todoTests++; + } else { + stats.remainingTests++; + fileStats.remainingTests++; + } + break; + case 'test-failed': + stats.failedTests++; + fileStats.failedTests++; + stats.remainingTests--; + fileStats.remainingTests--; + break; + case 'test-passed': + if (evt.knownFailing) { + stats.passedKnownFailingTests++; + fileStats.passedKnownFailingTests++; + } else { + stats.passedTests++; + fileStats.passedTests++; + } + stats.remainingTests--; + fileStats.remainingTests--; + break; + case 'timeout': + stats.timeouts++; + break; + case 'uncaught-exception': + stats.uncaughtExceptions++; + fileStats.uncaughtExceptions++; + break; + case 'unhandled-rejection': + stats.unhandledRejections++; + fileStats.unhandledRejections++; + break; + case 'worker-failed': + stats.failedWorkers++; + break; + case 'worker-finished': + stats.finishedWorkers++; + break; + default: + changedStats = false; + break; } - this.testCount += stats.testCount; - } - - handleTest(test) { - test.title = this.prefixTitle(test.file) + test.title; - - if (test.error) { - this.errors.push(test); + if (changedStats) { + this.emit('stateChange', {type: 'stats', stats: cloneDeep(stats)}); } - - if (test.failing && !test.error) { - this.knownFailures.push(test); - } - - this.emit('test', test, this); + this.emit('stateChange', evt); } - prefixTitle(file) { - if (!this.prefixTitles) { - return ''; + suggestExitCode(circumstances) { + if (circumstances.matching && this.stats.selectedTests === 0) { + return 1; } - const separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' '; - - return prefixTitle(file, this.base, separator); - } - - handleOutput(channel, data) { - this.emit(channel, data, this); - } + if ( + this.stats.declaredTests === 0 || + this.stats.internalErrors > 0 || + this.stats.failedHooks > 0 || + this.stats.failedTests > 0 || + this.stats.failedWorkers > 0 || + this.stats.timeouts > 0 || + this.stats.uncaughtExceptions > 0 || + this.stats.unhandledRejections > 0 + ) { + return 1; + } - processResults(results) { - // Assemble stats from all tests - this.stats = results.map(result => result.stats); - this.tests = results.map(result => result.tests); - this.tests = flatten(this.tests); - this.passCount = sum(this.stats, 'passCount'); - this.knownFailureCount = sum(this.stats, 'knownFailureCount'); - this.skipCount = sum(this.stats, 'skipCount'); - this.todoCount = sum(this.stats, 'todoCount'); - this.failCount = sum(this.stats, 'failCount'); - this.remainingCount = this.testCount - this.passCount - this.failCount - this.skipCount - this.todoCount - this.knownFailureCount; + return 0; } } - module.exports = RunStatus; diff --git a/lib/runner.js b/lib/runner.js index af7f8fcae..62419c93a 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1,13 +1,14 @@ 'use strict'; -const EventEmitter = require('events'); const path = require('path'); const matcher = require('matcher'); const ContextRef = require('./context-ref'); const createChain = require('./create-chain'); +const Emittery = require('./emittery'); const snapshotManager = require('./snapshot-manager'); +const serializeError = require('./serialize-error'); const Runnable = require('./test'); -class Runner extends EventEmitter { +class Runner extends Emittery { constructor(options) { super(); @@ -26,16 +27,6 @@ class Runner extends EventEmitter { this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this); this.interrupted = false; this.snapshots = null; - this.stats = { - failCount: 0, - failedHookCount: 0, - hasExclusive: false, - knownFailureCount: 0, - passCount: 0, - skipCount: 0, - testCount: 0, - todoCount: 0 - }; this.tasks = { after: [], afterAlways: [], @@ -89,11 +80,17 @@ class Runner extends EventEmitter { // --match selects TODO tests. if (matcher([specifiedTitle], this.match).length === 1) { metadata.exclusive = true; - this.stats.hasExclusive = true; + this.runOnlyExclusive = true; } } this.tasks.todo.push({title: specifiedTitle, metadata}); + this.emit('stateChange', { + type: 'declared-test', + title: specifiedTitle, + knownFailing: false, + todo: true + }); } else { if (implementations.length === 0) { throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.'); @@ -139,10 +136,16 @@ class Runner extends EventEmitter { task.metadata.exclusive = matcher([title], this.match).length === 1; } if (task.metadata.exclusive) { - this.stats.hasExclusive = true; + this.runOnlyExclusive = true; } this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task); + this.emit('stateChange', { + type: 'declared-test', + title, + knownFailing: metadata.failing, + todo: false + }); } else if (!metadata.skipped) { this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task); } @@ -178,15 +181,16 @@ class Runner extends EventEmitter { saveSnapshotState() { if (this.snapshots) { - const files = this.snapshots.save(); - if (files) { - this.emit('touched', files); - } - } else if (this.updateSnapshots) { + return this.snapshots.save(); + } + + 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? } + + return null; } onRun(runnable) { @@ -278,8 +282,13 @@ class Runner extends EventEmitter { // Only emit results for failed hooks. for (const result of outcome.storedResults) { if (!result.passed) { - this.stats.failedHookCount++; - this.emit('hook-failed', result); + this.emit('stateChange', { + type: 'hook-failed', + title: result.title, + err: serializeError('Hook failure', true, result.error), + duration: result.duration, + logs: result.logs + }); } } return false; @@ -306,20 +315,27 @@ class Runner extends EventEmitter { title: task.title }); return this.runSingle(test).then(result => { - if (!result.passed) { - this.stats.failCount++; - this.emit('test', result); - // Don't run `afterEach` hooks if the test failed. - return false; + if (result.passed) { + this.emit('stateChange', { + type: 'test-passed', + title: result.title, + duration: result.duration, + knownFailing: result.metadata.failing, + logs: result.logs + }); + return this.runHooks(this.tasks.afterEach, contextRef, hookSuffix); } - if (result.metadata.failing) { - this.stats.knownFailureCount++; - } else { - this.stats.passCount++; - } - this.emit('test', result); - return this.runHooks(this.tasks.afterEach, contextRef, hookSuffix); + this.emit('stateChange', { + type: 'test-failed', + title: result.title, + err: serializeError('Test failure', true, result.error), + duration: result.duration, + knownFailing: result.metadata.failing, + logs: result.logs + }); + // Don't run `afterEach` hooks if the test failed. + return false; }); }).then(hooksAndTestOk => { return this.runHooks(this.tasks.afterEachAlways, contextRef, hookSuffix).then(alwaysOk => { @@ -329,65 +345,63 @@ class Runner extends EventEmitter { } start() { - const runOnlyExclusive = this.stats.hasExclusive || this.runOnlyExclusive; - - const todoTitles = []; - for (const task of this.tasks.todo) { - if (runOnlyExclusive && !task.metadata.exclusive) { - continue; - } - - this.stats.testCount++; - this.stats.todoCount++; - todoTitles.push(task.title); - } - const concurrentTests = []; const serialTests = []; - const skippedTests = []; for (const task of this.tasks.serial) { - if (runOnlyExclusive && !task.metadata.exclusive) { + if (this.runOnlyExclusive && !task.metadata.exclusive) { continue; } - this.stats.testCount++; - if (task.metadata.skipped) { - this.stats.skipCount++; - skippedTests.push({ - failing: task.metadata.failing, - title: task.title - }); - } else { + this.emit('stateChange', { + type: 'selected-test', + title: task.title, + knownFailing: task.metadata.failing, + skip: task.metadata.skipped, + todo: false + }); + + if (!task.metadata.skipped) { serialTests.push(task); } } for (const task of this.tasks.concurrent) { - if (runOnlyExclusive && !task.metadata.exclusive) { + if (this.runOnlyExclusive && !task.metadata.exclusive) { continue; } - this.stats.testCount++; - if (task.metadata.skipped) { - this.stats.skipCount++; - skippedTests.push({ - failing: task.metadata.failing, - title: task.title - }); - } else if (this.serial) { - serialTests.push(task); - } else { - concurrentTests.push(task); + this.emit('stateChange', { + type: 'selected-test', + title: task.title, + knownFailing: task.metadata.failing, + skip: task.metadata.skipped, + todo: false + }); + + if (!task.metadata.skipped) { + if (this.serial) { + serialTests.push(task); + } else { + concurrentTests.push(task); + } } } - if (concurrentTests.length === 0 && serialTests.length === 0) { - this.emit('start', { - // `ended` is always resolved with `undefined`. - ended: Promise.resolve(undefined), - skippedTests, - stats: this.stats, - todoTitles + for (const task of this.tasks.todo) { + if (this.runOnlyExclusive && !task.metadata.exclusive) { + continue; + } + + this.emit('stateChange', { + type: 'selected-test', + title: task.title, + knownFailing: false, + skip: false, + todo: true }); + } + + if (concurrentTests.length === 0 && serialTests.length === 0) { + this.emit('finish'); // Don't run any hooks if there are no tests to run. return; } @@ -443,23 +457,15 @@ class Runner extends EventEmitter { const beforeExitHandler = this.beforeExitHandler.bind(this); process.on('beforeExit', beforeExitHandler); - const ended = concurrentPromise + concurrentPromise // Only run `after` hooks if all hooks and tests passed. .then(ok => ok && this.runHooks(this.tasks.after, contextRef)) // Always run `after.always` hooks. .then(() => this.runHooks(this.tasks.afterAlways, contextRef)) .then(() => { process.removeListener('beforeExit', beforeExitHandler); - // `ended` is always resolved with `undefined`. - return undefined; - }); - - this.emit('start', { - ended, - skippedTests, - stats: this.stats, - todoTitles - }); + }) + .then(() => this.emit('finish'), err => this.emit('error', err)); } interrupt() { diff --git a/lib/watcher.js b/lib/watcher.js index 3f5ed3ee7..c8efe4ff7 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -78,7 +78,7 @@ class TestDependency { } class Watcher { - constructor(logger, api, files, sources) { + constructor(reporter, api, files, sources) { this.debouncer = new Debouncer(this); this.avaFiles = new AvaFiles({ files, @@ -89,46 +89,49 @@ class Watcher { this.runVector = 0; this.previousFiles = files; this.run = (specificFiles, updateSnapshots) => { + const clearLogOnNextRun = this.clearLogOnNextRun && this.runVector > 0; if (this.runVector > 0) { - const cleared = this.clearLogOnNextRun && logger.clear(); - if (!cleared) { - logger.reset(); - logger.section(); - } this.clearLogOnNextRun = true; - - logger.reset(); - logger.start(); } - - this.runVector += 1; - - const currentVector = this.runVector; + this.runVector++; let runOnlyExclusive = false; - if (specificFiles) { const exclusiveFiles = specificFiles.filter(file => this.filesWithExclusiveTests.indexOf(file) !== -1); - runOnlyExclusive = exclusiveFiles.length !== this.filesWithExclusiveTests.length; - if (runOnlyExclusive) { // The test files that previously contained exclusive tests are always // run, together with the remaining specific files. const remainingFiles = diff(specificFiles, exclusiveFiles); specificFiles = this.filesWithExclusiveTests.concat(remainingFiles); } + + this.pruneFailures(specificFiles); } this.touchedFiles.clear(); this.previousFiles = specificFiles || files; - this.busy = api.run(this.previousFiles, {runOnlyExclusive, updateSnapshots: updateSnapshots === true}) + this.busy = api.run(this.previousFiles, { + clearLogOnNextRun, + previousFailures: this.sumPreviousFailures(this.runVector), + runOnlyExclusive, + runVector: this.runVector, + updateSnapshots: updateSnapshots === true + }) .then(runStatus => { - runStatus.previousFailCount = this.sumPreviousFailures(currentVector); - logger.finish(runStatus); - - const badCounts = runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount; - this.clearLogOnNextRun = this.clearLogOnNextRun && badCounts === 0; + reporter.endRun(); + + if (this.clearLogOnNextRun && ( + runStatus.stats.failedHooks > 0 || + runStatus.stats.failedTests > 0 || + runStatus.stats.failedWorkers > 0 || + runStatus.stats.internalErrors > 0 || + runStatus.stats.timeouts > 0 || + runStatus.stats.uncaughtExceptions > 0 || + runStatus.stats.unhandledRejections > 0 + )) { + this.clearLogOnNextRun = false; + } }) .catch(rethrowAsync); }; @@ -168,10 +171,14 @@ class Watcher { trackTestDependencies(api) { const relative = absPath => nodePath.relative(process.cwd(), absPath); - api.on('test-run', runStatus => { - runStatus.on('dependencies', (file, dependencies) => { - const sourceDeps = dependencies.map(x => relative(x)).filter(this.avaFiles.isSource); - this.updateTestDependencies(file, sourceDeps); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type !== 'dependencies') { + return; + } + + const sourceDeps = evt.dependencies.map(x => relative(x)).filter(this.avaFiles.isSource); + this.updateTestDependencies(evt.testFile, sourceDeps); }); }); } @@ -198,9 +205,13 @@ class Watcher { } trackTouchedFiles(api) { - api.on('test-run', runStatus => { - runStatus.on('touchedFiles', files => { - for (const file of files) { + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type !== 'touched-files') { + return; + } + + for (const file of evt.files) { this.touchedFiles.add(nodePath.relative(process.cwd(), file)); } }); @@ -208,8 +219,15 @@ class Watcher { } trackExclusivity(api) { - api.on('stats', stats => { - this.updateExclusivity(stats.file, stats.hasExclusive); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type !== 'worker-finished') { + return; + } + + const fileStats = plan.status.stats.byFile.get(evt.testFile); + this.updateExclusivity(evt.testFile, fileStats.declaredTests > fileStats.selectedTests); + }); }); } @@ -224,25 +242,34 @@ class Watcher { } trackFailures(api) { - api.on('test-run', (runStatus, files) => { - files.forEach(file => { - this.pruneFailures(nodePath.relative(process.cwd(), file)); - }); + api.on('run', plan => { + this.pruneFailures(plan.files); const currentVector = this.runVector; - runStatus.on('error', err => { - this.countFailure(err.file, currentVector); - }); - runStatus.on('test', result => { - if (result.error) { - this.countFailure(result.file, currentVector); + plan.status.on('stateChange', evt => { + if (!evt.testFile) { + return; + } + + switch (evt.type) { + case 'hook-failed': + case 'internal-error': + case 'test-failed': + case 'uncaught-exception': + case 'unhandled-rejection': + case 'worker-failed': + this.countFailure(evt.testFile, currentVector); + break; + default: + break; } }); }); } - pruneFailures(file) { - this.filesWithFailures = this.filesWithFailures.filter(state => state.file !== file); + pruneFailures(files) { + const toPrune = new Set(files); + this.filesWithFailures = this.filesWithFailures.filter(state => !toPrune.has(state.file)); } countFailure(file, vector) { @@ -280,7 +307,7 @@ class Watcher { unlinkedTests.forEach(testFile => { this.updateTestDependencies(testFile, []); this.updateExclusivity(testFile, false); - this.pruneFailures(testFile); + this.pruneFailures([testFile]); }); } diff --git a/lib/worker/dependency-tracker.js b/lib/worker/dependency-tracker.js index 524d2297b..6f87d79c5 100644 --- a/lib/worker/dependency-tracker.js +++ b/lib/worker/dependency-tracker.js @@ -1,18 +1,29 @@ 'use strict'; /* eslint-disable node/no-deprecated-api */ +const ipc = require('./ipc'); const seenDependencies = new Set(); -function getAll() { - return Array.from(seenDependencies); +let newDependencies = []; +function flush() { + if (newDependencies.length === 0) { + return; + } + + ipc.send({type: 'dependencies', dependencies: newDependencies}); + newDependencies = []; } -exports.getAll = getAll; +exports.flush = flush; function track(filename) { if (seenDependencies.has(filename)) { return; } + if (newDependencies.length === 0) { + process.nextTick(flush); + } seenDependencies.add(filename); + newDependencies.push(filename); } exports.track = track; diff --git a/lib/worker/ipc.js b/lib/worker/ipc.js index 2164d3ffa..695edae0f 100644 --- a/lib/worker/ipc.js +++ b/lib/worker/ipc.js @@ -1,34 +1,54 @@ 'use strict'; +const Emittery = require('../emittery'); // `process.channel` was added in Node.js 7.1.0, but the channel was available // through an undocumented API as `process._channel`. const channel = process.channel || process._channel; -// Parse and re-emit AVA messages +const emitter = new Emittery(); process.on('message', message => { if (!message.ava) { return; } - process.emit(message.name, message.data); + switch (message.ava.type) { + case 'peer-failed': + emitter.emit('peerFailed'); + break; + case 'pong': + emitter.emit('pong'); + break; + default: + break; + } }); -exports.send = (name, data) => { - process.send({ - name: `ava-${name}`, - data, - ava: true - }); -}; +exports.peerFailed = emitter.once('peerFailed'); -let allowUnref = true; -exports.unrefChannel = () => { - if (allowUnref) { - channel.unref(); +function send(evt) { + if (process.connected) { + process.send({ava: evt}); } -}; +} +exports.send = send; -exports.forceRefChannel = () => { - allowUnref = false; +function unref() { + channel.unref(); +} +exports.unref = unref; + +let pendingPings = Promise.resolve(); +function flush() { channel.ref(); -}; + const promise = pendingPings.then(() => { + send({type: 'ping'}); + return emitter.once('pong'); + }).then(() => { + if (promise === pendingPings) { + unref(); + } + }); + pendingPings = promise; + return promise; +} +exports.flush = flush; diff --git a/lib/worker/subprocess.js b/lib/worker/subprocess.js index 3ddcfa7e0..591211ea9 100644 --- a/lib/worker/subprocess.js +++ b/lib/worker/subprocess.js @@ -8,6 +8,7 @@ require('./load-chalk'); require('./consume-argv'); require('./fake-tty'); +const nowAndTimers = require('../now-and-timers'); const Runner = require('../runner'); const serializeError = require('../serialize-error'); const dependencyTracking = require('./dependency-tracker'); @@ -15,6 +16,15 @@ const ipc = require('./ipc'); const options = require('./options').get(); const precompilerHook = require('./precompiler-hook'); +function exit(code) { + if (!process.exitCode) { + process.exitCode = code; + } + + dependencyTracking.flush(); + return ipc.flush().then(() => process.exit()); +} + const runner = new Runner({ failFast: options.failFast, failWithoutAssertions: options.failWithoutAssertions, @@ -27,122 +37,10 @@ const runner = new Runner({ updateSnapshots: options.updateSnapshots }); -let accessedRunner = false; -exports.getRunner = () => { - accessedRunner = true; - return runner; -}; - -runner.on('dependency', dependencyTracking.track); - -const touchedFiles = new Set(); -runner.on('touched', files => { - for (const file of files) { - touchedFiles.add(file); - } -}); - -runner.on('start', started => { - ipc.send('stats', { - testCount: started.stats.testCount, - hasExclusive: started.stats.hasExclusive - }); - - for (const partial of started.skippedTests) { - ipc.send('test', { - duration: null, - error: null, - failing: partial.failing, - logs: [], - skip: true, - title: partial.title, - todo: false, - type: 'test' - }); - } - for (const title of started.todoTitles) { - ipc.send('test', { - duration: null, - error: null, - failing: false, - logs: [], - skip: true, - title, - todo: true, - type: 'test' - }); - } - - started.ended.then(() => { - runner.saveSnapshotState(); - return exit(); - }).catch(err => { - handleUncaughtException(err); - }); -}); - -runner.on('hook-failed', result => { - ipc.send('test', { - duration: result.duration, - error: serializeError('Hook failure', true, result.error), - failing: result.metadata.failing, - logs: result.logs, - skip: result.metadata.skip, - title: result.title, - todo: result.metadata.todo, - type: result.metadata.type - }); -}); - -runner.on('test', result => { - ipc.send('test', { - duration: result.duration, - error: result.passed ? null : serializeError('Test failure', true, result.error), - failing: result.metadata.failing, - logs: result.logs, - skip: result.metadata.skip, - title: result.title, - todo: result.metadata.todo, - type: result.metadata.type - }); +ipc.peerFailed.then(() => { + runner.interrupt(); }); -// Track when exiting begins, to avoid repeatedly sending stats, or sending -// individual test results once stats have been sent. This is necessary since -// exit() can be invoked from the worker process and over IPC. -let exiting = false; -function exit() { - if (exiting) { - return; - } - exiting = true; - - // Reference the IPC channel so the exit sequence can be completed. - ipc.forceRefChannel(); - - const stats = { - failCount: runner.stats.failCount + runner.stats.failedHookCount, - knownFailureCount: runner.stats.knownFailureCount, - passCount: runner.stats.passCount, - skipCount: runner.stats.skipCount, - testCount: runner.stats.testCount, - todoCount: runner.stats.todoCount - }; - ipc.send('results', {stats}); -} - -function handleUncaughtException(exception) { - if (runner.attributeLeakedError(exception)) { - return; - } - - // Ensure the IPC channel is referenced. The uncaught exception will kick off - // the teardown sequence, for which the messages must be received. - ipc.forceRefChannel(); - - ipc.send('uncaughtException', {exception: serializeError('Uncaught exception', true, exception)}); -} - const attributedRejections = new Set(); process.on('unhandledRejection', (reason, promise) => { if (runner.attributeLeakedError(reason)) { @@ -150,47 +48,51 @@ process.on('unhandledRejection', (reason, promise) => { } }); -process.on('uncaughtException', handleUncaughtException); +runner.on('dependency', dependencyTracking.track); +runner.on('stateChange', state => ipc.send(state)); + +runner.on('error', err => { + ipc.send({type: 'internal-error', err: serializeError('Internal runner error', false, err)}); + exit(1); +}); -let tearingDown = false; -process.on('ava-teardown', () => { - // AVA-teardown can be sent more than once - if (tearingDown) { +runner.on('finish', () => { + try { + const touchedFiles = runner.saveSnapshotState(); + if (touchedFiles) { + ipc.send({type: 'touched-files', files: touchedFiles}); + } + } catch (err) { + ipc.send({type: 'internal-error', err: serializeError('Internal runner error', false, err)}); + exit(1); return; } - tearingDown = true; - // Reference the IPC channel so the teardown sequence can be completed. - ipc.forceRefChannel(); - - const rejections = currentlyUnhandled().filter(rejection => !attributedRejections.has(rejection.promise)); - if (rejections.length > 0) { - ipc.send('unhandledRejections', { - rejections: rejections.map(rejection => serializeError('Unhandled rejection', true, rejection.reason)) + nowAndTimers.setImmediate(() => { + currentlyUnhandled().filter(rejection => { + return !attributedRejections.has(rejection.promise); + }).forEach(rejection => { + ipc.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason)}); }); - } - // 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. Also include any files that AVA touched during the - // test run. This allows the watcher to ignore modifications to those files. - ipc.send('teardown', { - dependencies: dependencyTracking.getAll(), - touchedFiles: Array.from(touchedFiles) + exit(0); }); }); -process.on('ava-exit', () => { - process.exit(0); // eslint-disable-line xo/no-process-exit -}); +process.on('uncaughtException', err => { + if (runner.attributeLeakedError(err)) { + return; + } -process.on('ava-init-exit', () => { - exit(); + ipc.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, err)}); + exit(1); }); -process.on('ava-peer-failed', () => { - runner.interrupt(); -}); +let accessedRunner = false; +exports.getRunner = () => { + accessedRunner = true; + return runner; +}; // Store value in case to prevent required modules from modifying it. const testPath = options.file; @@ -212,17 +114,17 @@ try { }); require(testPath); -} catch (err) { - handleUncaughtException(err); -} finally { - ipc.send('loaded-file', {avaRequired: accessedRunner}); if (accessedRunner) { // Unreference the IPC channel if the test file required AVA. This stops it // from keeping the event loop busy, which means the `beforeExit` event can be // used to detect when tests stall. - // If AVA was not required then the parent process will initiated a teardown - // sequence, for which this process ought to stay active. - ipc.unrefChannel(); + ipc.unref(); + } else { + ipc.send({type: 'missing-ava-import'}); + exit(1); } +} catch (err) { + ipc.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, err)}); + exit(1); } diff --git a/package-lock.json b/package-lock.json index cd58299a5..e1c81a707 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1700,6 +1700,11 @@ } } }, + "clone": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", + "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2120,6 +2125,14 @@ } } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "1.0.3" + } + }, "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -2267,6 +2280,11 @@ "jsbn": "0.1.1" } }, + "emittery": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.3.0.tgz", + "integrity": "sha512-Bn/IFhx+BQIjTKn0vq7YWwo/yfTNeBZMqOGufY5FEV07tbwy5heDROFDCkMO2PcO5s7B9FDDXZc+JGgl6KzBOQ==" + }, "empower-core": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz", @@ -4488,14 +4506,6 @@ "is-buffer": "1.1.5" } }, - "last-line-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/last-line-stream/-/last-line-stream-1.0.0.tgz", - "integrity": "sha1-0bZNafhv8kry0EiDos7uFFIKVgA=", - "requires": { - "through2": "2.0.3" - } - }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -4672,7 +4682,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, "requires": { "chalk": "2.3.2" } @@ -6596,6 +6605,19 @@ "wordwrap": "1.0.0" } }, + "ora": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", + "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", + "requires": { + "chalk": "2.3.2", + "cli-cursor": "2.1.0", + "cli-spinners": "1.1.0", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0", + "wcwidth": "1.0.1" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -8860,15 +8882,6 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "requires": { - "readable-stream": "2.2.10", - "xtend": "4.0.1" - } - }, "time-zone": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", @@ -9417,6 +9430,14 @@ "extsprintf": "1.0.2" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "1.0.3" + } + }, "well-known-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-1.0.0.tgz", diff --git a/package.json b/package.json index 52edb3838..f008e722a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "clean-stack": "^1.1.1", "clean-yaml-object": "^0.1.0", "cli-cursor": "^2.1.0", - "cli-spinners": "^1.1.0", "cli-truncate": "^1.1.0", "co-with-promise": "^4.6.0", "code-excerpt": "^2.1.1", @@ -91,6 +90,7 @@ "currently-unhandled": "^0.4.1", "debug": "^3.1.0", "dot-prop": "^4.2.0", + "emittery": "^0.3.0", "empower-core": "^0.6.1", "equal-length": "^1.0.0", "figures": "^2.0.0", @@ -105,8 +105,8 @@ "is-generator-fn": "^1.0.0", "is-observable": "^1.1.0", "is-promise": "^2.1.0", - "last-line-stream": "^1.0.0", "lodash.clone": "^4.5.0", + "lodash.clonedeep": "^4.5.0", "lodash.clonedeepwith": "^4.5.0", "lodash.debounce": "^4.0.3", "lodash.difference": "^4.3.0", @@ -119,6 +119,7 @@ "ms": "^2.1.1", "multimatch": "^2.1.0", "observable-to-promise": "^0.5.0", + "ora": "^2.0.0", "package-hash": "^2.0.0", "pkg-conf": "^2.1.0", "plur": "^2.0.0", @@ -134,6 +135,7 @@ "supertap": "^1.0.0", "supports-color": "^5.3.0", "trim-off-newlines": "^1.0.1", + "trim-right": "^1.0.1", "unique-temp-dir": "^1.0.0", "update-notifier": "^2.4.0" }, diff --git a/profile.js b/profile.js index 7cb790665..65278ffbd 100644 --- a/profile.js +++ b/profile.js @@ -6,7 +6,6 @@ require('./lib/worker/load-chalk'); // eslint-disable-line import/no-unassigned- // Intended to be used with iron-node for profiling purposes. const path = require('path'); -const EventEmitter = require('events'); const meow = require('meow'); const Promise = require('bluebird'); const pkgConf = require('pkg-conf'); @@ -15,6 +14,8 @@ const arrify = require('arrify'); const resolveCwd = require('resolve-cwd'); const babelConfigHelper = require('./lib/babel-config'); const CachingPrecompiler = require('./lib/caching-precompiler'); +const RunStatus = require('./lib/run-status'); +const VerboseReporter = require('./lib/reporters/verbose'); function resolveModules(modules) { return arrify(modules).map(name => { @@ -97,62 +98,57 @@ babelConfigHelper.build(process.cwd(), cacheDir, babelConfigHelper.validate(conf require: resolveModules(conf.require) }; - const events = new EventEmitter(); - events.on('loaded-file', () => {}); - - let failCount = 0; - let uncaughtExceptionCount = 0; - // Mock the behavior of a parent process + process.connected = true; process.channel = {ref() {}, unref() {}}; - process.send = data => { - if (data && data.ava) { - const name = data.name.replace(/^ava-/, ''); - - if (events.listeners(name).length > 0) { - events.emit(name, data.data); - } else { - console.log('UNHANDLED AVA EVENT:', name, data.data); - } - - return; - } - - console.log('NON AVA EVENT:', data); - }; - events.on('test', data => { - console.log('TEST:', data.title, data.error); + const reporter = new VerboseReporter({ + reportStream: process.stdout, + stdStream: process.stderr, + watching: false }); - events.on('results', data => { - if (console.profileEnd) { - console.profileEnd(); + const runStatus = new RunStatus([file]); + runStatus.observeWorker({ + onStateChange(listener) { + const emit = evt => listener(Object.assign(evt, {testFile: file})); + process.send = data => { + if (data && data.ava) { + const evt = data.ava; + if (evt.type === 'ping') { + if (console.profileEnd) { + console.profileEnd(); + } + + if (process.exitCode) { + emit({type: 'worker-failed', nonZeroExitCode: process.exitCode}); + } else { + emit({type: 'worker-finished', forcedExit: false}); + process.exitCode = runStatus.suggestExitCode({matching: false}); + } + + setImmediate(() => { + reporter.endRun(); + process.emit('message', {ava: {type: 'pong'}}); + }); + } else { + emit(data.ava); + } + } + }; } - - console.log('RESULTS:', data.stats); - - failCount = data.stats.failCount; - setImmediate(() => process.emit('ava-teardown')); - }); - - events.on('teardown', () => { - if (process.exit) { - process.exit(failCount + uncaughtExceptionCount); // eslint-disable-line unicorn/no-process-exit - } - }); - - events.on('stats', () => { - setImmediate(() => { - process.emit('ava-run', {}); - }); + }, file); + + reporter.startRun({ + failFastEnabled: false, + files: [file], + matching: false, + previousFailures: 0, + status: runStatus }); - events.on('uncaughtException', data => { - uncaughtExceptionCount++; - let stack = data && data.exception && data.exception.stack; - stack = stack || data; - console.log(stack); + process.on('beforeExit', () => { + process.exitCode = process.exitCode || runStatus.suggestExitCode({matching: false}); }); // The "subprocess" will read process.argv[2] for options diff --git a/test/api.js b/test/api.js index 6b4a4cb5e..4a4a537b4 100644 --- a/test/api.js +++ b/test/api.js @@ -3,7 +3,6 @@ require('../lib/chalk').set(); const path = require('path'); const fs = require('fs'); -const figures = require('figures'); const del = require('del'); const test = require('tap').test; const Api = require('../api'); @@ -29,8 +28,8 @@ test('ES2015 support', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/es2015.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -41,8 +40,8 @@ test('precompile helpers', t => { }); return api.run() - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -50,8 +49,8 @@ test('generators support', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/generators.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -59,148 +58,8 @@ test('async/await support', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/async-await.js')]) - .then(result => { - t.is(result.passCount, 2); - }); -}); - -test('test title prefixes — multiple files', t => { - t.plan(5); - - const separator = ` ${figures.pointerSmall} `; - const files = [ - path.join(__dirname, 'fixture/async-await.js'), - path.join(__dirname, 'fixture/generators.js'), - path.join(__dirname, 'fixture/subdir/in-a-subdir.js') - ]; - const expected = [ - ['async-await', 'async function'].join(separator), - ['async-await', 'arrow async function'].join(separator), - ['generators', 'generator function'].join(separator), - ['subdir', 'in-a-subdir', 'subdir'].join(separator) - ]; - let index; - - const api = apiCreator(); - - api.run(files) - .then(() => { - // If all lines were removed from expected output - // actual output matches expected output - t.is(expected.length, 0); - }); - - api.on('test-run', runStatus => { - runStatus.on('test', a => { - index = expected.indexOf(a.title); - - t.true(index >= 0); - - // Remove line from expected output - expected.splice(index, 1); - }); - }); -}); - -test('test title prefixes — single file', t => { - t.plan(2); - - const separator = ` ${figures.pointerSmall} `; - const files = [ - path.join(__dirname, 'fixture/generators.js') - ]; - const expected = [ - ['generator function'].join(separator) - ]; - let index; - - const api = apiCreator(); - - api.run(files) - .then(() => { - // If all lines were removed from expected output - // actual output matches expected output - t.is(expected.length, 0); - }); - - api.on('test-run', runStatus => { - runStatus.on('test', a => { - index = expected.indexOf(a.title); - - t.true(index >= 0); - - // Remove line from expected output - expected.splice(index, 1); - }); - }); -}); - -test('test title prefixes — single file (explicit)', t => { - t.plan(2); - - const separator = ` ${figures.pointerSmall} `; - const files = [ - path.join(__dirname, 'fixture/generators.js') - ]; - const expected = [ - ['generators', 'generator function'].join(separator) - ]; - let index; - - const api = apiCreator({ - explicitTitles: true - }); - - api.run(files) - .then(() => { - // If all lines were removed from expected output - // actual output matches expected output - t.is(expected.length, 0); - }); - - api.on('test-run', runStatus => { - runStatus.on('test', a => { - index = expected.indexOf(a.title); - - t.true(index >= 0); - - // Remove line from expected output - expected.splice(index, 1); - }); - }); -}); - -test('display filename prefixes for failed test stack traces', t => { - const files = [ - path.join(__dirname, 'fixture/es2015.js'), - path.join(__dirname, 'fixture/one-pass-one-fail.js') - ]; - - const api = apiCreator(); - - return api.run(files) - .then(result => { - t.is(result.passCount, 2); - t.is(result.failCount, 1); - t.match(result.errors[0].title, /one-pass-one-fail \S this is a failing test/); - }); -}); - -// This is a seperate test because we can't ensure the order of the errors (to match them), and this is easier than -// sorting. -test('display filename prefixes for failed test stack traces in subdirs', t => { - const files = [ - path.join(__dirname, 'fixture/es2015.js'), - path.join(__dirname, 'fixture/subdir/failing-subdir.js') - ]; - - const api = apiCreator(); - - return api.run(files) - .then(result => { - t.is(result.passCount, 1); - t.is(result.failCount, 1); - t.match(result.errors[0].title, /subdir \S failing-subdir \S subdir fail/); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); }); }); @@ -211,17 +70,24 @@ test('fail-fast mode - single file & serial', t => { const tests = []; - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + tests.push({ + ok: false, + title: evt.title + }); + } else if (evt.type === 'test-passed') { + tests.push({ + ok: true, + title: evt.title + }); + } }); }); return api.run([path.join(__dirname, 'fixture/fail-fast/single-file/test.js')]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); t.strictDeepEqual(tests, [{ ok: true, @@ -233,8 +99,8 @@ test('fail-fast mode - single file & serial', t => { ok: true, title: 'third pass' }]); - t.is(result.passCount, 2); - t.is(result.failCount, 1); + t.is(runStatus.stats.passedTests, 2); + t.is(runStatus.stats.failedTests, 1); }); }); @@ -246,12 +112,21 @@ test('fail-fast mode - multiple files & serial', t => { const tests = []; - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + tests.push({ + ok: false, + testFile: evt.testFile, + title: evt.title + }); + } else if (evt.type === 'test-passed') { + tests.push({ + ok: true, + testFile: evt.testFile, + title: evt.title + }); + } }); }); @@ -259,17 +134,19 @@ test('fail-fast mode - multiple files & serial', t => { path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), path.join(__dirname, 'fixture/fail-fast/multiple-files/passes.js') ]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); t.strictDeepEqual(tests, [{ ok: true, - title: `fails ${figures.pointerSmall} first pass` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), + title: 'first pass' }, { ok: false, - title: `fails ${figures.pointerSmall} second fail` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), + title: 'second fail' }]); - t.is(result.passCount, 1); - t.is(result.failCount, 1); + t.is(runStatus.stats.passedTests, 1); + t.is(runStatus.stats.failedTests, 1); }); }); @@ -281,12 +158,21 @@ test('fail-fast mode - multiple files & interrupt', t => { const tests = []; - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + tests.push({ + ok: false, + testFile: evt.testFile, + title: evt.title + }); + } else if (evt.type === 'test-passed') { + tests.push({ + ok: true, + testFile: evt.testFile, + title: evt.title + }); + } }); }); @@ -294,23 +180,27 @@ test('fail-fast mode - multiple files & interrupt', t => { path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), path.join(__dirname, 'fixture/fail-fast/multiple-files/passes-slow.js') ]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); t.strictDeepEqual(tests, [{ ok: true, - title: `fails ${figures.pointerSmall} first pass` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), + title: 'first pass' }, { ok: false, - title: `fails ${figures.pointerSmall} second fail` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), + title: 'second fail' }, { ok: true, - title: `fails ${figures.pointerSmall} third pass` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'), + title: 'third pass' }, { ok: true, - title: `passes-slow ${figures.pointerSmall} first pass` + testFile: path.join(__dirname, 'fixture/fail-fast/multiple-files/passes-slow.js'), + title: 'first pass' }]); - t.is(result.passCount, 3); - t.is(result.failCount, 1); + t.is(runStatus.stats.passedTests, 3); + t.is(runStatus.stats.failedTests, 1); }); }); @@ -321,17 +211,23 @@ test('fail-fast mode - crash & serial', t => { }); const tests = []; - const errors = []; - - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); - }); - runStatus.on('error', err => { - errors.push(err); + const workerFailures = []; + + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + tests.push({ + ok: false, + title: evt.title + }); + } else if (evt.type === 'test-passed') { + tests.push({ + ok: true, + title: evt.title + }); + } else if (evt.type === 'worker-failed') { + workerFailures.push(evt); + } }); }); @@ -339,14 +235,13 @@ test('fail-fast mode - crash & serial', t => { path.join(__dirname, 'fixture/fail-fast/crash/crashes.js'), path.join(__dirname, 'fixture/fail-fast/crash/passes.js') ]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); t.strictDeepEqual(tests, []); - t.is(errors.length, 1); - t.is(errors[0].name, 'AvaError'); - t.is(errors[0].message, `${path.join('test', 'fixture', 'fail-fast', 'crash', 'crashes.js')} exited with a non-zero exit code: 1`); - t.is(result.passCount, 0); - t.is(result.failCount, 0); + t.is(workerFailures.length, 1); + t.is(workerFailures[0].testFile, path.join(__dirname, 'fixture', 'fail-fast', 'crash', 'crashes.js')); + t.is(runStatus.stats.passedTests, 0); + t.is(runStatus.stats.failedTests, 0); }); }); @@ -358,17 +253,23 @@ test('fail-fast mode - timeout & serial', t => { }); const tests = []; - const errors = []; - - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); - }); - runStatus.on('error', err => { - errors.push(err); + const timeouts = []; + + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + tests.push({ + ok: false, + title: evt.title + }); + } else if (evt.type === 'test-passed') { + tests.push({ + ok: true, + title: evt.title + }); + } else if (evt.type === 'timeout') { + timeouts.push(evt); + } }); }); @@ -376,14 +277,13 @@ test('fail-fast mode - timeout & serial', t => { path.join(__dirname, 'fixture/fail-fast/timeout/fails.js'), path.join(__dirname, 'fixture/fail-fast/timeout/passes.js') ]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); t.strictDeepEqual(tests, []); - t.is(errors.length, 1); - t.is(errors[0].name, 'AvaError'); - t.is(errors[0].message, 'Exited because no new tests completed within the last 100ms of inactivity'); - t.is(result.passCount, 0); - t.is(result.failCount, 0); + t.is(timeouts.length, 1); + t.is(timeouts[0].period, 100); + t.is(runStatus.stats.passedTests, 0); + t.is(runStatus.stats.failedTests, 0); }); }); @@ -392,29 +292,14 @@ test('fail-fast mode - no errors', t => { failFast: true }); - const tests = []; - const errors = []; - - api.on('test-run', runStatus => { - runStatus.on('test', test => { - tests.push({ - ok: !test.error, - title: test.title - }); - }); - runStatus.on('error', err => { - errors.push(err); - }); - }); - return api.run([ path.join(__dirname, 'fixture/fail-fast/without-error/a.js'), path.join(__dirname, 'fixture/fail-fast/without-error/b.js') ]) - .then(result => { + .then(runStatus => { t.ok(api.options.failFast); - t.is(result.passCount, 2); - t.is(result.failCount, 0); + t.is(runStatus.stats.passedTests, 2); + t.is(runStatus.stats.failedTests, 0); }); }); @@ -424,19 +309,10 @@ test('serial execution mode', t => { }); return api.run([path.join(__dirname, 'fixture/serial.js')]) - .then(result => { + .then(runStatus => { t.ok(api.options.serial); - t.is(result.passCount, 3); - t.is(result.failCount, 0); - }); -}); - -test('circular references on assertions do not break process.send', t => { - const api = apiCreator(); - - return api.run([path.join(__dirname, 'fixture/circular-reference-on-assertion.js')]) - .then(result => { - t.is(result.failCount, 1); + t.is(runStatus.stats.passedTests, 3); + t.is(runStatus.stats.failedTests, 0); }); }); @@ -444,8 +320,8 @@ test('run from package.json folder by default', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/process-cwd-default.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -454,54 +330,8 @@ test('control worker\'s process.cwd() with projectDir option', t => { const api = apiCreator({projectDir: path.dirname(fullPath)}); return api.run([fullPath]) - .then(result => { - t.is(result.passCount, 1); - }); -}); - -test('unhandled promises will throw an error', t => { - t.plan(3); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.is(data.name, 'Error'); - t.match(data.message, /You can't handle this!/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/loud-rejection.js')]) - .then(result => { - t.is(result.passCount, 1); - }); -}); - -test('uncaught exception will throw an error', t => { - t.plan(3); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.is(data.name, 'Error'); - t.match(data.message, /Can't catch me!/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/uncaught-exception.js')]) - .then(result => { - t.is(result.passCount, 1); - }); -}); - -test('errors can occur without messages', t => { - const api = apiCreator(); - - return api.run([path.join(__dirname, 'fixture/error-without-message.js')]) - .then(result => { - t.is(result.failCount, 1); - t.is(result.errors.length, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -512,17 +342,19 @@ test('stack traces for exceptions are corrected using a source map file', t => { cacheEnabled: true }); - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.match(data.message, /Thrown by source-map-fixtures/); - t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); - t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:4.*$/m); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'uncaught-exception') { + t.match(evt.err.message, /Thrown by source-map-fixtures/); + t.match(evt.err.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); + t.match(evt.err.stack, /^.*?Immediate\b.*source-map-file.js:4.*$/m); + } }); }); return api.run([path.join(__dirname, 'fixture/source-map-file.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -533,17 +365,19 @@ test('stack traces for exceptions are corrected using a source map file in what cacheEnabled: true }); - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.match(data.message, /Thrown by source-map-fixtures/); - t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); - t.match(data.stack, /^.*?Immediate\b.*source-map-file-browser-env.js:7.*$/m); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'uncaught-exception') { + t.match(evt.err.message, /Thrown by source-map-fixtures/); + t.match(evt.err.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); + t.match(evt.err.stack, /^.*?Immediate\b.*source-map-file-browser-env.js:7.*$/m); + } }); }); return api.run([path.join(__dirname, 'fixture/source-map-file-browser-env.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -571,13 +405,21 @@ test('enhanced assertion formatting necessary whitespace and empty strings', t = t.plan(14); const api = apiCreator(); + const errors = []; + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-failed') { + errors.push(evt.err); + } + }); + }); return api.run([path.join(__dirname, 'fixture/enhanced-assertion-formatting.js')]) - .then(result => { - t.is(result.errors.length, 3); - t.is(result.passCount, 0); + .then(runStatus => { + t.is(errors.length, 3); + t.is(runStatus.stats.passedTests, 0); - result.errors.forEach((error, errorIndex) => { - error.error.statements.forEach((statement, statementIndex) => { + errors.forEach((error, errorIndex) => { + error.statements.forEach((statement, statementIndex) => { t.match(statement[0], expected[errorIndex][statementIndex]); }); }); @@ -591,17 +433,19 @@ test('stack traces for exceptions are corrected using a source map file (cache o cacheEnabled: false }); - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.match(data.message, /Thrown by source-map-fixtures/); - t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); - t.match(data.stack, /^.*?Immediate\b.*source-map-file.js:4.*$/m); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'uncaught-exception') { + t.match(evt.err.message, /Thrown by source-map-fixtures/); + t.match(evt.err.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); + t.match(evt.err.stack, /^.*?Immediate\b.*source-map-file.js:4.*$/m); + } }); }); return api.run([path.join(__dirname, 'fixture/source-map-file.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -612,17 +456,19 @@ test('stack traces for exceptions are corrected using a source map, taking an in cacheEnabled: true }); - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.match(data.message, /Thrown by source-map-fixtures/); - t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); - t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'uncaught-exception') { + t.match(evt.err.message, /Thrown by source-map-fixtures/); + t.match(evt.err.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); + t.match(evt.err.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m); + } }); }); return api.run([path.join(__dirname, 'fixture/source-map-initial.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -633,17 +479,19 @@ test('stack traces for exceptions are corrected using a source map, taking an in cacheEnabled: false }); - api.on('test-run', runStatus => { - runStatus.on('error', data => { - t.match(data.message, /Thrown by source-map-fixtures/); - t.match(data.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); - t.match(data.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'uncaught-exception') { + t.match(evt.err.message, /Thrown by source-map-fixtures/); + t.match(evt.err.stack, /^.*?Object\.t.*?as run\b.*source-map-fixtures.src.throws.js:1.*$/m); + t.match(evt.err.stack, /^.*?Immediate\b.*source-map-initial-input.js:14.*$/m); + } }); }); return api.run([path.join(__dirname, 'fixture/source-map-initial.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -651,8 +499,8 @@ test('absolute paths', t => { const api = apiCreator(); return api.run([path.resolve('test/fixture/es2015.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -660,8 +508,8 @@ test('symlink to directory containing test files', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/symlink')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -669,8 +517,8 @@ test('symlink to test file directly', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/symlinkfile.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -678,142 +526,40 @@ test('search directories recursively for files', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/subdir')]) - .then(result => { - t.is(result.passCount, 2); - t.is(result.failCount, 1); - }); -}); - -test('titles of both passing and failing tests and AssertionErrors are returned', t => { - const api = apiCreator(); - - return api.run([path.join(__dirname, 'fixture/one-pass-one-fail.js')]) - .then(result => { - t.match(result.errors[0].title, /this is a failing test/); - t.match(result.tests[0].title, /this is a passing test/); - t.match(result.errors[0].error.name, /AssertionError/); - }); -}); - -test('empty test files cause an AvaError to be emitted', t => { - t.plan(2); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /No tests found.*?import "ava"/); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); + t.is(runStatus.stats.failedTests, 1); }); - }); - - return api.run([path.join(__dirname, 'fixture/empty.js')]); -}); - -test('test file with no tests causes an AvaError to be emitted', t => { - t.plan(2); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /No tests/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/no-tests.js')]); -}); - -test('test file that immediately exits with 0 exit code', t => { - t.plan(2); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Test results were not received from/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/immediate-0-exit.js')]); -}); - -test('test file that immediately exits with 3 exit code', t => { - t.plan(3); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.is(err.file, path.join('test', 'fixture', 'immediate-3-exit.js')); - t.match(err.message, /exited with a non-zero exit code: 3/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/immediate-3-exit.js')]); -}); - -test('testing nonexistent files causes an AvaError to be emitted', t => { - t.plan(2); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Couldn't find any files to test/); - }); - }); - - return api.run([path.join(__dirname, 'fixture/broken.js')]); }); test('test file in node_modules is ignored', t => { - t.plan(2); + t.plan(1); const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Couldn't find any files to test/); + return api.run([path.join(__dirname, 'fixture/ignored-dirs/node_modules/test.js')]) + .then(runStatus => { + t.is(runStatus.stats.declaredTests, 0); }); - }); - - return api.run([path.join(__dirname, 'fixture/ignored-dirs/node_modules/test.js')]); }); test('test file in fixtures is ignored', t => { - t.plan(2); + t.plan(1); const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Couldn't find any files to test/); + return api.run([path.join(__dirname, 'fixture/ignored-dirs/fixtures/test.js')]) + .then(runStatus => { + t.is(runStatus.stats.declaredTests, 0); }); - }); - - return api.run([path.join(__dirname, 'fixture/ignored-dirs/fixtures/test.js')]); }); test('test file in helpers is ignored', t => { - t.plan(2); + t.plan(1); const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Couldn't find any files to test/); + return api.run([path.join(__dirname, 'fixture/ignored-dirs/helpers/test.js')]) + .then(runStatus => { + t.is(runStatus.stats.declaredTests, 0); }); - }); - - return api.run([path.join(__dirname, 'fixture/ignored-dirs/helpers/test.js')]); }); test('Node.js-style --require CLI argument', t => { @@ -824,8 +570,8 @@ test('Node.js-style --require CLI argument', t => { }); return api.run([path.join(__dirname, 'fixture/validate-installed-global.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -884,9 +630,10 @@ test('test file with only skipped tests does not create a failure', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/skip-only.js')]) - .then(result => { - t.is(result.tests.length, 1); - t.true(result.tests[0].skip); + .then(runStatus => { + t.is(runStatus.stats.selectedTests, 1); + t.is(runStatus.stats.skippedTests, 1); + t.is(runStatus.stats.failedTests, 0); }); }); @@ -894,25 +641,15 @@ test('test file with only skipped tests does not run hooks', t => { const api = apiCreator(); return api.run([path.join(__dirname, 'fixture/hooks-skipped.js')]) - .then(result => { - t.is(result.tests.length, 1); - t.is(result.skipCount, 1); - t.is(result.passCount, 0); - t.is(result.failCount, 0); + .then(runStatus => { + t.is(runStatus.stats.selectedTests, 1); + t.is(runStatus.stats.skippedTests, 1); + t.is(runStatus.stats.passedTests, 0); + t.is(runStatus.stats.failedTests, 0); + t.is(runStatus.stats.failedHooks, 0); }); }); -test('resets state before running', t => { - const api = apiCreator(); - - return api.run([path.resolve('test/fixture/es2015.js')]).then(result => { - t.is(result.passCount, 1); - return api.run([path.resolve('test/fixture/es2015.js')]); - }).then(result => { - t.is(result.passCount, 1); - }); -}); - test('emits dependencies for test files', t => { t.plan(8); @@ -921,10 +658,10 @@ test('emits dependencies for test files', t => { }); const testFiles = [ - path.normalize('test/fixture/with-dependencies/no-tests.js'), - path.normalize('test/fixture/with-dependencies/test.js'), - path.normalize('test/fixture/with-dependencies/test-failure.js'), - path.normalize('test/fixture/with-dependencies/test-uncaught-exception.js') + path.resolve('test/fixture/with-dependencies/no-tests.js'), + path.resolve('test/fixture/with-dependencies/test.js'), + path.resolve('test/fixture/with-dependencies/test-failure.js'), + path.resolve('test/fixture/with-dependencies/test-uncaught-exception.js') ]; const sourceFiles = [ @@ -933,64 +670,32 @@ test('emits dependencies for test files', t => { path.resolve('test/fixture/with-dependencies/dep-3.custom') ]; - api.on('test-run', runStatus => { - runStatus.on('dependencies', (file, dependencies) => { - t.notEqual(testFiles.indexOf(file), -1); - t.strictDeepEqual(dependencies.slice(-3), sourceFiles); - }); - - // The test files are designed to cause errors so ignore them here. - runStatus.on('error', () => {}); - }); - - const result = api.run(['test/fixture/with-dependencies/*test*.js']); - - return result.catch(() => {}); -}); - -test('emits stats for test files', t => { - t.plan(2); - - const api = apiCreator(); - api.on('test-run', runStatus => { - runStatus.on('stats', stats => { - if (stats.file === path.normalize('test/fixture/exclusive.js')) { - t.is(stats.hasExclusive, true); - } else if (stats.file === path.normalize('test/fixture/generators.js')) { - t.is(stats.hasExclusive, false); - } else { - t.ok(false); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'dependencies') { + t.notEqual(testFiles.indexOf(evt.testFile), -1); + t.strictDeepEqual(evt.dependencies.slice(-3), sourceFiles); } }); }); - return api.run([ - 'test/fixture/exclusive.js', - 'test/fixture/generators.js' - ]); + return api.run(['test/fixture/with-dependencies/*test*.js']); }); test('verify test count', t => { - t.plan(8); + t.plan(4); const api = apiCreator(); - api.on('test-run', runStatus => { - t.is(runStatus.passCount, 0); - t.is(runStatus.failCount, 0); - t.is(runStatus.skipCount, 0); - t.is(runStatus.todoCount, 0); - }); - return api.run([ path.join(__dirname, 'fixture/test-count.js'), path.join(__dirname, 'fixture/test-count-2.js'), path.join(__dirname, 'fixture/test-count-3.js') - ]).then(result => { - t.is(result.passCount, 4, 'pass count'); - t.is(result.failCount, 3, 'fail count'); - t.is(result.skipCount, 3, 'skip count'); - t.is(result.todoCount, 3, 'todo count'); + ]).then(runStatus => { + t.is(runStatus.stats.passedTests, 4, 'pass count'); + t.is(runStatus.stats.failedTests, 3, 'fail count'); + t.is(runStatus.stats.skippedTests, 3, 'skip count'); + t.is(runStatus.stats.todoTests, 3, 'todo count'); }); }); @@ -1007,15 +712,17 @@ test('babel.testOptions with a custom plugin', t => { projectDir: __dirname }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.is(data.title, 'FOO'); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.is(evt.title, 'FOO'); + } }); }); return api.run([path.join(__dirname, 'fixture/babelrc/test.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }, t.threw); }); @@ -1026,15 +733,17 @@ test('babel.testOptions.babelrc effectively defaults to true', t => { projectDir: path.join(__dirname, 'fixture/babelrc') }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.ok((data.title === 'foo') || (data.title === 'repeated test: foo')); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.ok((evt.title === 'foo') || (evt.title === 'repeated test: foo')); + } }); }); return api.run() - .then(result => { - t.is(result.passCount, 2); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); }); }); @@ -1049,15 +758,17 @@ test('babel.testOptions.babelrc can explicitly be true', t => { projectDir: path.join(__dirname, 'fixture/babelrc') }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.ok(data.title === 'foo' || data.title === 'repeated test: foo'); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.ok(evt.title === 'foo' || evt.title === 'repeated test: foo'); + } }); }); return api.run() - .then(result => { - t.is(result.passCount, 2); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); }); }); @@ -1072,15 +783,17 @@ test('babel.testOptions.babelrc can explicitly be false', t => { projectDir: path.join(__dirname, 'fixture/babelrc') }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.is(data.title, 'foo'); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.is(evt.title, 'foo'); + } }); }); return api.run() - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); @@ -1098,15 +811,17 @@ test('babelConfig.testOptions merges plugins with .babelrc', t => { projectDir: path.join(__dirname, 'fixture/babelrc') }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.ok(data.title === 'FOO' || data.title === 'repeated test: foo'); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.ok(evt.title === 'FOO' || evt.title === 'repeated test: foo'); + } }); }); return api.run() - .then(result => { - t.is(result.passCount, 2); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); }); }); @@ -1124,55 +839,33 @@ test('babelConfig.testOptions with extends still merges plugins with .babelrc', projectDir: path.join(__dirname, 'fixture/babelrc') }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.ok(data.title === 'BAR' || data.title === 'repeated test: bar'); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.ok(evt.title === 'BAR' || evt.title === 'repeated test: bar'); + } }); }); return api.run() - .then(result => { - t.is(result.passCount, 2); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 2); }); }); -test('using --match with no matching tests causes an AvaError to be emitted', t => { - t.plan(2); - - const api = apiCreator({ - match: ['can\'t match this'] - }); - - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.fail(`Unexpected test run: ${data.title}`); - }); - runStatus.on('error', err => { - t.is(err.name, 'AvaError'); - t.match(err.message, /Couldn't find any matching tests/); - }); - }); - - return api.run([ - path.join(__dirname, 'fixture/match-no-match.js'), - path.join(__dirname, 'fixture/match-no-match-2.js'), - path.join(__dirname, 'fixture/test-count.js') - ]); -}); - test('using --match with matching tests will only report those passing tests', t => { - t.plan(2); + t.plan(3); const api = apiCreator({ match: ['this test will match'] }); - api.on('test-run', runStatus => { - runStatus.on('test', data => { - t.match(data.title, /^match-no-match-2 .+ this test will match$/); - }); - runStatus.on('error', err => { - t.fail(`Unexpected failure: ${err}`); + api.on('run', plan => { + plan.status.on('stateChange', evt => { + if (evt.type === 'selected-test') { + t.match(evt.testFile, /match-no-match-2/); + t.is(evt.title, 'this test will match'); + } }); }); @@ -1180,32 +873,11 @@ test('using --match with matching tests will only report those passing tests', t path.join(__dirname, 'fixture/match-no-match.js'), path.join(__dirname, 'fixture/match-no-match-2.js'), path.join(__dirname, 'fixture/test-count.js') - ]).then(result => { - t.is(result.passCount, 1); - }).catch(() => { - t.fail(); + ]).then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); -test('errors thrown when running files are emitted', t => { - t.plan(3); - - const api = apiCreator(); - - api.on('test-run', runStatus => { - runStatus.on('error', err => { - t.is(err.name, 'SyntaxError'); - t.is(err.file, path.join('test', 'fixture', 'syntax-error.js')); - t.match(err.message, /Unexpected token/); - }); - }); - - return api.run([ - path.join(__dirname, 'fixture/es2015.js'), - path.join(__dirname, 'fixture/syntax-error.js') - ]); -}); - function generatePassDebugTests(execArgv, expectedInspectIndex) { test(`pass ${execArgv.join(' ')} to fork`, t => { const api = apiCreator({testOnlyExecArgv: execArgv}); @@ -1225,8 +897,8 @@ function generatePassDebugIntegrationTests(execArgv) { test(`pass ${execArgv.join(' ')} to fork`, t => { const api = apiCreator({testOnlyExecArgv: execArgv}); return api.run([path.join(__dirname, 'fixture/debug-arg.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); } @@ -1235,8 +907,8 @@ function generatePassInspectIntegrationTests(execArgv) { test(`pass ${execArgv.join(' ')} to fork`, t => { const api = apiCreator({testOnlyExecArgv: execArgv}); return api.run([path.join(__dirname, 'fixture/inspect-arg.js')]) - .then(result => { - t.is(result.passCount, 1); + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); }); }); } @@ -1262,3 +934,14 @@ if (Number(process.version.split('.')[0].slice(1)) < 8) { generatePassInspectIntegrationTests(['--inspect=9229']); generatePassInspectIntegrationTests(['--inspect']); } + +test('`esm` package support', t => { + const api = apiCreator({ + require: [require.resolve('esm')] + }); + + return api.run([path.join(__dirname, 'fixture/esm-pkg/test.js')]) + .then(runStatus => { + t.is(runStatus.stats.passedTests, 1); + }); +}); diff --git a/test/cli.js b/test/cli.js index 64c933255..eb29316f1 100644 --- a/test/cli.js +++ b/test/cli.js @@ -34,7 +34,8 @@ function execCli(args, opts, cb) { const processPromise = new Promise(resolve => { child = childProcess.spawn(process.execPath, [cliPath].concat(args), { cwd: dirname, - env, + env: Object.assign({CI: '1'}, env), // Force CI to ensure the correct reporter is selected + // env, stdio: [null, 'pipe', 'pipe'] }); @@ -84,117 +85,117 @@ test('enabling long stack traces will provide detailed debug information', t => }); test('`AssertionError` should capture infinity stack trace', t => { - execCli('fixture/infinity-stack-trace.js', (err, stdout, stderr) => { + execCli('fixture/infinity-stack-trace.js', (err, stdout) => { t.ok(err); - t.match(stderr, /c \(.+?infinity-stack-trace\.js:6:20\)/); - t.match(stderr, /b \(.+?infinity-stack-trace\.js:7:18\)/); - t.match(stderr, /a \(.+?infinity-stack-trace\.js:8:18\)/); + t.match(stdout, /c \(.+?infinity-stack-trace\.js:6:20\)/); + t.match(stdout, /b \(.+?infinity-stack-trace\.js:7:18\)/); + t.match(stdout, /a \(.+?infinity-stack-trace\.js:8:18\)/); t.end(); }); }); test('timeout', t => { - execCli(['fixture/long-running.js', '-T', '1s'], (err, stdout, stderr) => { + execCli(['fixture/long-running.js', '-T', '1s'], (err, stdout) => { t.ok(err); - t.match(stderr, /Exited because no new tests completed within the last 1000ms of inactivity/); + t.match(stdout, /Exited because no new tests completed within the last 1000ms of inactivity/); t.end(); }); }); test('include anonymous functions in error reports', t => { - execCli('fixture/error-in-anonymous-function.js', (err, stdout, stderr) => { + execCli('fixture/error-in-anonymous-function.js', (err, stdout) => { t.ok(err); - t.match(stderr, /test\/fixture\/error-in-anonymous-function\.js:4:8/); + t.match(stdout, /test\/fixture\/error-in-anonymous-function\.js:4:8/); t.end(); }); }); test('improper use of t.throws will be reported to the console', t => { - execCli('fixture/improper-t-throws/throws.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/throws.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws from within a Promise will be reported to the console', t => { - execCli('fixture/improper-t-throws/promise.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/promise.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws from within a pending promise, even if caught and rethrown immediately, will be reported to the console', t => { - execCli('fixture/improper-t-throws/leaked-from-promise.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/leaked-from-promise.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws from within an async callback will be reported to the console', t => { - execCli('fixture/improper-t-throws/async-callback.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/async-callback.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws, swallowed as an unhandled rejection, will be reported to the console', t => { - execCli('fixture/improper-t-throws/unhandled-rejection.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/unhandled-rejection.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws, even if caught, will be reported to the console', t => { - execCli('fixture/improper-t-throws/caught.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/caught.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.notMatch(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.notMatch(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws, even if caught and then rethrown immediately, will be reported to the console', t => { - execCli('fixture/improper-t-throws/caught-and-leaked.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/caught-and-leaked.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws, even if caught and then later rethrown, will be reported to the console', t => { - execCli('fixture/improper-t-throws/caught-and-leaked-slowly.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/caught-and-leaked-slowly.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.match(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.match(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); test('improper use of t.throws, even if caught and then rethrown too slowly, will be reported to the console', t => { - execCli('fixture/improper-t-throws/caught-and-leaked-too-slowly.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/caught-and-leaked-too-slowly.js', (err, stdout) => { t.ok(err); - t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); - t.notMatch(stderr, /should be detected/); - t.match(stderr, /Try wrapping the first argument/); + t.match(stdout, /Improper usage of `t\.throws\(\)` detected/); + t.notMatch(stdout, /should be detected/); + t.match(stdout, /Try wrapping the first argument/); t.end(); }); }); @@ -202,42 +203,42 @@ test('improper use of t.throws, even if caught and then rethrown too slowly, wil test('precompiler require hook does not apply to source files', t => { t.plan(3); - execCli('fixture/babel-hook.js', (err, stdout, stderr) => { + execCli('fixture/babel-hook.js', (err, stdout) => { t.ok(err); t.is(err.code, 1); - t.match(stderr, /Unexpected (token|reserved word)/); + t.match(stdout, /Unexpected (token|reserved word)/); t.end(); }); }); test('pkg-conf(resolve-dir): works as expected when run from the package.json directory', t => { - execCli(['--verbose'], {dirname: 'fixture/pkg-conf/resolve-dir'}, (err, stdout, stderr) => { + execCli(['--verbose'], {dirname: 'fixture/pkg-conf/resolve-dir'}, (err, stdout) => { t.ifError(err); - t.match(stderr, /dir-a-base-1/); - t.match(stderr, /dir-a-base-2/); - t.notMatch(stderr, /dir-a-wrapper/); + t.match(stdout, /dir-a-base-1/); + t.match(stdout, /dir-a-base-2/); + t.notMatch(stdout, /dir-a-wrapper/); t.notMatch(stdout, /dir-a-wrapper/); t.end(); }); }); test('pkg-conf(resolve-dir): resolves tests from the package.json dir if none are specified on cli', t => { - execCli(['--verbose'], {dirname: 'fixture/pkg-conf/resolve-dir/dir-a-wrapper'}, (err, stdout, stderr) => { + execCli(['--verbose'], {dirname: 'fixture/pkg-conf/resolve-dir/dir-a-wrapper'}, (err, stdout) => { t.ifError(err); - t.match(stderr, /dir-a-base-1/); - t.match(stderr, /dir-a-base-2/); - t.notMatch(stderr, /dir-a-wrapper/); + t.match(stdout, /dir-a-base-1/); + t.match(stdout, /dir-a-base-2/); + t.notMatch(stdout, /dir-a-wrapper/); t.notMatch(stdout, /dir-a-wrapper/); t.end(); }); }); test('pkg-conf(resolve-dir): resolves tests process.cwd() if globs are passed on the command line', t => { - execCli(['--verbose', 'dir-a/*.js'], {dirname: 'fixture/pkg-conf/resolve-dir/dir-a-wrapper'}, (err, stdout, stderr) => { + execCli(['--verbose', 'dir-a/*.js'], {dirname: 'fixture/pkg-conf/resolve-dir/dir-a-wrapper'}, (err, stdout) => { t.ifError(err); - t.match(stderr, /dir-a-wrapper-3/); - t.match(stderr, /dir-a-wrapper-4/); - t.notMatch(stderr, /dir-a-base/); + t.match(stdout, /dir-a-wrapper-3/); + t.match(stdout, /dir-a-wrapper-4/); + t.notMatch(stdout, /dir-a-base/); t.notMatch(stdout, /dir-a-base/); t.end(); }); @@ -246,7 +247,7 @@ test('pkg-conf(resolve-dir): resolves tests process.cwd() if globs are passed on test('watcher reruns test files when they changed', t => { let killed = false; - const child = execCli(['--verbose', '--watch', 'test.js'], {dirname: 'fixture/watcher'}, err => { + const child = execCli(['--verbose', '--watch', 'test.js'], {dirname: 'fixture/watcher', env: {CI: ''}}, err => { t.ok(killed); t.ifError(err); t.end(); @@ -254,7 +255,7 @@ test('watcher reruns test files when they changed', t => { let buffer = ''; let passedFirst = false; - child.stderr.on('data', str => { + child.stdout.on('data', str => { buffer += str; if (/1 test passed/.test(buffer)) { if (!passedFirst) { @@ -272,7 +273,7 @@ test('watcher reruns test files when they changed', t => { test('watcher reruns test files when source dependencies change', t => { let killed = false; - const child = execCli(['--verbose', '--watch', 'test-*.js'], {dirname: 'fixture/watcher/with-dependencies'}, err => { + const child = execCli(['--verbose', '--watch', 'test-*.js'], {dirname: 'fixture/watcher/with-dependencies', env: {CI: ''}}, err => { t.ok(killed); t.ifError(err); t.end(); @@ -280,7 +281,7 @@ test('watcher reruns test files when source dependencies change', t => { let buffer = ''; let passedFirst = false; - child.stderr.on('data', str => { + child.stdout.on('data', str => { buffer += str; if (/2 tests passed/.test(buffer) && !passedFirst) { touch.sync(path.join(__dirname, 'fixture/watcher/with-dependencies/source.js')); @@ -296,7 +297,7 @@ test('watcher reruns test files when source dependencies change', t => { test('watcher does not rerun test files when they write snapshot files', t => { let killed = false; - const child = execCli(['--verbose', '--watch', '--update-snapshots', 'test.js'], {dirname: 'fixture/snapshots'}, err => { + const child = execCli(['--verbose', '--watch', '--update-snapshots', 'test.js'], {dirname: 'fixture/snapshots', env: {CI: ''}}, err => { t.ok(killed); t.ifError(err); t.end(); @@ -304,7 +305,7 @@ test('watcher does not rerun test files when they write snapshot files', t => { let buffer = ''; let passedFirst = false; - child.stderr.on('data', str => { + child.stdout.on('data', str => { buffer += str; if (/2 tests passed/.test(buffer) && !passedFirst) { buffer = ''; @@ -322,7 +323,7 @@ test('watcher does not rerun test files when they write snapshot files', t => { test('watcher reruns test files when snapshot dependencies change', t => { let killed = false; - const child = execCli(['--verbose', '--watch', '--update-snapshots', 'test.js'], {dirname: 'fixture/snapshots'}, err => { + const child = execCli(['--verbose', '--watch', '--update-snapshots', 'test.js'], {dirname: 'fixture/snapshots', env: {CI: ''}}, err => { t.ok(killed); t.ifError(err); t.end(); @@ -330,7 +331,7 @@ test('watcher reruns test files when snapshot dependencies change', t => { let buffer = ''; let passedFirst = false; - child.stderr.on('data', str => { + child.stdout.on('data', str => { buffer += str; if (/2 tests passed/.test(buffer)) { buffer = ''; @@ -350,7 +351,7 @@ test('watcher reruns test files when snapshot dependencies change', t => { test('`"tap": true` config is ignored when --watch is given', t => { let killed = false; - const child = execCli(['--watch', '--verbose', 'test.js'], {dirname: 'fixture/watcher/tap-in-conf'}, () => { + const child = execCli(['--watch', '--verbose', 'test.js'], {dirname: 'fixture/watcher/tap-in-conf', env: {CI: ''}}, () => { t.ok(killed); t.end(); }); @@ -371,7 +372,7 @@ test('`"tap": true` config is ignored when --watch is given', t => { ['--watch', '-w'].forEach(watchFlag => { ['--tap', '-t'].forEach(tapFlag => { test(`bails when ${tapFlag} reporter is used while ${watchFlag} is given`, t => { - execCli([tapFlag, watchFlag, 'test.js'], {dirname: 'fixture/watcher'}, (err, stdout, stderr) => { + execCli([tapFlag, watchFlag, 'test.js'], {dirname: 'fixture/watcher', env: {CI: ''}}, (err, stdout, stderr) => { t.is(err.code, 1); t.match(stderr, 'The TAP reporter is not available when using watch mode.'); t.end(); @@ -465,9 +466,9 @@ test('handles NODE_PATH', t => { }); test('works when no files are found', t => { - execCli('!*', (err, stdout, stderr) => { + execCli('!*', (err, stdout) => { t.is(err.code, 1); - t.match(stderr, 'Couldn\'t find any files to test'); + t.match(stdout, 'Couldn\'t find any files to test'); t.end(); }); }); @@ -501,7 +502,7 @@ test('use current working directory if `package.json` is not found', () => { fs.writeFileSync(testFilePath, `import test from ${JSON.stringify(avaPath)};\ntest('test', t => { t.pass(); });`); - return execa(process.execPath, [cliPath], {cwd}); + return execa(process.execPath, [cliPath], {cwd, env: {CI: '1'}}); }); test('workers ensure test files load the same version of ava', t => { @@ -522,20 +523,6 @@ test('workers ensure test files load the same version of ava', t => { }); }); -test('worker errors are treated as uncaught exceptions', t => { - execCli(['--no-color', '--verbose', 'test.js'], {dirname: 'fixture/trigger-worker-exception'}, (_, __, stderr) => { - t.match(stderr, /Forced error/); - t.end(); - }); -}); - -test('uncaught exceptions are raised for worker errors even if the error cannot be serialized', t => { - execCli(['--no-color', '--verbose', 'test-fallback.js'], {dirname: 'fixture/trigger-worker-exception'}, (_, __, stderr) => { - t.match(stderr, /Failed to serialize uncaught exception/); - t.end(); - }); -}); - test('tests without assertions do not fail if failWithoutAssertions option is set to false', t => { execCli([], {dirname: 'fixture/pkg-conf/fail-without-assertions'}, err => { t.ifError(err); @@ -544,22 +531,22 @@ test('tests without assertions do not fail if failWithoutAssertions option is se }); test('callback tests fail if event loop empties before they\'re ended', t => { - execCli('callback.js', {dirname: 'fixture/stalled-tests'}, (_, __, stderr) => { - t.match(stderr, /`t\.end\(\)` was never called/); + execCli('callback.js', {dirname: 'fixture/stalled-tests'}, (_, stdout) => { + t.match(stdout, /`t\.end\(\)` was never called/); t.end(); }); }); test('observable tests fail if event loop empties before they\'re resolved', t => { - execCli('observable.js', {dirname: 'fixture/stalled-tests'}, (_, __, stderr) => { - t.match(stderr, /Observable returned by test never completed/); + execCli('observable.js', {dirname: 'fixture/stalled-tests'}, (_, stdout) => { + t.match(stdout, /Observable returned by test never completed/); t.end(); }); }); test('promise tests fail if event loop empties before they\'re resolved', t => { - execCli('promise.js', {dirname: 'fixture/stalled-tests'}, (_, __, stderr) => { - t.match(stderr, /Promise returned by test never resolved/); + execCli('promise.js', {dirname: 'fixture/stalled-tests'}, (_, stdout) => { + t.match(stdout, /Promise returned by test never resolved/); t.end(); }); }); @@ -608,9 +595,9 @@ test('one', t => { })`; fs.writeFileSync(path.join(cwd, 'test.js'), initial); - const run = () => execa(process.execPath, [cliPath, '--verbose', '--no-color'], {cwd, reject: false}); + const run = () => execa(process.execPath, [cliPath, '--verbose', '--no-color'], {cwd, env: {CI: '1'}, reject: false}); return run().then(result => { - t.match(result.stderr, /1 test passed/); + t.match(result.stdout, /1 test passed/); fs.writeFileSync(path.join(cwd, 'test.js'), `${initial} test('two', t => { @@ -618,7 +605,7 @@ test('two', t => { })`); return run(); }).then(result => { - t.match(result.stderr, /2 tests passed/); + t.match(result.stdout, /2 tests passed/); fs.writeFileSync(path.join(cwd, 'test.js'), `${initial} test('two', t => { @@ -627,7 +614,7 @@ test('two', t => { return run(); }).then(result => { - t.match(result.stderr, /1 test failed/); + t.match(result.stdout, /1 test failed/); }); }); @@ -635,12 +622,12 @@ test('outdated snapshot version is reported to the console', t => { const snapPath = path.join(__dirname, 'fixture', 'snapshots', 'test.js.snap'); fs.writeFileSync(snapPath, Buffer.from([0x0A, 0x00, 0x00])); - execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout, stderr) => { + execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout) => { t.ok(err); - t.match(stderr, /The snapshot file is v0, but only v1 is supported\./); - t.match(stderr, /File path:/); - t.match(stderr, snapPath); - t.match(stderr, /Please run AVA again with the .*--update-snapshots.* flag to upgrade\./); + t.match(stdout, /The snapshot file is v0, but only v1 is supported\./); + t.match(stdout, /File path:/); + t.match(stdout, snapPath); + t.match(stdout, /Please run AVA again with the .*--update-snapshots.* flag to upgrade\./); t.end(); }); }); @@ -649,12 +636,12 @@ test('newer snapshot version is reported to the console', t => { const snapPath = path.join(__dirname, 'fixture', 'snapshots', 'test.js.snap'); fs.writeFileSync(snapPath, Buffer.from([0x0A, 0xFF, 0xFF])); - execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout, stderr) => { + execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout) => { t.ok(err); - t.match(stderr, /The snapshot file is v65535, but only v1 is supported\./); - t.match(stderr, /File path:/); - t.match(stderr, snapPath); - t.match(stderr, /You should upgrade AVA\./); + t.match(stdout, /The snapshot file is v65535, but only v1 is supported\./); + t.match(stdout, /File path:/); + t.match(stdout, snapPath); + t.match(stdout, /You should upgrade AVA\./); t.end(); }); }); @@ -663,12 +650,12 @@ test('snapshot corruption is reported to the console', t => { const snapPath = path.join(__dirname, 'fixture', 'snapshots', 'test.js.snap'); fs.writeFileSync(snapPath, Buffer.from([0x0A, 0x01, 0x00])); - execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout, stderr) => { + execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout) => { t.ok(err); - t.match(stderr, /The snapshot file is corrupted\./); - t.match(stderr, /File path:/); - t.match(stderr, snapPath); - t.match(stderr, /Please run AVA again with the .*--update-snapshots.* flag to recreate it\./); + t.match(stdout, /The snapshot file is corrupted\./); + t.match(stdout, /File path:/); + t.match(stdout, snapPath); + t.match(stdout, /Please run AVA again with the .*--update-snapshots.* flag to recreate it\./); t.end(); }); }); @@ -677,12 +664,12 @@ test('legacy snapshot files are reported to the console', t => { const snapPath = path.join(__dirname, 'fixture', 'snapshots', 'test.js.snap'); fs.writeFileSync(snapPath, Buffer.from('// Jest Snapshot v1, https://goo.gl/fbAQLP\n')); - execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout, stderr) => { + execCli(['test.js'], {dirname: 'fixture/snapshots'}, (err, stdout) => { t.ok(err); - t.match(stderr, /The snapshot file was created with AVA 0\.19\. It's not supported by this AVA version\./); - t.match(stderr, /File path:/); - t.match(stderr, snapPath); - t.match(stderr, /Please run AVA again with the .*--update-snapshots.* flag to upgrade\./); + t.match(stdout, /The snapshot file was created with AVA 0\.19\. It's not supported by this AVA version\./); + t.match(stdout, /File path:/); + t.match(stdout, snapPath); + t.match(stdout, /Please run AVA again with the .*--update-snapshots.* flag to upgrade\./); t.end(); }); }); @@ -717,10 +704,10 @@ test('snapshots infer their location from sourcemaps', t => { const verifySnapFixtureFiles = relFilePath => { t.true(fs.existsSync(relFilePath)); }; - execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => { + execCli([], {dirname: relativeFixtureDir}, (err, stdout) => { t.ifError(err); snapFixtureFilePaths.forEach(x => verifySnapFixtureFiles(x)); - t.match(stderr, /6 passed/); + t.match(stdout, /6 tests passed/); t.end(); }); }); @@ -756,82 +743,82 @@ test('snapshots resolved location from "snapshotDir" in AVA config', t => { const verifySnapFixtureFiles = relFilePath => { t.true(fs.existsSync(relFilePath)); }; - execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => { + execCli([], {dirname: relativeFixtureDir}, (err, stdout) => { t.ifError(err); snapFixtureFilePaths.forEach(x => verifySnapFixtureFiles(x)); - t.match(stderr, /6 passed/); + t.match(stdout, /6 tests passed/); t.end(); }); }); test('--no-color disables formatting colors', t => { - execCli(['--no-color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout, stderr) => { + execCli(['--no-color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout) => { t.ok(err); - t.is(stripAnsi(stderr), stderr); + t.is(stripAnsi(stdout), stdout); t.end(); }); }); test('--color enables formatting colors', t => { - execCli(['--color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout, stderr) => { + execCli(['--color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout) => { t.ok(err); - t.isNot(stripAnsi(stderr), stderr); + t.isNot(stripAnsi(stdout), stdout); t.end(); }); }); test('sets NODE_ENV to test when it is not set', t => { - execCli([path.join('fixture', 'node-env-test.js')], {env: {}}, (err, stdout, stderr) => { + execCli([path.join('fixture', 'node-env-test.js')], {env: {}}, (err, stdout) => { t.ifError(err); - t.match(stderr, /1 passed/); + t.match(stdout, /1 test passed/); t.end(); }); }); test('doesn\'t set NODE_ENV when it is set', t => { - execCli([path.join('fixture', 'node-env-foo.js')], {env: {NODE_ENV: 'foo'}}, (err, stdout, stderr) => { + execCli([path.join('fixture', 'node-env-foo.js')], {env: {NODE_ENV: 'foo'}}, (err, stdout) => { t.ifError(err); - t.match(stderr, /1 passed/); + t.match(stdout, /1 test passed/); t.end(); }); }); test('skips test file compilation when babel=false and compileEnhancements=false', t => { - execCli(['import.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout, stderr) => { + execCli(['import.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout) => { t.ok(err); - t.match(stderr, /SyntaxError: Unexpected (reserved word|token import)/); + t.match(stdout, /SyntaxError: Unexpected (reserved word|token import)/); t.end(); }); }); test('skips helper file compilation when babel=false and compileEnhancements=false', t => { - execCli(['require-helper.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout, stderr) => { + execCli(['require-helper.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout) => { t.ifError(err); - t.match(stderr, /1 passed/); + t.match(stdout, /1 test passed/); t.end(); }); }); test('no power-assert when babel=false and compileEnhancements=false', t => { - execCli(['no-power-assert.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout, stderr) => { + execCli(['no-power-assert.js'], {dirname: 'fixture/no-babel-compilation'}, (err, stdout) => { t.ok(err); - t.notMatch(stripAnsi(stderr), /bool\n.*=> false/); + t.notMatch(stripAnsi(stdout), /bool\n.*=> false/); t.end(); }); }); test('skips stage-4 transform when babel=false and compileEnhancements=true', t => { - execCli(['import.js'], {dirname: 'fixture/just-enhancement-compilation'}, (err, stdout, stderr) => { + execCli(['import.js'], {dirname: 'fixture/just-enhancement-compilation'}, (err, stdout) => { t.ok(err); - t.match(stderr, /SyntaxError: Unexpected (reserved word|token import)/); + t.match(stdout, /SyntaxError: Unexpected (reserved word|token import)/); t.end(); }); }); test('power-assert when babel=false and compileEnhancements=true', t => { - execCli(['power-assert.js'], {dirname: 'fixture/just-enhancement-compilation'}, (err, stdout, stderr) => { + execCli(['power-assert.js'], {dirname: 'fixture/just-enhancement-compilation'}, (err, stdout) => { t.ok(err); - t.match(stripAnsi(stderr), /bool\n.*=> false/); + t.match(stripAnsi(stdout), /bool\n.*=> false/); t.end(); }); }); diff --git a/test/fork.js b/test/fork.js deleted file mode 100644 index 0f280cdae..000000000 --- a/test/fork.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; -const path = require('path'); -const tap = require('tap'); -const _fork = require('../lib/fork.js'); -const CachingPrecompiler = require('../lib/caching-precompiler'); - -const skip = tap.skip; -const test = tap.test; - -const cacheDir = path.join(__dirname, '../node_modules/.cache/ava'); -const isNode4 = /\d+/.exec(process.version)[0] === '4'; -const precompiler = new CachingPrecompiler({ - babelCacheKeys: {}, - getBabelOptions() { - return { - babelrc: false, - presets: [require.resolve('@ava/babel-preset-stage-4')] - }; - }, - path: cacheDir -}); - -function fork(testPath, options) { - const hash = precompiler.precompileFile(testPath); - const precompiled = {}; - precompiled[testPath] = hash; - - return _fork(testPath, Object.assign({ - cacheDir, - precompiled - }, options)); -} - -function fixture(name) { - return path.join(__dirname, 'fixture', name); -} - -test('emits test event', t => { - t.plan(1); - - fork(fixture('generators.js')) - .on('test', tt => { - t.is(tt.title, 'generator function'); - t.end(); - }); -}); - -test('resolves promise with tests info', t => { - t.plan(3); - - const file = fixture('generators.js'); - - return fork(file) - .then(info => { - t.is(info.stats.passCount, 1); - t.is(info.tests.length, 1); - t.is(info.file, path.relative('.', file)); - t.end(); - }); -}); - -test('exit after tests are finished', t => { - t.plan(2); - - const start = Date.now(); - let cleanupCompleted = false; - - fork(fixture('slow-exit.js')) - .on('exit', () => { - t.true(Date.now() - start < 10000, 'test waited for a pending setTimeout'); - t.true(cleanupCompleted, 'cleanup did not complete'); - }) - .on('cleanup-completed', event => { - cleanupCompleted = event.completed; - }); -}); - -test('rejects promise if the process exits with a non-zero code', t => { - return fork(fixture('immediate-3-exit.js')) - .catch(err => { - t.is(err.name, 'AvaError'); - t.is(err.message, path.join('test', 'fixture', 'immediate-3-exit.js') + ' exited with a non-zero exit code: 3'); - }); -}); - -test('rejects promise if the process exits without results', t => { - return fork(fixture('immediate-0-exit.js')) - .catch(err => { - t.is(err.name, 'AvaError'); - t.is(err.message, 'Test results were not received from ' + path.join('test', 'fixture', 'immediate-0-exit.js')); - }); -}); - -test('rejects promise if the process is killed', t => { - const forked = fork(fixture('es2015.js')); - return forked - .on('stats', function () { - this.kill('SIGKILL'); - }) - .catch(err => { - t.is(err.name, 'AvaError'); - t.is(err.message, path.join('test', 'fixture', 'es2015.js') + ' exited due to SIGKILL'); - }); -}); - -test('fake timers do not break duration', t => { - return fork(fixture('fake-timers.js')) - .then(info => { - const duration = info.tests[0].duration; - t.true(duration < 1000, `${duration} < 1000`); - t.is(info.stats.failCount, 0); - t.is(info.stats.passCount, 1); - t.end(); - }); -}); - -test('babelrc is ignored', t => { - return fork(fixture('babelrc/test.js')) - .then(info => { - t.is(info.stats.passCount, 1); - t.end(); - }); -}); - -(isNode4 ? skip : test)( -'`esm` package support', t => { - return fork(fixture('esm-pkg/test.js'), { - require: [require.resolve('esm')] - }) - .then(info => { - t.is(info.stats.passCount, 1); - t.end(); - }); -}); - -// TODO: Skipped until we can do this properly in #1455 -test('color support is initialized correctly', t => { - t.plan(1); - - return Promise.all([ - fork(fixture('chalk-enabled.js'), {color: true}), - fork(fixture('chalk-disabled.js'), {color: false}), - fork(fixture('chalk-disabled.js'), {}) - ]).then(infos => { - for (const info of infos) { - if (info.stats.failCount > 0) { - t.fail(`${info.file} failed`); - } - } - - t.is(infos.length, 3); - }); -}, {skip: true}); diff --git a/test/helper/fix-reporter-env.js b/test/helper/fix-reporter-env.js index 12c195647..43327ba70 100644 --- a/test/helper/fix-reporter-env.js +++ b/test/helper/fix-reporter-env.js @@ -1,4 +1,5 @@ 'use strict'; +const os = require('os'); const lolex = require('lolex'); const fixColors = () => { @@ -16,6 +17,9 @@ module.exports = () => { ] }); + // Fix line endings. + Object.defineProperty(os, 'EOL', {value: '\n'}); + fixColors(); require('../../lib/chalk').set({enabled: true, level: 3}); }; diff --git a/test/helper/report.js b/test/helper/report.js index 82e9197f7..8f6b2ecfd 100644 --- a/test/helper/report.js +++ b/test/helper/report.js @@ -5,7 +5,6 @@ const path = require('path'); const globby = require('globby'); const proxyquire = require('proxyquire'); const replaceString = require('replace-string'); -const Logger = require('../../lib/logger'); let _Api = null; const createApi = options => { @@ -86,7 +85,6 @@ const run = (type, reporter) => { require: [], cacheEnable: true, compileEnhancements: true, - explicitTitles: type === 'watch', match: [], babelConfig: {testOptions: {}}, resolveTestsFrom: projectDir, @@ -98,49 +96,24 @@ const run = (type, reporter) => { color: true }); - reporter.api = api; - const logger = new Logger(reporter); - logger.start(); - - api.on('test-run', runStatus => { - reporter.api = runStatus; - runStatus.on('test', logger.test); - runStatus.on('error', logger.unhandledError); - - runStatus.on('stdout', logger.stdout); - runStatus.on('stderr', logger.stderr); - }); + api.on('run', plan => reporter.startRun(plan)); const files = globby.sync('*.js', {cwd: projectDir}).sort(); if (type !== 'watch') { - return api.run(files).then(runStatus => { - logger.finish(runStatus); + return api.run(files).then(() => { + reporter.endRun(); }); } // Mimick watch mode - return api.run(files).then(runStatus => { - logger.finish(runStatus); - - // Don't clear - logger.reset(); - logger.section(); - logger.reset(); - logger.start(); - - return api.run(files); - }).then(runStatus => { - runStatus.previousFailCount = 2; - logger.finish(runStatus); - - // Clear - logger.clear(); - logger.reset(); - logger.start(); - - return api.run(files); - }).then(runStatus => { - logger.finish(runStatus); + return api.run(files, {clearLogOnNextRun: false, previousFailures: 0, runVector: 1}).then(() => { + reporter.endRun(); + return api.run(files, {clearLogOnNextRun: true, previousFailures: 2, runVector: 2}); + }).then(() => { + reporter.endRun(); + return api.run(files, {clearLogOnNextRun: false, previousFailures: 0, runVector: 3}); + }).then(() => { + reporter.endRun(); }); }; diff --git a/test/helper/tty-stream.js b/test/helper/tty-stream.js index d4e4dc31f..eb3d31706 100644 --- a/test/helper/tty-stream.js +++ b/test/helper/tty-stream.js @@ -1,5 +1,6 @@ 'use strict'; const stream = require('stream'); +const ansiEscapes = require('ansi-escapes'); class TTYStream extends stream.Writable { constructor(options) { @@ -10,9 +11,15 @@ class TTYStream extends stream.Writable { this.sanitizers = options.sanitizers || []; this.chunks = []; + this.spinnerActivity = []; } _write(chunk, encoding, callback) { + if (this.spinnerActivity.length > 0) { + this.chunks.push(Buffer.concat(this.spinnerActivity), TTYStream.SEPARATOR); + this.spinnerActivity = []; + } + const str = this.sanitizers.reduce((str, sanitizer) => sanitizer(str), chunk.toString('utf8')); // Ignore the chunk if it was scrubbed completely. Still count 0-length // chunks. @@ -25,9 +32,33 @@ class TTYStream extends stream.Writable { callback(); } + _writev(chunks, callback) { + if (this.spinnerActivity.length > 0) { + this.chunks.push(Buffer.concat(this.spinnerActivity), TTYStream.SEPARATOR); + this.spinnerActivity = []; + } + for (const obj of chunks) { + this.chunks.push(Buffer.from(this.sanitizers.reduce((str, sanitizer) => sanitizer(str), obj.chunk.toString('utf8')), 'utf8')); + } + this.chunks.push(TTYStream.SEPARATOR); + callback(); + } + asBuffer() { return Buffer.concat(this.chunks); } + + clearLine() { + this.spinnerActivity.push(Buffer.from(ansiEscapes.eraseLine, 'ascii')); + } + + cursorTo(x, y) { + this.spinnerActivity.push(Buffer.from(ansiEscapes.cursorTo(x, y), 'ascii')); + } + + moveCursor(dx, dy) { + this.spinnerActivity.push(Buffer.from(ansiEscapes.cursorMove(dx, dy), 'ascii')); + } } TTYStream.SEPARATOR = Buffer.from('---tty-stream-chunk-separator\n', 'utf8'); diff --git a/test/hooks.js b/test/hooks.js index d5c7c5b58..31e511d84 100644 --- a/test/hooks.js +++ b/test/hooks.js @@ -2,38 +2,12 @@ require('../lib/chalk').set(); require('../lib/worker/options').set({}); -const path = require('path'); const test = require('tap').test; const Runner = require('../lib/runner'); -const _fork = require('../lib/fork.js'); -const CachingPrecompiler = require('../lib/caching-precompiler'); - -const cacheDir = path.join(__dirname, '../node_modules/.cache/ava'); -const precompiler = new CachingPrecompiler({ - babelCacheKeys: {}, - getBabelOptions() { - return { - babelrc: false, - presets: [require.resolve('@ava/babel-preset-stage-4')] - }; - }, - path: cacheDir -}); - -function fork(testPath) { - const hash = precompiler.precompileFile(testPath); - const precompiled = {}; - precompiled[testPath] = hash; - - return _fork(testPath, { - cacheDir, - precompiled - }); -} const promiseEnd = (runner, next) => { return new Promise(resolve => { - runner.on('start', data => resolve(data.ended)); + resolve(runner.once('finish')); next(runner); }).then(() => runner); }; @@ -57,10 +31,16 @@ test('before', t => { }); test('after', t => { - t.plan(3); + t.plan(2); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain.after(() => { arr.push('b'); }); @@ -69,18 +49,22 @@ test('after', t => { a.pass(); arr.push('a'); }); - }).then(runner => { - t.is(runner.stats.passCount, 1); - t.is(runner.stats.failCount, 0); + }).then(() => { t.strictDeepEqual(arr, ['a', 'b']); }); }); test('after not run if test failed', t => { - t.plan(3); + t.plan(2); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-failed') { + t.pass(); + } + }); + runner.chain.after(() => { arr.push('a'); }); @@ -88,18 +72,22 @@ test('after not run if test failed', t => { runner.chain('test', () => { throw new Error('something went wrong'); }); - }).then(runner => { - t.is(runner.stats.passCount, 0); - t.is(runner.stats.failCount, 1); + }).then(() => { t.strictDeepEqual(arr, []); }); }); test('after.always run even if test failed', t => { - t.plan(3); + t.plan(2); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-failed') { + t.pass(); + } + }); + runner.chain.after.always(() => { arr.push('a'); }); @@ -107,9 +95,7 @@ test('after.always run even if test failed', t => { runner.chain('test', () => { throw new Error('something went wrong'); }); - }).then(runner => { - t.is(runner.stats.passCount, 0); - t.is(runner.stats.failCount, 1); + }).then(() => { t.strictDeepEqual(arr, ['a']); }); }); @@ -218,6 +204,12 @@ test('fail if beforeEach hook fails', t => { const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'hook-failed') { + t.pass(); + } + }); + runner.chain.beforeEach(a => { arr.push('a'); a.fail(); @@ -227,8 +219,7 @@ test('fail if beforeEach hook fails', t => { arr.push('b'); a.pass(); }); - }).then(runner => { - t.is(runner.stats.failedHookCount, 1); + }).then(() => { t.strictDeepEqual(arr, ['a']); }); }); @@ -411,9 +402,13 @@ test('ensure hooks run only around tests', t => { }); test('shared context', t => { - t.plan(1); - return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'hook-failed' || evt.type === 'test-failed') { + t.fail(); + } + }); + runner.chain.before(a => { a.deepEqual(a.context, {}); a.context.arr = ['a']; @@ -446,15 +441,17 @@ test('shared context', t => { a.is(a.context.prop, 'test'); a.context.prop = 'afterEach'; }); - }).then(runner => { - t.is(runner.stats.failCount, 0); }); }); test('shared context of any type', t => { - t.plan(1); - return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'hook-failed' || evt.type === 'test-failed') { + t.fail(); + } + }); + runner.chain.beforeEach(a => { a.context = 'foo'; }); @@ -463,27 +460,5 @@ test('shared context of any type', t => { a.pass(); a.is(a.context, 'foo'); }); - }).then(runner => { - t.is(runner.stats.failCount, 0); }); }); - -test('don\'t display hook title if it did not fail', t => { - t.plan(2); - - return fork(path.join(__dirname, 'fixture/hooks-passing.js')) - .on('test', test => { - t.strictDeepEqual(test.error, null); - t.is(test.title, 'pass'); - }); -}); - -test('display hook title if it failed', t => { - t.plan(2); - - return fork(path.join(__dirname, 'fixture/hooks-failing.js')) - .on('test', test => { - t.is(test.error.name, 'AssertionError'); - t.is(test.title, 'beforeEach hook for pass'); - }); -}); diff --git a/test/logger.js b/test/logger.js deleted file mode 100644 index aa4723e1b..000000000 --- a/test/logger.js +++ /dev/null @@ -1,167 +0,0 @@ -'use strict'; -const test = require('tap').test; -const Logger = require('../lib/logger'); -const TapReporter = require('../lib/reporters/tap'); - -test('only call start if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.start = undefined; - logger.start(); - t.end(); -}); - -test('only write if start is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.start = undefined; - logger.write = t.fail; - logger.start(); - t.end(); -}); - -test('only call reset if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.reset = undefined; - logger.reset(); - t.end(); -}); - -test('only write if reset is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.reset = undefined; - logger.write = t.fail; - logger.reset(); - t.end(); -}); - -test('only call section if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.section = undefined; - logger.section(); - t.end(); -}); - -test('only write if section is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.section = undefined; - logger.write = t.fail; - logger.section(); - t.end(); -}); - -test('only call clear if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.clear = undefined; - logger.clear(); - t.end(); -}); - -test('only write if clear is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.clear = undefined; - logger.write = t.fail; - logger.clear(); - t.end(); -}); - -test('return false if clear is not supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.clear = undefined; - t.false(logger.clear()); - t.end(); -}); - -test('return true if clear is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.clear = () => {}; - t.true(logger.clear()); - t.end(); -}); - -test('writes the reporter reset result', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.reset = () => 'test reset'; - logger.write = str => { - t.equal(str, 'test reset'); - t.end(); - }; - logger.reset(); -}); - -test('only call unhandledError if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.unhandledError = undefined; - logger.unhandledError(); - t.end(); -}); - -test('only write if unhandledError is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.unhandledError = undefined; - logger.write = t.fail; - logger.unhandledError(); - t.end(); -}); - -test('only call finish if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.finish = undefined; - logger.finish(); - t.end(); -}); - -test('only write if finish is supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.finish = undefined; - logger.write = t.fail; - logger.finish(); - t.end(); -}); - -test('only call write if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.write = undefined; - logger.write(); - t.end(); -}); - -test('only call stdout if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.stdout = undefined; - logger.stdout(); - t.end(); -}); - -test('don\'t alter data when calling stdout', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.stdout = function (data) { - t.equal(data, 'test data'); - t.end(); - }; - logger.stdout('test data'); -}); - -test('only call stderr if supported by reporter', t => { - const tapReporter = new TapReporter(); - const logger = new Logger(tapReporter); - tapReporter.stderr = undefined; - logger.stderr(); - t.end(); -}); diff --git a/test/reporters/mini.failfast.log b/test/reporters/mini.failfast.log index 6f100294c..f40158c79 100644 --- a/test/reporters/mini.failfast.log +++ b/test/reporters/mini.failfast.log @@ -1,11 +1,14 @@ ----tty-stream-chunk-separator - - a › fails +[?25l---tty-stream-chunk-separator - 1 failed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator +* ---tty-stream-chunk-separator +---tty-stream-chunk-separator +* a › fails - 1 failed + 1 test failed---tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 1 test failed a › fails @@ -20,4 +23,5 @@ `--fail-fast` is on. 1 test file was skipped. + ---tty-stream-chunk-separator diff --git a/test/reporters/mini.failfast2.log b/test/reporters/mini.failfast2.log index 8d1fbb174..e08e380f4 100644 --- a/test/reporters/mini.failfast2.log +++ b/test/reporters/mini.failfast2.log @@ -1,11 +1,14 @@ ----tty-stream-chunk-separator - - a › fails +[?25l---tty-stream-chunk-separator - 1 failed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator +* ---tty-stream-chunk-separator +---tty-stream-chunk-separator +* a › fails - 1 failed + 1 test failed---tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 1 test failed a › fails @@ -20,4 +23,5 @@ `--fail-fast` is on. At least 1 test was skipped, as well as 1 test file. + ---tty-stream-chunk-separator diff --git a/test/reporters/mini.js b/test/reporters/mini.js index 800a550db..3cd3c5de7 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -17,15 +17,15 @@ const run = type => t => { columns: 200, sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.unreliableProcessIO] }); - const reporter = Object.assign(new MiniReporter({color: true, watching: type === 'watch'}), { - stream: tty, - // Disable the spinner. - start() { - return ''; + const reporter = new MiniReporter({ + spinner: { + interval: 60 * 60 * 1000, // No need to update the spinner + color: false, + frames: ['*'] }, - spinnerChar() { - return ' '; - } + reportStream: tty, + stdStream: tty, + watching: type === 'watch' }); return report[type](reporter) .then(() => { diff --git a/test/reporters/mini.only.log b/test/reporters/mini.only.log index ad6823449..7bb4c83c2 100644 --- a/test/reporters/mini.only.log +++ b/test/reporters/mini.only.log @@ -1,14 +1,17 @@ ----tty-stream-chunk-separator - - a › only +[?25l---tty-stream-chunk-separator + +---tty-stream-chunk-separator +* ---tty-stream-chunk-separator +---tty-stream-chunk-separator +* a › only 1 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator - - b › passes +---tty-stream-chunk-separator +* b › passes 2 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 2 tests passed - 2 passed ---tty-stream-chunk-separator diff --git a/test/reporters/mini.regular.log b/test/reporters/mini.regular.log index dded51d50..76c9f8680 100644 --- a/test/reporters/mini.regular.log +++ b/test/reporters/mini.regular.log @@ -1,139 +1,124 @@ ----tty-stream-chunk-separator - - unhandled-rejection › passes +[?25l---tty-stream-chunk-separator + +---tty-stream-chunk-separator +* ---tty-stream-chunk-separator +---tty-stream-chunk-separator +* unhandled-rejection › passes 1 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator - - unhandled-rejection › unhandled non-error rejection +---tty-stream-chunk-separator +* unhandled-rejection › unhandled non-error rejection 2 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator - - uncaught-exception › passes +---tty-stream-chunk-separator +* uncaught-exception › passes 3 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator stdout ----tty-stream-chunk-separator - - uncaught-exception › passes +* uncaught-exception › passes 3 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator stderr ----tty-stream-chunk-separator - - uncaught-exception › passes +* uncaught-exception › passes 3 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › passes +---tty-stream-chunk-separator +* test › passes 4 passed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › fails +---tty-stream-chunk-separator +* test › fails 4 passed - 1 failed + 1 test failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › known failure +---tty-stream-chunk-separator +* test › known failure - 5 passed + 4 passed 1 known failure - 1 failed + 1 test failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › no longer failing +---tty-stream-chunk-separator +* test › no longer failing - 5 passed + 4 passed 1 known failure - 2 failed + 2 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › logs +---tty-stream-chunk-separator +* test › logs - 5 passed + 4 passed 1 known failure - 3 failed + 3 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › formatted +---tty-stream-chunk-separator +* test › formatted - 5 passed + 4 passed 1 known failure - 4 failed + 4 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › power-assert +---tty-stream-chunk-separator +* test › power-assert - 5 passed + 4 passed 1 known failure - 5 failed + 5 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › bad throws +---tty-stream-chunk-separator +* test › bad throws - 5 passed + 4 passed 1 known failure - 6 failed + 6 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › bad notThrows +---tty-stream-chunk-separator +* test › bad notThrows - 5 passed + 4 passed 1 known failure - 7 failed + 7 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › implementation throws non-error +---tty-stream-chunk-separator +* test › implementation throws non-error - 5 passed + 4 passed 1 known failure - 8 failed + 8 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - slow › slow +---tty-stream-chunk-separator +* slow › slow - 6 passed + 5 passed 1 known failure - 8 failed + 8 tests failed 1 skipped 1 todo---tty-stream-chunk-separator ----tty-stream-chunk-separator - - 6 passed +---tty-stream-chunk-separator +[?25h + 8 tests failed 1 known failure - 8 failed - 1 skipped - 1 todo - 2 rejections - 3 exceptions + 1 test skipped + 1 test todo + 2 unhandled rejections + 2 uncaught exceptions - test › known failure + test › known failure test › fails @@ -150,7 +135,7 @@ stderr test › no longer failing - Test was expected to fail, but succeeded, you should stop marking the test as failing + Error: Test was expected to fail, but succeeded, you should stop marking the test as failing @@ -268,18 +253,43 @@ stderr Uncaught exception in test/fixture/report/regular/bad-test-chain.js - TypeError: _.default.serial.test is not a function - Object. (bad-test-chain.js:3:13) - ✖ No tests found in test/fixture/report/regular/bad-test-chain.js + ~/test/fixture/report/regular/bad-test-chain.js:3 + + 2: +  3: test.serial.test('passes', t => t.pass()); + 4: + + TypeError: _.default.serial.test is not a function + + + + Uncaught exception in test/fixture/report/regular/uncaught-exception.js + + ~/test/fixture/report/regular/uncaught-exception.js:5 + + 4: setTimeout(() => { +  5: throw new Error('Can/'t catch me'); + 6: }); + + Error: Can't catch me + + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js - Error: Can't catch me - t (unhandled-rejection.js:4:17) + + ~/test/fixture/report/regular/unhandled-rejection.js:4 + + 3: test('passes', t => { +  4: Promise.reject(new Error('Can/'t catch me')); + 5: t.pass(); + + Error: Can't catch me + + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js + null - Uncaught exception in test/fixture/report/regular/uncaught-exception.js - Error: Can't catch me - Timeout.setTimeout (uncaught-exception.js:5:9) + ---tty-stream-chunk-separator diff --git a/test/reporters/mini.watch.log b/test/reporters/mini.watch.log index a2479aab8..1f6d8ef7c 100644 --- a/test/reporters/mini.watch.log +++ b/test/reporters/mini.watch.log @@ -1,31 +1,48 @@ ----tty-stream-chunk-separator - - test › passes +[?25l---tty-stream-chunk-separator - 1 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator +---tty-stream-chunk-separator +* ---tty-stream-chunk-separator +---tty-stream-chunk-separator +* test › passes + + 1 passed [17:19:12]---tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 1 test passed [17:19:12] - 1 passed [17:19:12] ---tty-stream-chunk-separator +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +---tty-stream-chunk-separator +[?25l---tty-stream-chunk-separator -────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────---tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › passes +---tty-stream-chunk-separator +* test › passes - 1 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator + 1 passed [17:19:12]---tty-stream-chunk-separator +---tty-stream-chunk-separator +* test › passes - 1 passed [17:19:12] + 1 passed [17:19:12]---tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 1 test passed [17:19:12] 2 previous failures in test files that were not rerun + ---tty-stream-chunk-separator ----tty-stream-chunk-separator ----tty-stream-chunk-separator - - test › passes +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +---tty-stream-chunk-separator +[?25l---tty-stream-chunk-separator + +---tty-stream-chunk-separator +* test › passes + + 1 passed [17:19:12]---tty-stream-chunk-separator +---tty-stream-chunk-separator +* test › passes - 1 passed---tty-stream-chunk-separator ----tty-stream-chunk-separator + 1 passed [17:19:12]---tty-stream-chunk-separator +---tty-stream-chunk-separator +[?25h + 1 test passed [17:19:12] - 1 passed [17:19:12] ---tty-stream-chunk-separator diff --git a/test/reporters/prefix-title.js b/test/reporters/prefix-title.js new file mode 100644 index 000000000..9772eccb7 --- /dev/null +++ b/test/reporters/prefix-title.js @@ -0,0 +1,50 @@ +'use strict'; +require('../../lib/chalk').set(); + +const path = require('path'); +const figures = require('figures'); +const test = require('tap').test; +const chalk = require('../../lib/chalk').get(); +const prefixTitle = require('../../lib/reporters/prefix-title'); + +const sep = ' ' + chalk.gray.dim(figures.pointerSmall) + ' '; + +test('removes base if found at start of path', t => { + t.is(prefixTitle(`test${path.sep}`, path.normalize('test/run-status.js'), 'title'), `run-status${sep}title`); + t.end(); +}); + +test('does not remove base if found but not at start of path', t => { + t.is(prefixTitle(path.sep, path.normalize('test/run-status.js'), 'title'), `test${sep}run-status${sep}title`); + t.end(); +}); + +test('removes .js extension', t => { + t.is(prefixTitle(path.sep, 'run-status.js', 'title'), `run-status${sep}title`); + t.end(); +}); + +test('does not remove .js from middle of path', t => { + t.is(prefixTitle(path.sep, 'run-.js-status.js', 'title'), `run-.js-status${sep}title`); + t.end(); +}); + +test('removes __tests__ from path', t => { + t.is(prefixTitle(path.sep, path.normalize('backend/__tests__/run-status.js'), 'title'), `backend${sep}run-status${sep}title`); + t.end(); +}); + +test('removes .spec from path', t => { + t.is(prefixTitle(path.sep, path.normalize('backend/run-status.spec.js'), 'title'), `backend${sep}run-status${sep}title`); + t.end(); +}); + +test('removes .test from path', t => { + t.is(prefixTitle(path.sep, path.normalize('backend/run-status.test.js'), 'title'), `backend${sep}run-status${sep}title`); + t.end(); +}); + +test('removes test- from path', t => { + t.is(prefixTitle(path.sep, path.normalize('backend/test-run-status.js'), 'title'), `backend${sep}run-status${sep}title`); + t.end(); +}); diff --git a/test/reporters/tap.failfast2.log b/test/reporters/tap.failfast2.log index 58a85fe98..965c4e1a0 100644 --- a/test/reporters/tap.failfast2.log +++ b/test/reporters/tap.failfast2.log @@ -9,10 +9,13 @@ not ok 1 - a › fails at: 't (a.js:3:22)' ... ---tty-stream-chunk-separator +# 1 test remaining in test/fixture/report/failfast2/a.js +not ok 2 - 1 test remaining in test/fixture/report/failfast2/a.js +---tty-stream-chunk-separator 1..1 # tests 1 # pass 0 -# fail 1 +# fail 2 ---tty-stream-chunk-separator diff --git a/test/reporters/tap.js b/test/reporters/tap.js index 4046baeb9..50ae82653 100644 --- a/test/reporters/tap.js +++ b/test/reporters/tap.js @@ -17,8 +17,9 @@ const run = type => t => { columns: 200, sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.unreliableProcessIO] }); - const reporter = Object.assign(new TapReporter(), { - streams: {stderr: tty, stdout: tty} + const reporter = new TapReporter({ + reportStream: tty, + stdStream: tty }); return report[type](reporter) .then(() => { diff --git a/test/reporters/tap.regular.log b/test/reporters/tap.regular.log index 0f2d3aaae..019330909 100644 --- a/test/reporters/tap.regular.log +++ b/test/reporters/tap.regular.log @@ -1,14 +1,15 @@ TAP version 13 ---tty-stream-chunk-separator -# _.default.serial.test is not a function -not ok 1 - _.default.serial.test is not a function +# TypeError: _.default.serial.test is not a function +not ok 1 - TypeError: _.default.serial.test is not a function --- name: TypeError + message: _.default.serial.test is not a function at: 'Object. (bad-test-chain.js:3:13)' ... ---tty-stream-chunk-separator -# No tests found in test/fixture/report/regular/bad-test-chain.js -not ok 2 - No tests found in test/fixture/report/regular/bad-test-chain.js +# test/fixture/report/regular/bad-test-chain.js exited with a non-zero exit code: 1 +not ok 2 - test/fixture/report/regular/bad-test-chain.js exited with a non-zero exit code: 1 ---tty-stream-chunk-separator # unhandled-rejection › passes ok 3 - unhandled-rejection › passes @@ -16,15 +17,16 @@ ok 3 - unhandled-rejection › passes # unhandled-rejection › unhandled non-error rejection ok 4 - unhandled-rejection › unhandled non-error rejection ---tty-stream-chunk-separator -# Can't catch me -not ok 5 - Can't catch me +# Error: Can't catch me +not ok 5 - Error: Can't catch me --- name: Error + message: Can't catch me at: 't (unhandled-rejection.js:4:17)' ... ---tty-stream-chunk-separator -# undefined -not ok 6 - undefined +# unhandled-rejection +not ok 6 - unhandled-rejection --- message: Non-error object formatted: 'null' @@ -33,28 +35,32 @@ not ok 6 - undefined # uncaught-exception › passes ok 7 - uncaught-exception › passes ---tty-stream-chunk-separator -# Can't catch me -not ok 8 - Can't catch me +# Error: Can't catch me +not ok 8 - Error: Can't catch me --- name: Error + message: Can't catch me at: 'Timeout.setTimeout (uncaught-exception.js:5:9)' ... ---tty-stream-chunk-separator +# test/fixture/report/regular/uncaught-exception.js exited with a non-zero exit code: 1 +not ok 9 - test/fixture/report/regular/uncaught-exception.js exited with a non-zero exit code: 1 +---tty-stream-chunk-separator stdout ---tty-stream-chunk-separator stderr ---tty-stream-chunk-separator # test › skip -ok 9 - test › skip # SKIP +ok 10 - test › skip # SKIP ---tty-stream-chunk-separator # test › todo -not ok 10 - test › todo # TODO +not ok 11 - test › todo # TODO ---tty-stream-chunk-separator # test › passes -ok 11 - test › passes +ok 12 - test › passes ---tty-stream-chunk-separator # test › fails -not ok 12 - test › fails +not ok 13 - test › fails --- name: AssertionError message: Test failed via `t.fail()` @@ -63,10 +69,10 @@ not ok 12 - test › fails ... ---tty-stream-chunk-separator # test › known failure -ok 13 - test › known failure +ok 14 - test › known failure ---tty-stream-chunk-separator # test › no longer failing -not ok 14 - test › no longer failing +not ok 15 - test › no longer failing --- name: Error message: >- @@ -75,7 +81,7 @@ not ok 14 - test › no longer failing ... ---tty-stream-chunk-separator # test › logs -not ok 15 - test › logs +not ok 16 - test › logs * hello * world --- @@ -86,7 +92,7 @@ not ok 15 - test › logs ... ---tty-stream-chunk-separator # test › formatted -not ok 16 - test › formatted +not ok 17 - test › formatted --- name: AssertionError assertion: deepEqual @@ -98,7 +104,7 @@ not ok 16 - test › formatted ... ---tty-stream-chunk-separator # test › power-assert -not ok 17 - test › power-assert +not ok 18 - test › power-assert --- name: AssertionError assertion: falsy @@ -109,7 +115,7 @@ not ok 17 - test › power-assert ... ---tty-stream-chunk-separator # test › bad throws -not ok 18 - test › bad throws +not ok 19 - test › bad throws --- name: AssertionError message: Improper usage of `t.throws()` detected @@ -123,7 +129,7 @@ not ok 18 - test › bad throws ... ---tty-stream-chunk-separator # test › bad notThrows -not ok 19 - test › bad notThrows +not ok 20 - test › bad notThrows --- name: AssertionError message: Improper usage of `t.notThrows()` detected @@ -137,7 +143,7 @@ not ok 19 - test › bad notThrows ... ---tty-stream-chunk-separator # test › implementation throws non-error -not ok 20 - test › implementation throws non-error +not ok 21 - test › implementation throws non-error --- name: AssertionError message: Error thrown in test @@ -146,13 +152,13 @@ not ok 20 - test › implementation throws non-error ... ---tty-stream-chunk-separator # slow › slow -ok 21 - slow › slow +ok 22 - slow › slow ---tty-stream-chunk-separator -1..14 -# tests 14 -# pass 5 +1..16 +# tests 15 +# pass 6 # skip 1 -# fail 13 +# fail 15 ---tty-stream-chunk-separator diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index 95dd4e849..fefcaa1d5 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -17,8 +17,10 @@ const run = type => t => { columns: 200, sanitizers: [report.sanitizers.cwd, report.sanitizers.posix, report.sanitizers.slow, report.sanitizers.unreliableProcessIO] }); - const reporter = Object.assign(new VerboseReporter({color: true, watching: type === 'watch'}), { - stream: tty + const reporter = new VerboseReporter({ + reportStream: tty, + stdStream: tty, + watching: type === 'watch' }); return report[type](reporter) .then(() => { diff --git a/test/reporters/verbose.regular.log b/test/reporters/verbose.regular.log index f97b2f1b5..dec9a02e0 100644 --- a/test/reporters/verbose.regular.log +++ b/test/reporters/verbose.regular.log @@ -1,38 +1,54 @@ ---tty-stream-chunk-separator Uncaught exception in test/fixture/report/regular/bad-test-chain.js - TypeError: _.default.serial.test is not a function - Object. (bad-test-chain.js:3:13) + ~/test/fixture/report/regular/bad-test-chain.js:3 + 2: +  3: test.serial.test('passes', t => t.pass()); + 4: + + TypeError: _.default.serial.test is not a function ---tty-stream-chunk-separator - ✖ No tests found in test/fixture/report/regular/bad-test-chain.js + ✖ test/fixture/report/regular/bad-test-chain.js exited with a non-zero exit code: 1 ---tty-stream-chunk-separator ✔ unhandled-rejection › passes ---tty-stream-chunk-separator ✔ unhandled-rejection › unhandled non-error rejection ---tty-stream-chunk-separator + Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js - Error: Can't catch me - t (unhandled-rejection.js:4:17) + ~/test/fixture/report/regular/unhandled-rejection.js:4 + + 3: test('passes', t => { +  4: Promise.reject(new Error('Can/'t catch me')); + 5: t.pass(); + Error: Can't catch me ---tty-stream-chunk-separator Unhandled rejection in test/fixture/report/regular/unhandled-rejection.js - null + null ---tty-stream-chunk-separator ✔ uncaught-exception › passes ---tty-stream-chunk-separator + Uncaught exception in test/fixture/report/regular/uncaught-exception.js - Error: Can't catch me - Timeout.setTimeout (uncaught-exception.js:5:9) + ~/test/fixture/report/regular/uncaught-exception.js:5 + + 4: setTimeout(() => { +  5: throw new Error('Can/'t catch me'); + 6: }); + Error: Can't catch me +---tty-stream-chunk-separator + ✖ test/fixture/report/regular/uncaught-exception.js exited with a non-zero exit code: 1 ---tty-stream-chunk-separator stdout ---tty-stream-chunk-separator @@ -72,8 +88,7 @@ stderr 1 test skipped 1 test todo 2 unhandled rejections - 3 uncaught exceptions - + 2 uncaught exceptions test › known failure @@ -92,7 +107,7 @@ stderr test › no longer failing - Test was expected to fail, but succeeded, you should stop marking the test as failing + Error: Test was expected to fail, but succeeded, you should stop marking the test as failing diff --git a/test/reporters/verbose.watch.log b/test/reporters/verbose.watch.log index b437964a2..31689cbd4 100644 --- a/test/reporters/verbose.watch.log +++ b/test/reporters/verbose.watch.log @@ -17,6 +17,8 @@ 2 previous failures in test files that were not rerun ---tty-stream-chunk-separator +──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +---tty-stream-chunk-separator ---tty-stream-chunk-separator ✔ test › passes diff --git a/test/run-status.js b/test/run-status.js deleted file mode 100644 index 214d3439e..000000000 --- a/test/run-status.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; -require('../lib/chalk').set(); - -const path = require('path'); -const test = require('tap').test; -const chalk = require('chalk'); -const figures = require('figures'); -const RunStatus = require('../lib/run-status'); - -const sep = ' ' + chalk.gray.dim(figures.pointerSmall) + ' '; - -test('prefixTitle returns empty if prefixTitles == false', t => { - const runStatus = new RunStatus({prefixTitles: false}); - t.is(runStatus.prefixTitle('test/run-status.js'), ''); - t.end(); -}); - -test('prefixTitle removes base if found at start of path', t => { - const runStatus = new RunStatus({base: `test${path.sep}`}); - t.is(runStatus.prefixTitle(path.normalize('test/run-status.js')), `run-status${sep}`); - t.end(); -}); - -test('prefixTitle does not remove base if found but not at start of path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle(path.normalize('test/run-status.js')), `test${sep}run-status${sep}`); - t.end(); -}); - -test('prefixTitle removes .js extension', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle('run-status.js'), `run-status${sep}`); - t.end(); -}); - -test('prefixTitle does not remove .js from middle of path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle('run-.js-status.js'), `run-.js-status${sep}`); - t.end(); -}); - -test('prefixTitle removes __tests__ from path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle(path.normalize('backend/__tests__/run-status.js')), `backend${sep}run-status${sep}`); - t.end(); -}); - -test('prefixTitle removes .spec from path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle(path.normalize('backend/run-status.spec.js')), `backend${sep}run-status${sep}`); - t.end(); -}); - -test('prefixTitle removes .test from path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle(path.normalize('backend/run-status.test.js')), `backend${sep}run-status${sep}`); - t.end(); -}); - -test('prefixTitle removes test- from path', t => { - const runStatus = new RunStatus({base: path.sep}); - t.is(runStatus.prefixTitle(path.normalize('backend/test-run-status.js')), `backend${sep}run-status${sep}`); - t.end(); -}); - -test('successfully initializes without any options provided', t => { - const runStatus = new RunStatus(); - t.is(runStatus.base, ''); - t.end(); -}); - -test('calculate remaining test count', t => { - const runStatus = new RunStatus(); - runStatus.testCount = 10; - - const results = [{ - stats: { - passCount: 1, - failCount: 1, - skipCount: 1, - todoCount: 1, - knownFailureCount: 1 - } - }]; - - runStatus.processResults(results); - - t.is(runStatus.remainingCount, 5); - t.end(); -}); diff --git a/test/runner.js b/test/runner.js index 9aafa11d3..6ec4e4b3a 100644 --- a/test/runner.js +++ b/test/runner.js @@ -10,7 +10,7 @@ const noop = () => {}; const promiseEnd = (runner, next) => { return new Promise(resolve => { - runner.on('start', data => resolve(data.ended)); + resolve(runner.once('finish')); next(runner); }).then(() => runner); }; @@ -43,14 +43,19 @@ test('tests must be declared synchronously', t => { }); }); -test('runner emits a "test" event', t => { +test('runner emits "stateChange" events', t => { const runner = new Runner(); - runner.on('test', props => { - t.ifError(props.error); - t.is(props.title, 'foo'); - t.not(props.duration, undefined); - t.end(); + runner.on('stateChange', evt => { + if (evt.type === 'declared-test') { + t.deepEqual(evt, { + type: 'declared-test', + title: 'foo', + knownFailing: false, + todo: false + }); + t.end(); + } }); runner.chain('foo', a => { @@ -122,53 +127,20 @@ test('anything can be skipped', t => { }); }); -test('emit skipped tests at start', t => { - t.plan(1); - - const runner = new Runner(); - runner.on('start', data => { - t.strictDeepEqual(data.skippedTests, [ - {failing: false, title: 'test.serial.skip'}, - {failing: true, title: 'test.failing.skip'} - ]); - }); - - return promiseEnd(runner, () => { - runner.chain.before('before', noop); - runner.chain.before.skip('before.skip', noop); - - runner.chain.beforeEach('beforeEach', noop); - runner.chain.beforeEach.skip('beforeEach.skip', noop); - - runner.chain.serial('test.serial', a => a.pass()); - runner.chain.serial.skip('test.serial.skip', noop); - - runner.chain.failing('test.failing', a => a.fail()); - runner.chain.failing.skip('test.failing.skip', noop); - - runner.chain.after('after', noop); - runner.chain.after.skip('after.skip', noop); - - runner.chain.afterEach('afterEach', noop); - runner.chain.afterEach.skip('afterEach.skip', noop); - }); -}); - test('test types and titles', t => { - t.plan(20); + t.plan(10); const fail = a => a.fail(); const pass = a => a.pass(); const check = (setup, expect) => { const runner = new Runner(); - const assert = data => { - const expected = expect.shift(); - t.is(data.title, expected.title); - t.is(data.metadata.type, expected.type); - }; - runner.on('hook-failed', assert); - runner.on('test', assert); + runner.on('stateChange', evt => { + if (evt.type === 'hook-failed' || evt.type === 'test-failed' || evt.type === 'test-passed') { + const expected = expect.shift(); + t.is(evt.title, expected.title); + } + }); return promiseEnd(runner, () => setup(runner.chain)); }; @@ -217,10 +189,19 @@ test('test types and titles', t => { }); test('skip test', t => { - t.plan(4); + t.plan(3); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'selected-test' && evt.skip) { + t.pass(); + } + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('test', a => { arr.push('a'); a.pass(); @@ -229,10 +210,7 @@ test('skip test', t => { runner.chain.skip('skip', () => { arr.push('b'); }); - }).then(runner => { - t.is(runner.stats.testCount, 2); - t.is(runner.stats.passCount, 1); - t.is(runner.stats.skipCount, 1); + }).then(() => { t.strictDeepEqual(arr, ['a']); }); }); @@ -270,20 +248,26 @@ test('tests must have an implementation', t => { }); test('todo test', t => { - t.plan(4); + t.plan(3); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'selected-test' && evt.todo) { + t.pass(); + } + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('test', a => { arr.push('a'); a.pass(); }); runner.chain.todo('todo'); - }).then(runner => { - t.is(runner.stats.testCount, 2); - t.is(runner.stats.passCount, 1); - t.is(runner.stats.todoCount, 1); + }).then(() => { t.strictDeepEqual(arr, ['a']); }); }); @@ -321,10 +305,16 @@ test('todo test titles must be unique', t => { }); test('only test', t => { - t.plan(3); + t.plan(2); const arr = []; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'selected-test') { + t.pass(); + } + }); + runner.chain('test', a => { arr.push('a'); a.pass(); @@ -334,9 +324,7 @@ test('only test', t => { arr.push('b'); a.pass(); }); - }).then(runner => { - t.is(runner.stats.testCount, 1); - t.is(runner.stats.passCount, 1); + }).then(() => { t.strictDeepEqual(arr, ['b']); }); }); @@ -409,8 +397,10 @@ test('options.failFast does not stop concurrent tests from running', t => { a.pass(); }); - runner.on('test', data => { - t.is(data.title, expected.shift()); + runner.on('stateChange', evt => { + if (evt.type === 'test-failed' || evt.type === 'test-passed') { + t.is(evt.title, expected.shift()); + } }); }); }); @@ -441,8 +431,10 @@ test('options.failFast && options.serial stops subsequent tests from running ', a.pass(); }); - runner.on('test', data => { - t.is(data.title, expected.shift()); + runner.on('stateChange', evt => { + if (evt.type === 'test-failed' || evt.type === 'test-passed') { + t.is(evt.title, expected.shift()); + } }); }); }); @@ -477,16 +469,24 @@ test('options.failFast & failing serial test stops subsequent tests from running a.pass(); }); - runner.on('test', data => { - t.is(data.title, expected.shift()); + runner.on('stateChange', evt => { + if (evt.type === 'test-failed' || evt.type === 'test-passed') { + t.is(evt.title, expected.shift()); + } }); }); }); test('options.match will not run tests with non-matching titles', t => { - t.plan(5); + t.plan(4); return promiseEnd(new Runner({match: ['*oo', '!foo']}), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('mhm. grass tasty. moo', a => { t.pass(); a.pass(); @@ -506,17 +506,19 @@ test('options.match will not run tests with non-matching titles', t => { t.fail(); a.pass(); }); - }).then(runner => { - t.is(runner.stats.skipCount, 0); - t.is(runner.stats.passCount, 2); - t.is(runner.stats.testCount, 2); }); }); test('options.match hold no effect on hooks with titles', t => { - t.plan(4); + t.plan(2); return promiseEnd(new Runner({match: ['!before*']}), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + let actual; runner.chain.before('before hook with title', () => { @@ -527,17 +529,19 @@ test('options.match hold no effect on hooks with titles', t => { t.is(actual, 'foo'); a.pass(); }); - }).then(runner => { - t.is(runner.stats.skipCount, 0); - t.is(runner.stats.passCount, 1); - t.is(runner.stats.testCount, 1); }); }); test('options.match overrides .only', t => { - t.plan(5); + t.plan(4); return promiseEnd(new Runner({match: ['*oo']}), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('moo', a => { t.pass(); a.pass(); @@ -547,29 +551,34 @@ test('options.match overrides .only', t => { t.pass(); a.pass(); }); - }).then(runner => { - t.is(runner.stats.skipCount, 0); - t.is(runner.stats.passCount, 2); - t.is(runner.stats.testCount, 2); }); }); test('options.match matches todo tests', t => { - t.plan(2); + t.plan(1); return promiseEnd(new Runner({match: ['*oo']}), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'selected-test' && evt.todo) { + t.pass(); + } + }); + runner.chain.todo('moo'); runner.chain.todo('oom'); - }).then(runner => { - t.is(runner.stats.testCount, 1); - t.is(runner.stats.todoCount, 1); }); }); test('macros: Additional args will be spread as additional args on implementation function', t => { - t.plan(4); + t.plan(3); return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain.before(function (a) { t.deepEqual(slice.call(arguments, 1), ['foo', 'bar']); a.pass(); @@ -579,14 +588,11 @@ test('macros: Additional args will be spread as additional args on implementatio t.deepEqual(slice.call(arguments, 1), ['foo', 'bar']); a.pass(); }, 'foo', 'bar'); - }).then(runner => { - t.is(runner.stats.passCount, 1); - t.is(runner.stats.testCount, 1); }); }); test('macros: Customize test names attaching a `title` function', t => { - t.plan(8); + t.plan(6); const expectedTitles = [ 'defaultA', @@ -608,16 +614,15 @@ test('macros: Customize test names attaching a `title` function', t => { macroFn.title = (title, firstArg) => (title || 'default') + firstArg; return promiseEnd(new Runner(), runner => { - runner.on('test', props => { - t.is(props.title, expectedTitles.shift()); + runner.on('stateChange', evt => { + if (evt.type === 'declared-test') { + t.is(evt.title, expectedTitles.shift()); + } }); runner.chain(macroFn, 'A'); runner.chain('supplied', macroFn, 'B'); runner.chain(macroFn, 'C'); - }).then(runner => { - t.is(runner.stats.passCount, 3); - t.is(runner.stats.testCount, 3); }); }); @@ -646,7 +651,7 @@ test('macros: hook titles must be strings', t => { }); test('match applies to macros', t => { - t.plan(3); + t.plan(1); function macroFn(avaT) { avaT.pass(); @@ -655,15 +660,14 @@ test('match applies to macros', t => { macroFn.title = (title, firstArg) => `${firstArg}bar`; return promiseEnd(new Runner({match: ['foobar']}), runner => { - runner.on('test', props => { - t.is(props.title, 'foobar'); + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.is(evt.title, 'foobar'); + } }); runner.chain(macroFn, 'foo'); runner.chain(macroFn, 'bar'); - }).then(runner => { - t.is(runner.stats.passCount, 1); - t.is(runner.stats.testCount, 1); }); }); @@ -693,20 +697,24 @@ test('arrays of macros', t => { macroFnB.title = prefix => `${prefix}.B`; return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('A', [macroFnA, macroFnB], 'A'); runner.chain('B', [macroFnA, macroFnB], 'B'); runner.chain('C', macroFnA, 'C'); runner.chain('D', macroFnB, 'D'); - }).then(runner => { - t.is(runner.stats.passCount, 6); - t.is(runner.stats.testCount, 6); + }).then(() => { t.is(expectedArgsA.length, 0); t.is(expectedArgsB.length, 0); }); }); test('match applies to arrays of macros', t => { - t.plan(3); + t.plan(1); // Foo function fooMacro(a) { @@ -727,26 +735,29 @@ test('match applies to arrays of macros', t => { bazMacro.title = (title, firstArg) => `${firstArg}baz`; return promiseEnd(new Runner({match: ['foobar']}), runner => { - runner.on('test', props => { - t.is(props.title, 'foobar'); + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.is(evt.title, 'foobar'); + } }); runner.chain([fooMacro, barMacro, bazMacro], 'foo'); runner.chain([fooMacro, barMacro, bazMacro], 'bar'); - }).then(runner => { - t.is(runner.stats.passCount, 1); - t.is(runner.stats.testCount, 1); }); }); test('silently skips other tests when .only is used', t => { + t.plan(1); return promiseEnd(new Runner(), runner => { + runner.on('stateChange', evt => { + if (evt.type === 'test-passed') { + t.pass(); + } + }); + runner.chain('skip me', a => a.pass()); runner.chain.serial('skip me too', a => a.pass()); runner.chain.only('only me', a => a.pass()); - }).then(runner => { - t.is(runner.stats.passCount, 1); - t.is(runner.stats.skipCount, 0); }); }); diff --git a/test/watcher.js b/test/watcher.js index 7b3ae388b..e0412a6b1 100644 --- a/test/watcher.js +++ b/test/watcher.js @@ -39,7 +39,7 @@ const group = makeGroup(test); group('chokidar', (beforeEach, test, group) => { let chokidar; let debug; - let logger; + let reporter; let api; let avaFiles; let Subject; @@ -73,12 +73,8 @@ group('chokidar', (beforeEach, test, group) => { debug = sinon.spy(); - logger = { - start: sinon.spy(), - finish: sinon.spy(), - section: sinon.spy(), - clear: sinon.stub().returns(true), - reset: sinon.spy() + reporter = { + endRun: sinon.spy() }; api = { @@ -88,10 +84,25 @@ group('chokidar', (beforeEach, test, group) => { resetRunStatus = () => { runStatus = { - failCount: 0, - rejectionCount: 0, - exceptionCount: 0, - updateSnapshots: false + stats: { + byFile: new Map(), + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + failedWorkers: 0, + files, + finishedWorkers: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + timeouts: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 + } }; return runStatus; @@ -112,8 +123,6 @@ group('chokidar', (beforeEach, test, group) => { chokidarEmitter = new EventEmitter(); chokidar.watch.returns(chokidarEmitter); - logger.clear.returns(true); - avaFiles = AvaFiles; api.run.returns(new Promise(() => {})); @@ -123,7 +132,10 @@ group('chokidar', (beforeEach, test, group) => { 'test' ]; defaultApiOptions = { + clearLogOnNextRun: false, + previousFailures: 0, runOnlyExclusive: false, + runVector: 1, updateSnapshots: false }; @@ -135,7 +147,7 @@ group('chokidar', (beforeEach, test, group) => { Subject = proxyWatcher(); }); - const start = (specificFiles, sources) => new Subject(logger, api, specificFiles || files, sources || []); + const start = (specificFiles, sources) => new Subject(reporter, api, specificFiles || files, sources || []); const emitChokidar = (event, path) => { chokidarEmitter.emit('all', event, path); @@ -210,7 +222,7 @@ group('chokidar', (beforeEach, test, group) => { }); test('starts running the initial tests', t => { - t.plan(8); + t.plan(4); let done; api.run.returns(new Promise(resolve => { @@ -220,18 +232,14 @@ group('chokidar', (beforeEach, test, group) => { })); start(); - t.ok(logger.clear.notCalled); - t.ok(logger.reset.notCalled); - t.ok(logger.start.notCalled); t.ok(api.run.calledOnce); t.strictDeepEqual(api.run.firstCall.args, [files, defaultApiOptions]); - // Finish is only called after the run promise fulfils - t.ok(logger.finish.notCalled); + // The endRun method is only called after the run promise fulfils + t.ok(reporter.endRun.notCalled); done(); return delay().then(() => { - t.ok(logger.finish.calledOnce); - t.is(logger.finish.firstCall.args[0], runStatus); + t.ok(reporter.endRun.calledOnce); }); }); @@ -277,7 +285,7 @@ group('chokidar', (beforeEach, test, group) => { } ].forEach(variant => { test(`reruns initial tests when a source file ${variant.label}`, t => { - t.plan(12); + t.plan(4); api.run.returns(Promise.resolve(runStatus)); start(); @@ -291,29 +299,21 @@ group('chokidar', (beforeEach, test, group) => { variant.fire(); return debounce().then(() => { - t.ok(logger.clear.calledOnce); - t.ok(logger.reset.calledOnce); - t.ok(logger.start.calledOnce); t.ok(api.run.calledTwice); - // Clear is called before reset - t.ok(logger.clear.firstCall.calledBefore(logger.reset.firstCall)); - // Reset is called before the second run - t.ok(logger.reset.firstCall.calledBefore(api.run.secondCall)); - // Reset is called before start - t.ok(logger.reset.firstCall.calledBefore(logger.start.firstCall)); // No explicit files are provided - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); // Finish is only called after the run promise fulfils - t.ok(logger.finish.calledOnce); - t.is(logger.finish.firstCall.args[0], runStatus); + t.ok(reporter.endRun.calledOnce); resetRunStatus(); done(); return delay(); }).then(() => { - t.ok(logger.finish.calledTwice); - t.is(logger.finish.secondCall.args[0], runStatus); + t.ok(reporter.endRun.calledTwice); }); }); }); @@ -321,85 +321,43 @@ group('chokidar', (beforeEach, test, group) => { [ { label: 'failures', - prop: 'failCount' + prop: 'failedTests' }, { label: 'rejections', - prop: 'rejectionCount' + prop: 'unhandledRejections' }, { label: 'exceptions', - prop: 'exceptionCount' + prop: 'uncaughtExceptions' } ].forEach(variant => { - test(`does not clear logger if the previous run had ${variant.label}`, t => { + test(`does not clear log if the previous run had ${variant.label}`, t => { t.plan(2); - runStatus[variant.prop] = 1; + runStatus.stats[variant.prop] = 1; api.run.returns(Promise.resolve(runStatus)); start(); api.run.returns(Promise.resolve(resetRunStatus())); change(); return debounce().then(() => { - t.ok(logger.clear.notCalled); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: false, + runVector: 2 + })]); change(); return debounce(); }).then(() => { - t.ok(logger.clear.calledOnce); + t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 3 + })]); }); }); }); - test('sections the logger if it was not cleared', t => { - t.plan(5); - - api.run.returns(Promise.resolve({failCount: 1})); - start(); - - api.run.returns(Promise.resolve({failCount: 0})); - change(); - return debounce().then(() => { - t.ok(logger.clear.notCalled); - t.ok(logger.reset.calledTwice); - t.ok(logger.section.calledOnce); - t.ok(logger.reset.firstCall.calledBefore(logger.section.firstCall)); - t.ok(logger.reset.secondCall.calledAfter(logger.section.firstCall)); - }); - }); - - test('sections the logger if it could not be cleared', t => { - t.plan(5); - - logger.clear.returns(false); - api.run.returns(Promise.resolve(runStatus)); - start(); - - change(); - return debounce().then(() => { - t.ok(logger.clear.calledOnce); - t.ok(logger.reset.calledTwice); - t.ok(logger.section.calledOnce); - t.ok(logger.reset.firstCall.calledBefore(logger.section.firstCall)); - t.ok(logger.reset.secondCall.calledAfter(logger.section.firstCall)); - }); - }); - - test('does not section the logger if it was cleared', t => { - t.plan(3); - - api.run.returns(Promise.resolve(runStatus)); - start(); - - change(); - return debounce().then(() => { - t.ok(logger.clear.calledOnce); - t.ok(logger.section.notCalled); - t.ok(logger.reset.calledOnce); - }); - }); - test('debounces by 100ms', t => { t.plan(1); api.run.returns(Promise.resolve(runStatus)); @@ -447,7 +405,7 @@ group('chokidar', (beforeEach, test, group) => { let done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); start(); @@ -472,7 +430,7 @@ group('chokidar', (beforeEach, test, group) => { let done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); @@ -504,7 +462,7 @@ group('chokidar', (beforeEach, test, group) => { } ].forEach(variant => { test(`(re)runs a test file when it ${variant.label}`, t => { - t.plan(6); + t.plan(4); api.run.returns(Promise.resolve(runStatus)); start(); @@ -520,18 +478,19 @@ group('chokidar', (beforeEach, test, group) => { return debounce().then(() => { t.ok(api.run.calledTwice); // The `test.js` file is provided - t.strictDeepEqual(api.run.secondCall.args, [['test.js'], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [['test.js'], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); - // Finish is only called after the run promise fulfills - t.ok(logger.finish.calledOnce); - t.is(logger.finish.firstCall.args[0], runStatus); + // The endRun method is only called after the run promise fulfills + t.ok(reporter.endRun.calledOnce); resetRunStatus(); done(); return delay(); }).then(() => { - t.ok(logger.finish.calledTwice); - t.is(logger.finish.secondCall.args[0], runStatus); + t.ok(reporter.endRun.calledTwice); }); }); }); @@ -546,7 +505,10 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // The test files are provided - t.strictDeepEqual(api.run.secondCall.args, [['test-one.js', 'test-two.js'], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [['test-one.js', 'test-two.js'], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -560,7 +522,10 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // No explicit files are provided - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -586,7 +551,10 @@ group('chokidar', (beforeEach, test, group) => { add('foo-baz.js'); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [['foo-bar.js', 'foo-baz.js'], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [['foo-bar.js', 'foo-baz.js'], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -612,7 +580,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // `foo-bar.js` is excluded from being a test file, thus the initial tests // are run - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -627,7 +598,10 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // `foo.bar` cannot be a test file, thus the initial tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -642,7 +616,10 @@ group('chokidar', (beforeEach, test, group) => { return debounce(2).then(() => { t.ok(api.run.calledTwice); // `_foo.bar` cannot be a test file, thus the initial tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -664,7 +641,10 @@ group('chokidar', (beforeEach, test, group) => { path.join('dir', 'nested', 'test.js'), path.join('another-dir', 'nested', 'deeper', 'test.js') ], - defaultApiOptions + Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + }) ]); }); }); @@ -691,7 +671,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // `dir/exclude/foo.js` is excluded from being a test file, thus the initial // tests are run - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -704,13 +687,17 @@ group('chokidar', (beforeEach, test, group) => { stdin.write(`${input}\n`); return delay().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + runVector: 2 + })]); stdin.write(`\t${input} \n`); return delay(); }).then(() => { t.ok(api.run.calledThrice); - t.strictDeepEqual(api.run.thirdCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, { + runVector: 3 + })]); }); }); }); @@ -725,26 +712,34 @@ group('chokidar', (beforeEach, test, group) => { stdin.write(`u\n`); return delay().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [previousFiles, options]); + t.strictDeepEqual(api.run.secondCall.args, [previousFiles, Object.assign({}, options, { + runVector: 2 + })]); stdin.write(`\tu \n`); return delay(); }).then(() => { t.ok(api.run.calledThrice); - t.strictDeepEqual(api.run.thirdCall.args, [previousFiles, options]); + t.strictDeepEqual(api.run.thirdCall.args, [previousFiles, Object.assign({}, options, { + runVector: 3 + })]); }); }); ['r', 'rs', 'u'].forEach(input => { - test(`entering "${input}" on stdin prevents the logger from being cleared`, t => { + test(`entering "${input}" on stdin prevents the log from being cleared`, t => { t.plan(2); - api.run.returns(Promise.resolve({failCount: 0})); + api.run.returns(Promise.resolve(runStatus)); start().observeStdin(stdin); stdin.write(`${input}\n`); return delay().then(() => { t.ok(api.run.calledTwice); - t.ok(logger.clear.notCalled); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: false, + runVector: 2, + updateSnapshots: input === 'u' + })]); }); }); @@ -757,7 +752,7 @@ group('chokidar', (beforeEach, test, group) => { let done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); @@ -790,7 +785,7 @@ group('chokidar', (beforeEach, test, group) => { const previous = done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); @@ -897,28 +892,50 @@ group('chokidar', (beforeEach, test, group) => { }; runStatusEmitter = new EventEmitter(); runStatus = { + stats: { + byFile: new Map(), + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + failedWorkers: 0, + files, + finishedWorkers: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + timeouts: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 + }, on(event, fn) { runStatusEmitter.on(event, fn); } }; }); - const emitDependencies = (file, dependencies) => { - runStatusEmitter.emit('dependencies', file, dependencies); + const emitDependencies = (testFile, dependencies) => { + runStatusEmitter.emit('stateChange', {type: 'dependencies', testFile, dependencies}); }; const seed = sources => { let done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); const watcher = start(null, sources); const files = [path.join('test', '1.js'), path.join('test', '2.js')]; const absFiles = files.map(relFile => path.resolve(relFile)); - apiEmitter.emit('test-run', runStatus, absFiles); + apiEmitter.emit('run', { + files: absFiles, + status: runStatus + }); emitDependencies(files[0], [path.resolve('dep-1.js'), path.resolve('dep-3.js')]); emitDependencies(files[1], [path.resolve('dep-2.js'), path.resolve('dep-3.js')]); @@ -934,7 +951,10 @@ group('chokidar', (beforeEach, test, group) => { change('dep-1.js'); return debounce().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -945,7 +965,10 @@ group('chokidar', (beforeEach, test, group) => { change('cannot-be-mapped.js'); return debounce().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -959,7 +982,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); t.strictDeepEqual(api.run.secondCall.args, [ [path.join('test', '2.js'), path.join('test', '1.js')], - defaultApiOptions + Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + }) ]); }); }); @@ -972,7 +998,10 @@ group('chokidar', (beforeEach, test, group) => { change('dep-1.js'); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -984,7 +1013,10 @@ group('chokidar', (beforeEach, test, group) => { change('dep-3.js'); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '2.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '2.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -996,7 +1028,10 @@ group('chokidar', (beforeEach, test, group) => { change('dep-4.js'); return debounce().then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1022,7 +1057,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // Expect all tests to be rerun since `dep-2.js` is not a tracked // dependency - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); }); @@ -1040,7 +1078,10 @@ group('chokidar', (beforeEach, test, group) => { api.run.returns(Promise.resolve(runStatus)); return debounce(3).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); change('foo.bar'); return debounce(); @@ -1048,7 +1089,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledThrice); // Expect all tests to be rerun since `foo.bar` is not a tracked // dependency - t.strictDeepEqual(api.run.thirdCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.thirdCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 3 + })]); }); }); @@ -1081,7 +1125,10 @@ group('chokidar', (beforeEach, test, group) => { t.ok(api.run.calledTwice); // Since the excluded files are not tracked as a dependency, all tests // are expected to be rerun - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1095,7 +1142,10 @@ group('chokidar', (beforeEach, test, group) => { return debounce(1).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[path.join('test', '1.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1109,7 +1159,7 @@ group('chokidar', (beforeEach, test, group) => { // also be ignoring this file but hey) change(path.join('..', 'outside.js')); - api.run.returns(Promise.resolve({failCount: 0})); + api.run.returns(Promise.resolve(runStatus)); return debounce().then(() => { t.ok(api.run.calledTwice); // If `../outside.js` was tracked as a dependency of test/1.js this would @@ -1118,13 +1168,19 @@ group('chokidar', (beforeEach, test, group) => { // wouldn't even be picked up. However this lets us test dependency // tracking without directly inspecting the internal state of the // watcher. - t.strictDeepEqual(api.run.secondCall.args, [files, defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [files, Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); change('..foo.js'); return debounce(); }).then(() => { t.ok(api.run.calledThrice); - t.strictDeepEqual(api.run.thirdCall.args, [[path.join('test', '2.js')], defaultApiOptions]); + t.strictDeepEqual(api.run.thirdCall.args, [[path.join('test', '2.js')], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 3 + })]); }); }); @@ -1154,18 +1210,56 @@ group('chokidar', (beforeEach, test, group) => { group('.only is sticky', (beforeEach, test) => { let apiEmitter; + let runStatus; + let runStatusEmitter; beforeEach(() => { apiEmitter = new EventEmitter(); api.on = (event, fn) => { apiEmitter.on(event, fn); }; + runStatusEmitter = new EventEmitter(); + runStatus = { + stats: { + byFile: new Map(), + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + failedWorkers: 0, + files, + finishedWorkers: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + timeouts: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 + }, + on(event, fn) { + runStatusEmitter.on(event, fn); + } + }; }); - const emitStats = (file, hasExclusive) => { - apiEmitter.emit('stats', { - file, - hasExclusive + const emitStats = (testFile, hasExclusive) => { + runStatus.stats.byFile.set(testFile, { + declaredTests: 2, + failedHooks: 0, + failedTests: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: hasExclusive ? 1 : 2, + skippedTests: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 }); + runStatusEmitter.emit('stateChange', {type: 'worker-finished', testFile}); }; const t1 = path.join('test', '1.js'); @@ -1177,11 +1271,15 @@ group('chokidar', (beforeEach, test, group) => { let done; api.run.returns(new Promise(resolve => { done = () => { - resolve({}); + resolve(runStatus); }; })); const watcher = start(); + apiEmitter.emit('run', { + files: [t1, t2, t3, t4], + status: runStatus + }); emitStats(t1, true); emitStats(t2, true); emitStats(t3, false); @@ -1201,7 +1299,10 @@ group('chokidar', (beforeEach, test, group) => { change(t4); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t3, t4], options]); + t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t3, t4], Object.assign({}, options, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1214,7 +1315,10 @@ group('chokidar', (beforeEach, test, group) => { change(t4); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t4], options]); + t.strictDeepEqual(api.run.secondCall.args, [[t1, t2, t4], Object.assign({}, options, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1226,7 +1330,10 @@ group('chokidar', (beforeEach, test, group) => { change(t2); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[t1, t2], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[t1, t2], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1241,7 +1348,10 @@ group('chokidar', (beforeEach, test, group) => { change(t4); return debounce(2).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); @@ -1255,7 +1365,10 @@ group('chokidar', (beforeEach, test, group) => { change(t4); return debounce(4).then(() => { t.ok(api.run.calledTwice); - t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], defaultApiOptions]); + t.strictDeepEqual(api.run.secondCall.args, [[t3, t4], Object.assign({}, defaultApiOptions, { + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); }); @@ -1271,6 +1384,25 @@ group('chokidar', (beforeEach, test, group) => { }; runStatusEmitter = new EventEmitter(); runStatus = { + stats: { + byFile: new Map(), + declaredTests: 0, + failedHooks: 0, + failedTests: 0, + failedWorkers: 0, + files, + finishedWorkers: 0, + internalErrors: 0, + remainingTests: 0, + passedKnownFailingTests: 0, + passedTests: 0, + selectedTests: 0, + skippedTests: 0, + timeouts: 0, + todoTests: 0, + uncaughtExceptions: 0, + unhandledRejections: 0 + }, on(event, fn) { runStatusEmitter.on(event, fn); } @@ -1287,9 +1419,10 @@ group('chokidar', (beforeEach, test, group) => { const watcher = start(); const files = [path.join('test', '1.js'), path.join('test', '2.js')]; - apiEmitter.emit('test-run', runStatus, files.map(relFile => { - return path.resolve(relFile); - })); + apiEmitter.emit('run', { + files, + status: runStatus + }); if (seedFailures) { seedFailures(files); @@ -1311,101 +1444,118 @@ group('chokidar', (beforeEach, test, group) => { change(file); return debounce().then(() => { - apiEmitter.emit('test-run', runStatus, [path.resolve(file)]); + apiEmitter.emit('run', { + files: [file], + status: runStatus + }); done(); api.run.returns(new Promise(() => {})); }); }; - test('sets runStatus.previousFailCount to 0 if there were no previous failures', t => { - t.plan(1); - - seed(files => { - runStatusEmitter.emit('error', {file: files[0]}); - }); - return debounce().then(() => { - t.is(runStatus.previousFailCount, 0); - }); - }); - - test('sets runStatus.previousFailCount if there were prevous failures', t => { - t.plan(1); + test('runs with previousFailures set to number of prevous failures', t => { + t.plan(2); let other; seed(files => { - runStatusEmitter.emit('test', { - file: files[0], - error: {} + runStatusEmitter.emit('stateChange', { + type: 'test-failed', + testFile: files[0] }); - runStatusEmitter.emit('error', { - file: files[0] + runStatusEmitter.emit('stateChange', { + type: 'uncaught-exception', + testFile: files[0] }); other = files[1]; }); return rerun(other).then(() => { - t.is(runStatus.previousFailCount, 2); + t.ok(api.run.calledTwice); + t.strictDeepEqual(api.run.secondCall.args, [[other], Object.assign({}, defaultApiOptions, { + previousFailures: 2, + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); test('tracks failures from multiple files', t => { - t.plan(1); + t.plan(2); let first; seed(files => { - runStatusEmitter.emit('test', { - file: files[0], - error: {} + runStatusEmitter.emit('stateChange', { + type: 'test-failed', + testFile: files[0] }); - runStatusEmitter.emit('error', {file: files[1]}); + runStatusEmitter.emit('stateChange', { + type: 'test-failed', + testFile: files[1] + }); first = files[0]; }); return rerun(first).then(() => { - t.is(runStatus.previousFailCount, 1); + t.ok(api.run.calledTwice); + t.strictDeepEqual(api.run.secondCall.args, [[first], Object.assign({}, defaultApiOptions, { + previousFailures: 1, + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); test('previous failures don\'t count when that file is rerun', t => { - t.plan(1); + t.plan(2); let same; seed(files => { - runStatusEmitter.emit('test', { - file: files[0], - error: {} + runStatusEmitter.emit('stateChange', { + type: 'test-failed', + testFile: files[0] }); - runStatusEmitter.emit('error', {file: files[0]}); + runStatusEmitter.emit('stateChange', { + type: 'uncaught-exception', + testFile: files[0] + }); same = files[0]; }); return rerun(same).then(() => { - t.is(runStatus.previousFailCount, 0); + t.ok(api.run.calledTwice); + t.strictDeepEqual(api.run.secondCall.args, [[same], Object.assign({}, defaultApiOptions, { + previousFailures: 0, + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); test('previous failures don\'t count when that file is deleted', t => { - t.plan(1); + t.plan(2); let same; let other; seed(files => { - runStatusEmitter.emit('test', { - file: files[0], - error: {} + runStatusEmitter.emit('stateChange', { + type: 'test-failed', + testFile: files[0] }); - runStatusEmitter.emit('error', {file: files[0]}); + runStatusEmitter.emit('stateChange', { + type: 'uncaught-exception', + testFile: files[0] + }); same = files[0]; other = files[1]; @@ -1414,7 +1564,12 @@ group('chokidar', (beforeEach, test, group) => { unlink(same); return debounce().then(() => rerun(other)).then(() => { - t.is(runStatus.previousFailCount, 0); + t.ok(api.run.calledTwice); + t.strictDeepEqual(api.run.secondCall.args, [[other], Object.assign({}, defaultApiOptions, { + previousFailures: 0, + clearLogOnNextRun: true, + runVector: 2 + })]); }); }); }); From b78c4ca83d42b849d2c68b57b405259c9d7bb2bf Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Wed, 21 Feb 2018 15:21:43 +0000 Subject: [PATCH 13/18] Remove visual tests They're covered by the integration tests for reporters. Tests shouldn't assume that AVA's reporters can preserve unbroken stdout/stderr output. However now that AVA reporters write to stdout, and the stdout/stderr from workers is directed to stderr, users can separate the output after the fact. --- package-lock.json | 50 ----------- package.json | 4 +- test/visual/console-log.js | 17 ---- test/visual/lorem-ipsum.js | 18 ---- test/visual/lorem-ipsum.txt | 5 -- test/visual/print-lorem-ipsum.js | 34 ------- test/visual/run-visual-tests.js | 100 --------------------- test/visual/stdout-write.js | 23 ----- test/visual/text-ends-at-terminal-width.js | 40 --------- 9 files changed, 1 insertion(+), 290 deletions(-) delete mode 100644 test/visual/console-log.js delete mode 100644 test/visual/lorem-ipsum.js delete mode 100644 test/visual/lorem-ipsum.txt delete mode 100644 test/visual/print-lorem-ipsum.js delete mode 100644 test/visual/run-visual-tests.js delete mode 100644 test/visual/stdout-write.js delete mode 100644 test/visual/text-ends-at-terminal-width.js diff --git a/package-lock.json b/package-lock.json index e1c81a707..bad508e28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3950,39 +3950,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, - "inquirer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.1.0.tgz", - "integrity": "sha512-kn7N70US1MSZHZHSGJLiZ7iCwwncc7b0gc68YtlX29OjI3Mp0tSVV+snVXpZ1G+ONS3Ac9zd1m6hve2ibLDYfA==", - "dev": true, - "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.1.0", - "external-editor": "2.1.0", - "figures": "2.0.0", - "lodash": "4.17.4", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "5.5.6", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - } - } - }, "invariant": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", @@ -7585,23 +7552,6 @@ "rx-lite": "4.0.8" } }, - "rxjs": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", - "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", - "dev": true, - "requires": { - "symbol-observable": "1.0.1" - }, - "dependencies": { - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - } - } - }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", diff --git a/package.json b/package.json index f008e722a..528293027 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,7 @@ "scripts": { "lint": "xo && (cd test/fixture && xo '**' '!{source-map-initial,syntax-error}.js' '!snapshots/test-sourcemaps/build/**')", "test": "npm run lint && flow check test/flow-types && tsc --noEmit -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" + "test-win": "tap --no-cov --reporter=classic --timeout=300 --jobs=4 test/*.js test/reporters/*.js" }, "files": [ "lib", @@ -150,7 +149,6 @@ "get-stream": "^3.0.0", "git-branch": "^1.0.0", "has-ansi": "^3.0.0", - "inquirer": "^5.1.0", "lolex": "^2.3.2", "nyc": "^11.6.0", "proxyquire": "^2.0.1", diff --git a/test/visual/console-log.js b/test/visual/console-log.js deleted file mode 100644 index cecbff389..000000000 --- a/test/visual/console-log.js +++ /dev/null @@ -1,17 +0,0 @@ -import delay from 'delay'; -import test from '../..'; - -test('testName1', async t => { - console.log('foo'); - await delay(2000); - console.log('baz'); - t.pass(); -}); - -test('testName2', async t => { - await delay(1000); - console.log('bar'); - await delay(2000); - console.log('quz'); - t.pass(); -}); diff --git a/test/visual/lorem-ipsum.js b/test/visual/lorem-ipsum.js deleted file mode 100644 index fc7c8596c..000000000 --- a/test/visual/lorem-ipsum.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; -const delay = require('delay'); -const test = require('../..'); -require('./print-lorem-ipsum'); // eslint-disable-line import/no-unassigned-import - -async function testFn(t) { - await delay(40); - t.pass(); -} - -async function failFn(t) { - await delay(40); - t.fail(); -} - -for (let i = 0; i < 400; i++) { - test.serial('test number ' + i, i === 125 ? failFn : testFn); -} diff --git a/test/visual/lorem-ipsum.txt b/test/visual/lorem-ipsum.txt deleted file mode 100644 index 63a146621..000000000 --- a/test/visual/lorem-ipsum.txt +++ /dev/null @@ -1,5 +0,0 @@ -Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. - -Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this. - -But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth. diff --git a/test/visual/print-lorem-ipsum.js b/test/visual/print-lorem-ipsum.js deleted file mode 100644 index fe1323fff..000000000 --- a/test/visual/print-lorem-ipsum.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); - -const text = fs.readFileSync(path.join(__dirname, 'lorem-ipsum.txt'), 'utf8'); -const lines = text.split(/\r?\n/g).map(line => line.split(' ')); - -setTimeout(() => { - let lineNum = 0; - let wordNum = 0; - - const interval = setInterval(() => { - if (lineNum >= lines.length) { - clearInterval(interval); - return; - } - - const line = lines[lineNum]; - if (wordNum >= line.length) { - process.stdout.write('\n'); - lineNum++; - wordNum = 0; - return; - } - - let word = line[wordNum]; - wordNum++; - if (wordNum < line.length) { - word += ' '; - } - - process.stdout.write(word); - }, 50); -}, 200); diff --git a/test/visual/run-visual-tests.js b/test/visual/run-visual-tests.js deleted file mode 100644 index c8e5c972a..000000000 --- a/test/visual/run-visual-tests.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; -require('loud-rejection/register'); // eslint-disable-line import/no-unassigned-import -const path = require('path'); -const childProcess = require('child_process'); -const chalk = require('chalk'); -const arrify = require('arrify'); -const inquirer = require('inquirer'); - -const cwd = path.resolve(__dirname, '../../'); - -function fixture(fixtureName) { - if (!path.extname(fixtureName)) { - fixtureName += '.js'; - } - - return path.join(__dirname, fixtureName); -} - -function exec(args) { - childProcess.spawnSync(process.execPath, ['cli.js'].concat(args), { - cwd, - stdio: 'inherit' - }); -} - -function run(name, args, message, question) { - console.log(chalk.cyan(`**BEGIN ${name}**`)); - exec(args); - console.log(chalk.cyan(`**END ${name}**\n`)); - console.log(arrify(message).join('\n') + '\n'); - - return inquirer.prompt([{ - type: 'confirm', - name: 'confirmed', - message: question || 'Does it appear correctly', - default: false - }]) - .then(data => { - if (!data.confirmed) { - throw new Error(arrify(args).join(' ') + ' failed'); - } - }); -} - -// Thunked version of run for promise handlers -function thenRun() { - const args = Array.prototype.slice.call(arguments); - return () => run.apply(null, args); -} - -run( - 'console.log() should not mess up mini reporter', - fixture('console-log'), - [ - 'The output should have four logged lines in the following order (no empty lines in between): ', - '', - ' foo', - ' bar', - ' baz', - ' quz', - '', - 'The mini reporter output (2 passes) should only appear at the end.' - ] -) - - .then(thenRun( - 'stdout.write() should not mess up the mini reporter', - fixture('stdout-write'), - [ - 'The output should have a single logged line: ', - '', - ' foo bar baz quz', - '', - 'The mini reporter output (3 passed) should only appear at the end' - ] - )) - - .then(thenRun( - 'stdout.write() of lines that are exactly the same width as the terminal', - fixture('text-ends-at-terminal-width'), - [ - 'The fixture runs twelve tests, each logging about half a line of text.', - 'The end result should be six lines of numbers, with no empty lines in between.', - 'Each line should fill the terminal completely left to right.', - '', - 'The mini reporter output (12 passed) should appear at the end.' - ] - )) - - .then(thenRun( - 'complex output', - fixture('lorem-ipsum'), - [ - 'You should see the entire contents of the Gettysburg address.', - 'Three paragraphs with a blank line in between each.', - 'There should be no other blank lines within the speech text.', - 'The test counter should display "399 passed 1 failed" at the bottom.' - ] - )); - diff --git a/test/visual/stdout-write.js b/test/visual/stdout-write.js deleted file mode 100644 index 884c5db62..000000000 --- a/test/visual/stdout-write.js +++ /dev/null @@ -1,23 +0,0 @@ -import delay from 'delay'; -import test from '../..'; - -test('testName1', async t => { - process.stdout.write('foo '); - await delay(2000); - process.stdout.write('baz '); - t.pass(); -}); - -test('testName2', async t => { - await delay(1000); - process.stdout.write('bar '); - await delay(2000); - process.stdout.write('quz '); - await delay(1000); - t.pass(); -}); - -test('testName3', async t => { - await delay(5000); - t.pass(); -}); diff --git a/test/visual/text-ends-at-terminal-width.js b/test/visual/text-ends-at-terminal-width.js deleted file mode 100644 index 24eca3b70..000000000 --- a/test/visual/text-ends-at-terminal-width.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-await-in-loop */ -import delay from 'delay'; -import test from '../..'; - -function writeFullWidth(even, adjust) { - return async function (t) { - await delay(200); - const len = Math[even ? 'floor' : 'ceil']((process.stdout.columns + adjust) / 2); - for (let i = 0; i < len; i++) { - process.stdout.write(String(i % 10)); - await delay(1); - } - await delay(200); - t.pass(); - }; -} - -// Line 1 (exactly full width) -test.serial(writeFullWidth(true, 0)); -test.serial(writeFullWidth(false, 0)); - -// Line 2 (one extra char on line 3) -test.serial(writeFullWidth(true, 1)); -test.serial(writeFullWidth(false, 1)); - -// Line 3 (ends one char short of complete width) -test.serial(writeFullWidth(true, -2)); -test.serial(writeFullWidth(false, -2)); - -// Line 4 (completes line 3 and ends the next line exactly complete width) -test.serial(writeFullWidth(true, 1)); -test.serial(writeFullWidth(false, 1)); - -// Line 5 (exact complete width) -test.serial(writeFullWidth(true, 0)); -test.serial(writeFullWidth(false, 0)); - -// Line 6 (exact complete width) -test.serial(writeFullWidth(true, 0)); -test.serial(writeFullWidth(false, 0)); From 493c01cfe6cb012a6e80e526edfd6e79ebc4a403 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 22 Feb 2018 15:55:23 +0000 Subject: [PATCH 14/18] Remove unnecessary try/catch from cli script --- cli.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cli.js b/cli.js index 58a13bab2..2114a9a14 100755 --- a/cli.js +++ b/cli.js @@ -7,10 +7,5 @@ const importLocal = require('import-local'); if (importLocal(__filename)) { debug('Using local install of AVA'); } else { - try { - require('./lib/cli').run(); - } catch (err) { - console.error(`\n ${err.message}`); - process.exit(1); - } + require('./lib/cli').run(); } From c232ce100f82939050fa8acf238194f7a7b8a26d Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 22 Feb 2018 17:01:38 +0000 Subject: [PATCH 15/18] Remove unused fixtures --- test/fixture/chalk-disabled.js | 6 ------ test/fixture/chalk-enabled.js | 6 ------ .../circular-reference-on-assertion.js | 7 ------- test/fixture/destructuring-public-api.js | 19 ------------------- test/fixture/empty.js | 8 -------- test/fixture/error-without-message.js | 5 ----- test/fixture/exclusive-nonexclusive.js | 9 --------- test/fixture/exclusive.js | 5 ----- test/fixture/fake-timers.js | 7 ------- test/fixture/hooks-failing.js | 10 ---------- test/fixture/hooks-passing.js | 11 ----------- test/fixture/immediate-0-exit.js | 2 -- test/fixture/immediate-3-exit.js | 2 -- test/fixture/loud-rejection.js | 9 --------- test/fixture/syntax-error.js | 5 ----- test/fixture/uncaught-exception.js | 8 -------- 16 files changed, 119 deletions(-) delete mode 100644 test/fixture/chalk-disabled.js delete mode 100644 test/fixture/chalk-enabled.js delete mode 100644 test/fixture/circular-reference-on-assertion.js delete mode 100644 test/fixture/destructuring-public-api.js delete mode 100644 test/fixture/empty.js delete mode 100644 test/fixture/error-without-message.js delete mode 100644 test/fixture/exclusive-nonexclusive.js delete mode 100644 test/fixture/exclusive.js delete mode 100644 test/fixture/fake-timers.js delete mode 100644 test/fixture/hooks-failing.js delete mode 100644 test/fixture/hooks-passing.js delete mode 100644 test/fixture/immediate-0-exit.js delete mode 100644 test/fixture/immediate-3-exit.js delete mode 100644 test/fixture/loud-rejection.js delete mode 100644 test/fixture/syntax-error.js delete mode 100644 test/fixture/uncaught-exception.js diff --git a/test/fixture/chalk-disabled.js b/test/fixture/chalk-disabled.js deleted file mode 100644 index 0204ac28b..000000000 --- a/test/fixture/chalk-disabled.js +++ /dev/null @@ -1,6 +0,0 @@ -import chalk from 'chalk'; -import test from '../..'; - -test('should not support colors', t => { - t.false(chalk.enabled); -}); diff --git a/test/fixture/chalk-enabled.js b/test/fixture/chalk-enabled.js deleted file mode 100644 index 0827cc370..000000000 --- a/test/fixture/chalk-enabled.js +++ /dev/null @@ -1,6 +0,0 @@ -import chalk from 'chalk'; -import test from '../..'; - -test('should support colors', t => { - t.true(chalk.enabled); -}); diff --git a/test/fixture/circular-reference-on-assertion.js b/test/fixture/circular-reference-on-assertion.js deleted file mode 100644 index 476e0f9f2..000000000 --- a/test/fixture/circular-reference-on-assertion.js +++ /dev/null @@ -1,7 +0,0 @@ -import test from '../..'; - -test('test', t => { - const circular = ['a', 'b']; - circular.push(circular); - t.deepEqual([circular, 'c'], [circular, 'd']); -}); diff --git a/test/fixture/destructuring-public-api.js b/test/fixture/destructuring-public-api.js deleted file mode 100644 index f8f6b3ac7..000000000 --- a/test/fixture/destructuring-public-api.js +++ /dev/null @@ -1,19 +0,0 @@ -import test from '../..'; - -test.beforeEach(t => { - t.context = 'bar'; -}); - -test.cb('callback mode', ({context: foo, ...t}) => { - t.is(foo, 'bar'); - t.end(); -}); - -test.cb('callback mode #2', ({context: foo, end, ...t}) => { - t.is(foo, 'bar'); - end(); -}); - -test('sync', ({context: foo, ...t}) => { - t.is(foo, 'bar'); -}); diff --git a/test/fixture/empty.js b/test/fixture/empty.js deleted file mode 100644 index 03d715446..000000000 --- a/test/fixture/empty.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - __ - ____ _____ _______/ |_ ___.__. - _/ __ \ / \\____ \ __< | | - \ ___/| Y Y \ |_> > | \___ | - \___ >__|_| / __/|__| / ____| - \/ \/|__| \/ - */ diff --git a/test/fixture/error-without-message.js b/test/fixture/error-without-message.js deleted file mode 100644 index 775933f27..000000000 --- a/test/fixture/error-without-message.js +++ /dev/null @@ -1,5 +0,0 @@ -import test from '../..'; - -test('throw an error without a message', () => { - throw new Error(); // eslint-disable-line unicorn/error-message -}); diff --git a/test/fixture/exclusive-nonexclusive.js b/test/fixture/exclusive-nonexclusive.js deleted file mode 100644 index c8260ef90..000000000 --- a/test/fixture/exclusive-nonexclusive.js +++ /dev/null @@ -1,9 +0,0 @@ -import test from '../..'; - -test.only('only', t => { - t.pass(); -}); - -test('test', t => { - t.fail(); -}); diff --git a/test/fixture/exclusive.js b/test/fixture/exclusive.js deleted file mode 100644 index d596e2609..000000000 --- a/test/fixture/exclusive.js +++ /dev/null @@ -1,5 +0,0 @@ -import test from '../..'; - -test.only('test', t => { - t.pass(); -}); diff --git a/test/fixture/fake-timers.js b/test/fixture/fake-timers.js deleted file mode 100644 index 576e765ff..000000000 --- a/test/fixture/fake-timers.js +++ /dev/null @@ -1,7 +0,0 @@ -import sinon from 'sinon'; -import test from '../..'; - -test('test', t => { - sinon.useFakeTimers(Date.now() + 10000); - t.pass(); -}); diff --git a/test/fixture/hooks-failing.js b/test/fixture/hooks-failing.js deleted file mode 100644 index 6876942aa..000000000 --- a/test/fixture/hooks-failing.js +++ /dev/null @@ -1,10 +0,0 @@ -import test from '../..'; - -test.beforeEach(fail); -test('pass', pass); - -function pass() {} - -function fail(t) { - t.fail(); -} diff --git a/test/fixture/hooks-passing.js b/test/fixture/hooks-passing.js deleted file mode 100644 index 4938ba919..000000000 --- a/test/fixture/hooks-passing.js +++ /dev/null @@ -1,11 +0,0 @@ -import test from '../..'; - -test.before(pass); -test.beforeEach(pass); -test.after(pass); -test.afterEach(pass); -test('pass', pass); - -function pass(t) { - t.pass(); -} diff --git a/test/fixture/immediate-0-exit.js b/test/fixture/immediate-0-exit.js deleted file mode 100644 index 0dfdf055b..000000000 --- a/test/fixture/immediate-0-exit.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -process.exit(0); // eslint-disable-line unicorn/no-process-exit diff --git a/test/fixture/immediate-3-exit.js b/test/fixture/immediate-3-exit.js deleted file mode 100644 index 3cf096b8b..000000000 --- a/test/fixture/immediate-3-exit.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -process.exit(3); // eslint-disable-line unicorn/no-process-exit diff --git a/test/fixture/loud-rejection.js b/test/fixture/loud-rejection.js deleted file mode 100644 index 08fbedb42..000000000 --- a/test/fixture/loud-rejection.js +++ /dev/null @@ -1,9 +0,0 @@ -import test from '../..'; - -test.cb('creates an unhandled rejection', t => { - Promise.reject(new Error('You can\'t handle this!')); - - setTimeout(() => { - t.end(); - }); -}); diff --git a/test/fixture/syntax-error.js b/test/fixture/syntax-error.js deleted file mode 100644 index b44af4aaa..000000000 --- a/test/fixture/syntax-error.js +++ /dev/null @@ -1,5 +0,0 @@ -import test from 'ava'; - -test.(t => { - t.pass(); -}); diff --git a/test/fixture/uncaught-exception.js b/test/fixture/uncaught-exception.js deleted file mode 100644 index 72f11c6f4..000000000 --- a/test/fixture/uncaught-exception.js +++ /dev/null @@ -1,8 +0,0 @@ -import test from '../..'; - -test('throw an uncaught exception', t => { - setImmediate(() => { - throw new Error(`Can't catch me!`); - }); - t.pass(); -}); From bb2a3cbfbe1a7c80985678ce4a8831808344bf4e Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 11 Mar 2018 16:26:08 +0000 Subject: [PATCH 16/18] Don't restart timeout timer when receiving events from timed out workers It can take longer for workers to exit than the timeout period, in which case restarting the timeout timer just causes it to fire again. --- api.js | 9 +++++++-- lib/fork.js | 1 + profile.js | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/api.js b/api.js index acc05d2cb..5342377eb 100644 --- a/api.js +++ b/api.js @@ -52,6 +52,7 @@ class Api extends Emittery { const failFast = apiOptions.failFast === true; let bailed = false; const pendingWorkers = new Set(); + const timedOutWorkerFiles = new Set(); let restartTimer; if (apiOptions.timeout) { const timeout = ms(apiOptions.timeout); @@ -64,6 +65,7 @@ class Api extends Emittery { } for (const worker of pendingWorkers) { + timedOutWorkerFiles.add(worker.file); worker.exit(); } @@ -98,8 +100,11 @@ class Api extends Emittery { } runStatus.on('stateChange', record => { - // Restart the timer whenever there is activity. - restartTimer(); + if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) { + // Restart the timer whenever there is activity from workers that + // haven't already timed out. + restartTimer(); + } if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) { // Prevent new test files from running once a test has failed. diff --git a/lib/fork.js b/lib/fork.js index 8795c7538..eb2399f2b 100644 --- a/lib/fork.js +++ b/lib/fork.js @@ -120,6 +120,7 @@ module.exports = (file, opts, execArgv) => { return emitter.on('stateChange', listener); }, + file, promise }; }; diff --git a/profile.js b/profile.js index 65278ffbd..8c6ba7437 100644 --- a/profile.js +++ b/profile.js @@ -110,6 +110,7 @@ babelConfigHelper.build(process.cwd(), cacheDir, babelConfigHelper.validate(conf const runStatus = new RunStatus([file]); runStatus.observeWorker({ + file, onStateChange(listener) { const emit = evt => listener(Object.assign(evt, {testFile: file})); process.send = data => { From 36d8224e3e812fda8a77d46b35c60a6cfbd8dec5 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Mon, 2 Apr 2018 18:45:48 +0100 Subject: [PATCH 17/18] Remove TAP job concurrency when running Windows tests The extra concurrency seems to particularly hurt the reporter integration tests. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 528293027..b78cab0d1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "lint": "xo && (cd test/fixture && xo '**' '!{source-map-initial,syntax-error}.js' '!snapshots/test-sourcemaps/build/**')", "test": "npm run lint && flow check test/flow-types && tsc --noEmit -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" + "test-win": "tap --no-cov --reporter=classic --timeout=300 --jobs=1 test/*.js test/reporters/*.js" }, "files": [ "lib", From 348a794ff123391389b9a0fed7e362c6aea1722d Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 8 Apr 2018 17:40:50 +0100 Subject: [PATCH 18/18] Suffer package-lock churn --- package-lock.json | 6025 +++++++++++++++++++++++++-------------------- 1 file changed, 3347 insertions(+), 2678 deletions(-) diff --git a/package-lock.json b/package-lock.json index bad508e28..db31fa553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -405,16 +405,6 @@ "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -749,27 +739,12 @@ } } }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "1.0.0" - } - }, "aproba": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", "optional": true }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, "are-we-there-yet": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", @@ -958,45 +933,6 @@ } } }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.4", - "source-map": "0.5.7", - "trim-right": "1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, "babel-plugin-espower": { "version": "3.0.0-beta.1", "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-3.0.0-beta.1.tgz", @@ -1011,103 +947,6 @@ "estraverse": "4.2.0" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.4.1", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.4" - }, - "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - } - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.2", - "lodash": "4.17.4" - }, - "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.4", - "to-fast-properties": "1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, "babylon": { "version": "7.0.0-beta.44", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", @@ -1339,26 +1178,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - }, - "dependencies": { - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - } - } - }, "chalk": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", @@ -1679,27 +1498,6 @@ "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", "dev": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - } - } - }, "clone": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", @@ -2105,26 +1903,6 @@ "core-assert": "0.2.1" } }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - } - } - }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -2204,15 +1982,6 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, "detect-libc": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", @@ -3533,12 +3302,6 @@ } } }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, "get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -3958,12 +3721,6 @@ "loose-envify": "1.3.1" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "irregular-plurals": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.2.0.tgz", @@ -4078,15 +3835,6 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -4489,15 +4237,6 @@ "set-getter": "0.1.0" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, "lcov-parse": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", @@ -4659,11 +4398,6 @@ "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", "dev": true }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", @@ -4749,15 +4483,6 @@ "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=" }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "1.1.0" - } - }, "meow": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.0.tgz", @@ -5248,7 +4973,7 @@ "micromatch": "2.3.11", "mkdirp": "0.5.1", "resolve-from": "2.0.0", - "rimraf": "2.6.1", + "rimraf": "2.6.2", "signal-exit": "3.0.2", "spawn-wrap": "1.4.2", "test-exclude": "4.2.1", @@ -5259,6 +4984,7 @@ "align-text": { "version": "0.1.4", "bundled": true, + "dev": true, "requires": { "kind-of": "3.2.2", "longest": "1.0.1", @@ -5272,20 +4998,44 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "ansi-styles": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } }, "arr-flatten": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "arr-union": { "version": "3.1.0", - "bundled": true + "bundled": true, + "dev": true }, "array-unique": { "version": "0.2.1", @@ -5299,7 +5049,8 @@ }, "assign-symbols": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "async": { "version": "1.5.2", @@ -5308,22 +5059,104 @@ }, "atob": { "version": "2.0.3", - "bundled": true + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.5" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.3", + "lodash": "4.17.5" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.5", + "to-fast-properties": "1.0.3" + } }, "babylon": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "bundled": true, "dev": true }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "bundled": true, + "dev": true }, "base": { "version": "0.11.2", "bundled": true, + "dev": true, "requires": { "cache-base": "1.0.1", "class-utils": "0.3.6", @@ -5337,31 +5170,46 @@ "define-property": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "is-descriptor": "1.0.2" } }, "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true } } }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, - "builtin-modules": { - "version": "1.1.1", - "bundled": true - }, - "cache-base": { + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "cache-base": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "collection-visit": "1.0.0", "component-emitter": "1.2.1", @@ -5376,17 +5224,32 @@ "dependencies": { "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true } } }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, "camelcase": { "version": "1.2.1", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "center-align": { "version": "0.1.3", "bundled": true, + "dev": true, + "optional": true, "requires": { "align-text": "0.1.4", "lazy-cache": "1.0.4" @@ -5394,8 +5257,8 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "bundled": true, + "dev": true, "requires": { "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", @@ -5407,6 +5270,7 @@ "class-utils": { "version": "0.3.6", "bundled": true, + "dev": true, "requires": { "arr-union": "3.1.0", "define-property": "0.2.5", @@ -5417,6 +5281,7 @@ "define-property": { "version": "0.2.5", "bundled": true, + "dev": true, "requires": { "is-descriptor": "0.1.6" } @@ -5424,6 +5289,7 @@ "is-accessor-descriptor": { "version": "0.1.6", "bundled": true, + "dev": true, "requires": { "kind-of": "3.2.2" }, @@ -5431,6 +5297,7 @@ "kind-of": { "version": "3.2.2", "bundled": true, + "dev": true, "requires": { "is-buffer": "1.1.6" } @@ -5440,6 +5307,7 @@ "is-data-descriptor": { "version": "0.1.4", "bundled": true, + "dev": true, "requires": { "kind-of": "3.2.2" }, @@ -5447,6 +5315,7 @@ "kind-of": { "version": "3.2.2", "bundled": true, + "dev": true, "requires": { "is-buffer": "1.1.6" } @@ -5456,6 +5325,7 @@ "is-descriptor": { "version": "0.1.6", "bundled": true, + "dev": true, "requires": { "is-accessor-descriptor": "0.1.6", "is-data-descriptor": "0.1.4", @@ -5464,17 +5334,21 @@ }, "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true }, "kind-of": { "version": "5.1.0", - "bundled": true + "bundled": true, + "dev": true } } }, "cliui": { "version": "2.1.0", "bundled": true, + "dev": true, + "optional": true, "requires": { "center-align": "0.1.3", "right-align": "0.1.3", @@ -5483,13 +5357,21 @@ "dependencies": { "wordwrap": { "version": "0.0.2", - "bundled": true + "bundled": true, + "dev": true, + "optional": true } } }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, "collection-visit": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "map-visit": "1.0.0", "object-visit": "1.0.1" @@ -5502,11 +5384,13 @@ }, "component-emitter": { "version": "1.2.1", - "bundled": true + "bundled": true, + "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true }, "convert-source-map": { "version": "1.5.1", @@ -5515,32 +5399,29 @@ }, "copy-descriptor": { "version": "0.1.1", - "bundled": true + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.3", + "bundled": true, + "dev": true }, "cross-spawn": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "bundled": true, "dev": true, "requires": { "lru-cache": "4.1.2", - "which": "1.2.14" + "which": "1.3.0" } }, "debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "bundled": true, + "dev": true, "requires": { "ms": "2.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "debug-log": { @@ -5550,15 +5431,26 @@ }, "decamelize": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "decode-uri-component": { "version": "0.2.0", - "bundled": true + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } }, "define-property": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "is-descriptor": "1.0.2", "isobject": "3.0.1" @@ -5566,28 +5458,41 @@ "dependencies": { "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true } } }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, "error-ex": { "version": "1.3.1", "bundled": true, + "dev": true, "requires": { "is-arrayish": "0.2.1" } }, "escape-string-regexp": { "version": "1.0.5", - "bundled": true + "bundled": true, + "dev": true }, "esutils": { "version": "2.0.2", - "bundled": true + "bundled": true, + "dev": true }, "execa": { "version": "0.7.0", "bundled": true, + "dev": true, "requires": { "cross-spawn": "5.1.0", "get-stream": "3.0.0", @@ -5601,10 +5506,11 @@ "cross-spawn": { "version": "5.1.0", "bundled": true, + "dev": true, "requires": { "lru-cache": "4.1.2", "shebang-command": "1.2.0", - "which": "1.2.14" + "which": "1.3.0" } } } @@ -5620,6 +5526,7 @@ "expand-range": { "version": "1.8.2", "bundled": true, + "dev": true, "requires": { "fill-range": "2.2.3" } @@ -5627,6 +5534,7 @@ "extend-shallow": { "version": "3.0.2", "bundled": true, + "dev": true, "requires": { "assign-symbols": "1.0.0", "is-extendable": "1.0.1" @@ -5635,6 +5543,7 @@ "is-extendable": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "is-plain-object": "2.0.4" } @@ -5657,6 +5566,7 @@ "fill-range": { "version": "2.2.3", "bundled": true, + "dev": true, "requires": { "is-number": "2.1.0", "isobject": "2.1.0", @@ -5685,11 +5595,13 @@ }, "for-in": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "for-own": { "version": "0.1.5", "bundled": true, + "dev": true, "requires": { "for-in": "1.0.2" } @@ -5706,6 +5618,7 @@ "fragment-cache": { "version": "0.2.1", "bundled": true, + "dev": true, "requires": { "map-cache": "0.2.2" } @@ -5717,15 +5630,18 @@ }, "get-caller-file": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "get-stream": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "get-value": { "version": "2.0.6", - "bundled": true + "bundled": true, + "dev": true }, "glob": { "version": "7.1.2", @@ -5743,6 +5659,7 @@ "glob-base": { "version": "0.3.0", "bundled": true, + "dev": true, "requires": { "glob-parent": "2.0.0", "is-glob": "2.0.1" @@ -5751,17 +5668,20 @@ "glob-parent": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "is-glob": "2.0.1" } }, "globals": { "version": "9.18.0", - "bundled": true + "bundled": true, + "dev": true }, "graceful-fs": { "version": "4.1.11", - "bundled": true + "bundled": true, + "dev": true }, "handlebars": { "version": "4.0.11", @@ -5787,6 +5707,7 @@ "has-ansi": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -5799,6 +5720,7 @@ "has-value": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "get-value": "2.0.6", "has-values": "1.0.0", @@ -5807,49 +5729,57 @@ "dependencies": { "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true } } }, "has-values": { "version": "1.0.0", "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, "dependencies": { - "ansi-regex": { + "is-number": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { "version": "3.2.2", "bundled": true, + "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-buffer": "1.1.6" } } } }, - "strip-ansi": { + "kind-of": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { - "camelcase": "4.1.0" + "is-buffer": "1.1.6" } } } }, "hosted-git-info": { "version": "2.6.0", - "bundled": true + "bundled": true, + "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true + "bundled": true, + "dev": true }, "inflight": { "version": "1.0.6", @@ -5860,34 +5790,53 @@ "wrappy": "1.0.2" } }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, "invert-kv": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-accessor-descriptor": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "kind-of": "6.0.2" }, "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true + "bundled": true, + "dev": true } } }, "is-arrayish": { "version": "0.2.1", - "bundled": true + "bundled": true, + "dev": true }, "is-buffer": { "version": "1.1.6", - "bundled": true + "bundled": true, + "dev": true }, "is-builtin-module": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "builtin-modules": "1.1.1" } @@ -5895,19 +5844,22 @@ "is-data-descriptor": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "kind-of": "6.0.2" }, "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true + "bundled": true, + "dev": true } } }, "is-descriptor": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "is-accessor-descriptor": "1.0.0", "is-data-descriptor": "1.0.0", @@ -5916,43 +5868,51 @@ "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true + "bundled": true, + "dev": true } } }, "is-dotfile": { "version": "1.0.3", - "bundled": true + "bundled": true, + "dev": true }, "is-equal-shallow": { "version": "0.1.3", "bundled": true, + "dev": true, "requires": { "is-primitive": "2.0.0" } }, "is-extendable": { "version": "0.1.1", - "bundled": true + "bundled": true, + "dev": true }, "is-extglob": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-finite": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "number-is-nan": "1.0.1" } }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-glob": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "is-extglob": "1.0.0" } @@ -5960,6 +5920,7 @@ "is-number": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "kind-of": "3.2.2" } @@ -5967,26 +5928,30 @@ "is-odd": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "is-number": "4.0.0" }, "dependencies": { "is-number": { "version": "4.0.0", - "bundled": true + "bundled": true, + "dev": true } } }, "is-plain-object": { "version": "2.0.4", "bundled": true, + "dev": true, "requires": { "isobject": "3.0.1" }, "dependencies": { "isobject": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -5997,31 +5962,38 @@ }, "is-primitive": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "is-utf8": { "version": "0.2.1", - "bundled": true + "bundled": true, + "dev": true }, "is-windows": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "isarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "isobject": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "isarray": "1.0.0" } @@ -6082,8 +6054,8 @@ "debug": "3.1.0", "istanbul-lib-coverage": "1.2.0", "mkdirp": "0.5.1", - "rimraf": "2.6.1", - "source-map": "0.5.6" + "rimraf": "2.6.2", + "source-map": "0.5.7" }, "dependencies": { "debug": { @@ -6106,26 +6078,32 @@ }, "js-tokens": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true }, "jsesc": { "version": "1.3.0", - "bundled": true + "bundled": true, + "dev": true }, "kind-of": { "version": "3.2.2", "bundled": true, + "dev": true, "requires": { "is-buffer": "1.1.6" } }, "lazy-cache": { "version": "1.0.4", - "bundled": true + "bundled": true, + "dev": true, + "optional": true }, "lcid": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "invert-kv": "1.0.0" } @@ -6133,6 +6111,7 @@ "load-json-file": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "graceful-fs": "4.1.11", "parse-json": "2.2.0", @@ -6159,15 +6138,18 @@ }, "lodash": { "version": "4.17.5", - "bundled": true + "bundled": true, + "dev": true }, "longest": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "loose-envify": { "version": "1.3.1", "bundled": true, + "dev": true, "requires": { "js-tokens": "3.0.2" } @@ -6175,6 +6157,7 @@ "lru-cache": { "version": "4.1.2", "bundled": true, + "dev": true, "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" @@ -6182,11 +6165,13 @@ }, "map-cache": { "version": "0.2.2", - "bundled": true + "bundled": true, + "dev": true }, "map-visit": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "object-visit": "1.0.1" } @@ -6207,6 +6192,7 @@ "mem": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "mimic-fn": "1.2.0" } @@ -6248,1486 +6234,2083 @@ }, "mimic-fn": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "bundled": true, + "dev": true }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "minimatch": { + "version": "3.0.4", + "bundled": true, "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "brace-expansion": "1.1.11" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "mixin-deep": { + "version": "1.3.1", + "bundled": true, "dev": true, "requires": { - "find-up": "1.1.2" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "is-extendable": { + "version": "1.0.1", + "bundled": true, "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "is-plain-object": "2.0.4" } } } }, - "remove-trailing-separator": { - "version": "1.1.0", - "bundled": true + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } }, - "resolve-from": { + "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "bundled": true, "dev": true }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + "nanomatch": { + "version": "1.2.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, "requires": { - "ansi-regex": "2.1.1" + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true, "requires": { - "is-utf8": "0.2.1" + "remove-trailing-separator": "1.1.0" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "bundled": true, + "dev": true, "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + } } }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==" + "object-visit": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true, "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "slide": "1.1.6" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } - } - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "obj-props": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.1.0.tgz", - "integrity": "sha1-YmMT+qRCvv1KROmgLDy2vek3tRE=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + }, + "object.pick": { + "version": "1.3.0", + "bundled": true, + "dev": true, "requires": { - "is-descriptor": "0.1.6" + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, "requires": { - "kind-of": "3.2.2" + "wrappy": "1.0.2" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, "requires": { - "kind-of": "3.2.2" + "minimist": "0.0.8", + "wordwrap": "0.0.3" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "observable-to-promise": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.5.0.tgz", - "integrity": "sha1-yCjw8NxH6fhq+KSXfF1VB2znqR8=", - "requires": { - "is-observable": "0.2.0", - "symbol-observable": "1.0.4" - }, - "dependencies": { - "is-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", - "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, "requires": { - "symbol-observable": "0.2.4" - }, - "dependencies": { - "symbol-observable": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", - "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=" - } + "p-try": "1.0.0" } - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "1.1.0" - } - }, - "open-editor": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/open-editor/-/open-editor-1.2.0.tgz", - "integrity": "sha1-dcoj8LdNSz9V7guKTg9cIyXrd18=", - "dev": true, - "requires": { - "env-editor": "0.3.1", - "line-column-path": "1.0.0", - "opn": "5.2.0" - } - }, - "opener": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", - "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", - "dev": true - }, - "opn": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", - "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "ora": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", - "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", - "requires": { - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-spinners": "1.1.0", - "log-symbols": "2.2.0", - "strip-ansi": "4.0.0", - "wcwidth": "1.0.1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, "dev": true, "requires": { - "lru-cache": "4.0.2", - "shebang-command": "1.2.0", - "which": "1.2.14" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "parse-json": { + "version": "2.2.0", + "bundled": true, "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "error-ex": "1.3.1" } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "own-or": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/own-or/-/own-or-1.0.0.tgz", - "integrity": "sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=", - "dev": true - }, - "own-or-env": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-or-env/-/own-or-env-1.0.1.tgz", - "integrity": "sha512-y8qULRbRAlL6x2+M0vIe7jJbJx/kmUTzYonRAa2ayesR2qWLswninkVyeJe4x3IEXhdgoNodzjQRKAoEs6Fmrw==", - "dev": true, - "requires": { - "own-or": "1.0.0" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=" - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "1.1.0" - } - }, - "p-map": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.1.1.tgz", - "integrity": "sha1-BfXkrpegaDcbwqXMhr+9vBnErno=", - "dev": true - }, - "package-hash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-2.0.0.tgz", - "integrity": "sha1-eK4ybIngWk2BO2hgGXevBcANKg0=", - "requires": { - "graceful-fs": "4.1.11", - "lodash.flattendeep": "4.4.0", - "md5-hex": "2.0.0", - "release-zalgo": "1.0.0" - } - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0", - "semver": "5.5.0" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "1.3.1" - } - }, - "parse-ms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", - "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + }, + "pascalcase": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "ret": { + "version": "0.1.15", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-regex": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "set-value": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "atob": "2.0.3", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "split-string": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "static-extend": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "3.1.9", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "braces": { + "version": "2.3.1", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "kind-of": "6.0.2", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "3.1.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, "dev": true - } - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "2.0.4" - } - }, - "pirates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-3.0.2.tgz", - "integrity": "sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==", - "requires": { - "node-modules-regexp": "1.0.0" - } - }, - "pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", - "requires": { - "find-up": "2.1.0", - "load-json-file": "4.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + }, + "to-object-path": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "to-regex": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "bundled": true, + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "bundled": true, + "dev": true + }, + "use": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, "requires": { "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "imurmurhash": "0.1.4", + "slide": "1.1.6" } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "dev": true, "requires": { - "error-ex": "1.3.1", - "json-parse-better-errors": "1.0.1" + "cliui": "4.0.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } } }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "yargs-parser": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } } } }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "requires": { - "find-up": "2.1.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "plur": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "requires": { - "irregular-plurals": "1.2.0" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, - "prettier": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.10.2.tgz", - "integrity": "sha512-TcdNoQIWFoHblurqqU6d1ysopjq7UX0oRcT/hJ8qvBAELiYWn+Ugf0AXdnzISEJ7vuhNnQ98N8jR8Sh53x4IZg==", - "dev": true - }, - "pretty-ms": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.1.0.tgz", - "integrity": "sha1-6crJx2v27lL+lC3ZxsQhMVOxKIE=", - "requires": { - "parse-ms": "1.0.1", - "plur": "2.1.2" - } - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "requires": { - "asap": "2.0.6" - } - }, - "prop-types": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", - "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", - "dev": true, - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" - } - }, - "proto-props": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-0.2.1.tgz", - "integrity": "sha1-XgHcJnWg3pq/p255nfozTW9IP0s=", - "dev": true - }, - "proxyquire": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.0.1.tgz", - "integrity": "sha512-fQr3VQrbdzHrdaDn3XuisVoJlJNDJizHAvUXw9IuXRR8BpV2x0N7LsCxrpJkeKfPbNjiNU/V5vc008cI0TmzzQ==", - "dev": true, - "requires": { - "fill-keys": "1.0.2", - "module-not-found-error": "1.0.1", - "resolve": "1.5.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "obj-props": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.1.0.tgz", + "integrity": "sha1-YmMT+qRCvv1KROmgLDy2vek3tRE=", "dev": true }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "1.1.5" - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "1.1.5" - } } } }, - "rc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", - "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "isobject": "3.0.1" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, - "react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.3.0.tgz", - "integrity": "sha512-Qh35tNbwY8SLFELkN3PCLO16EARV+lgcmNkQnoZXfzAF1ASRpeucZYUwBlBzsRAzTb7KyfBaLQ4/K/DLC6MYeA==", - "dev": true, - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.1" - } - }, - "react-is": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.3.0.tgz", - "integrity": "sha512-YOo+BNK2z8LiDxh2viaOklPqhwrMMsNPWnXTseOqJa8/ob8mv9aD9Z5FqqQnKNbIerBm+DoIwjAAAKcpdDo1/w==", - "dev": true - }, - "react-test-renderer": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.3.0.tgz", - "integrity": "sha512-7FAfgIT8Kvew36b2VFXnhMxjKkwAnicD1dyopq7Ot19WAhfJkyhYNwPzM1AnCfYccvEhO0x4FbqJETyMGQwgIg==", - "dev": true, - "requires": { - "fbjs": "0.8.16", - "object-assign": "4.1.1", - "prop-types": "15.6.1", - "react-is": "16.3.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.3.8", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.10.tgz", - "integrity": "sha512-HQEnnoV404e0EtwB9yNiuk2tJ+egeVC8Y9QBAxzDg8DBJt4BzRp+yQuIb/t3FIWkSTmIi+sgx7yVv/ZM0GNoqw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.2.10", - "set-immediate-shim": "1.0.1" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "requires": { - "indent-string": "3.2.0", - "strip-indent": "2.0.0" - } - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" - }, - "regenerate-unicode-properties": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-5.1.3.tgz", - "integrity": "sha512-Yjy6t7jFQczDhYE+WVm7pg6gWYE258q4sUkk9qDErwXJIqx7jU9jGrMFHutJK/SRfcg7MEkXjGaYiVlOZyev/A==", - "requires": { - "regenerate": "1.3.3" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regex-not": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", - "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", - "requires": { - "extend-shallow": "2.0.1" - } - }, - "regexpu-core": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.1.3.tgz", - "integrity": "sha512-mB+njEzO7oezA57IbQxxd6fVPOeWKDmnGvJ485CwmfNchjHe5jWwqKepapmzUEj41yxIAqOg+C4LbXuJlkiO8A==", - "requires": { - "regenerate": "1.3.3", - "regenerate-unicode-properties": "5.1.3", - "regjsgen": "0.3.0", - "regjsparser": "0.2.1", - "unicode-match-property-ecmascript": "1.0.3", - "unicode-match-property-value-ecmascript": "1.0.1" - } - }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "requires": { - "rc": "1.2.6", - "safe-buffer": "5.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "requires": { - "rc": "1.2.6" + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } } }, - "regjsgen": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.3.0.tgz", - "integrity": "sha1-DuSj6SdkMM2iXx54nqbBW4ewy0M=" - }, - "regjsparser": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.2.1.tgz", - "integrity": "sha1-w3h1U/rwTndcMCEC7zRtmVAA7Bw=", + "observable-to-promise": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/observable-to-promise/-/observable-to-promise-0.5.0.tgz", + "integrity": "sha1-yCjw8NxH6fhq+KSXfF1VB2znqR8=", "requires": { - "jsesc": "0.5.0" + "is-observable": "0.2.0", + "symbol-observable": "1.0.4" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "is-observable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", + "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", + "requires": { + "symbol-observable": "0.2.4" + }, + "dependencies": { + "symbol-observable": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", + "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=" + } + } } } }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "es6-error": "4.0.2" + "wrappy": "1.0.2" } }, - "remove-trailing-separator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", - "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=" - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "repeating": { + "onetime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "1.1.0" + } + }, + "open-editor": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/open-editor/-/open-editor-1.2.0.tgz", + "integrity": "sha1-dcoj8LdNSz9V7guKTg9cIyXrd18=", "dev": true, "requires": { - "is-finite": "1.0.2" + "env-editor": "0.3.1", + "line-column-path": "1.0.0", + "opn": "5.2.0" } }, - "replace-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/replace-string/-/replace-string-1.1.0.tgz", - "integrity": "sha1-hwYhF/gj/lgAwwa6yyz6NZuTX+o=", + "opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", "dev": true }, - "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "opn": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", + "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.2.1" - } - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.2.1" - } - } - } - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - } - } - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" - } - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "requires": { - "hoek": "4.2.1" - } - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "dev": true, - "requires": { - "punycode": "1.4.1" - } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - } + "is-wsl": "1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "ora": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz", + "integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==", + "requires": { + "chalk": "2.3.2", + "cli-cursor": "2.1.0", + "cli-spinners": "1.1.0", + "log-symbols": "2.2.0", + "strip-ansi": "4.0.0", + "wcwidth": "1.0.1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "own-or": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/own-or/-/own-or-1.0.0.tgz", + "integrity": "sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=", "dev": true }, - "require-main-filename": { + "own-or-env": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "resolved": "https://registry.npmjs.org/own-or-env/-/own-or-env-1.0.1.tgz", + "integrity": "sha512-y8qULRbRAlL6x2+M0vIe7jJbJx/kmUTzYonRAa2ayesR2qWLswninkVyeJe4x3IEXhdgoNodzjQRKAoEs6Fmrw==", + "dev": true, + "requires": { + "own-or": "1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "require-precompiled": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", - "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=" + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, + "p-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", + "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=" + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } + "p-limit": "1.1.0" } }, - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "p-map": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.1.1.tgz", + "integrity": "sha1-BfXkrpegaDcbwqXMhr+9vBnErno=", + "dev": true + }, + "package-hash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-2.0.0.tgz", + "integrity": "sha1-eK4ybIngWk2BO2hgGXevBcANKg0=", "requires": { - "path-parse": "1.0.5" + "graceful-fs": "4.1.11", + "lodash.flattendeep": "4.4.0", + "md5-hex": "2.0.0", + "release-zalgo": "1.0.0" } }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { - "resolve-from": "3.0.0" + "got": "6.7.1", + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0", + "semver": "5.5.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "parse-ms": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", + "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" }, "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true } } }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "pinkie": "2.0.4" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + "pirates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-3.0.2.tgz", + "integrity": "sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==", + "requires": { + "node-modules-regexp": "1.0.0" + } }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", "requires": { - "align-text": "0.1.4" + "find-up": "2.1.0", + "load-json-file": "4.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "1.3.1", + "json-parse-better-errors": "1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } } }, - "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "requires": { - "glob": "7.1.2" + "find-up": "2.1.0" } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", "dev": true, "requires": { - "is-promise": "2.1.0" + "find-up": "2.1.0" } }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, + "plur": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", + "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", "requires": { - "rx-lite": "4.0.8" + "irregular-plurals": "1.2.0" } }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "0.1.15" - } + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "requires": { - "semver": "5.5.0" - } + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, - "serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "prettier": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.10.2.tgz", + "integrity": "sha512-TcdNoQIWFoHblurqqU6d1ysopjq7UX0oRcT/hJ8qvBAELiYWn+Ugf0AXdnzISEJ7vuhNnQ98N8jR8Sh53x4IZg==", + "dev": true }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "pretty-ms": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.1.0.tgz", + "integrity": "sha1-6crJx2v27lL+lC3ZxsQhMVOxKIE=", "requires": { - "to-object-path": "0.3.0" + "parse-ms": "1.0.1", + "plur": "2.1.2" } }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, - "set-value": { + "progress": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, "requires": { - "shebang-regex": "1.0.0" + "asap": "2.0.6" } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sinon": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", - "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "prop-types": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", + "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", "dev": true, "requires": { - "@sinonjs/formatio": "2.0.0", - "diff": "3.5.0", - "lodash.get": "4.4.2", - "lolex": "2.3.2", - "nise": "1.3.2", - "supports-color": "5.3.0", - "type-detect": "4.0.8" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + "proto-props": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-0.2.1.tgz", + "integrity": "sha1-XgHcJnWg3pq/p255nfozTW9IP0s=", + "dev": true }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "proxyquire": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.0.1.tgz", + "integrity": "sha512-fQr3VQrbdzHrdaDn3XuisVoJlJNDJizHAvUXw9IuXRR8BpV2x0N7LsCxrpJkeKfPbNjiNU/V5vc008cI0TmzzQ==", + "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0" + "fill-keys": "1.0.2", + "module-not-found-error": "1.0.1", + "resolve": "1.5.0" } }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, - "snapdragon": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", - "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.6", - "source-map-resolve": "0.5.1", - "use": "2.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "0.1.6" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "3.2.2" }, @@ -7742,1068 +8325,1314 @@ } } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-buffer": "1.1.5" } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "rc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", + "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.3.0.tgz", + "integrity": "sha512-Qh35tNbwY8SLFELkN3PCLO16EARV+lgcmNkQnoZXfzAF1ASRpeucZYUwBlBzsRAzTb7KyfBaLQ4/K/DLC6MYeA==", + "dev": true, "requires": { - "kind-of": "3.2.2" + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.1" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "react-is": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.3.0.tgz", + "integrity": "sha512-YOo+BNK2z8LiDxh2viaOklPqhwrMMsNPWnXTseOqJa8/ob8mv9aD9Z5FqqQnKNbIerBm+DoIwjAAAKcpdDo1/w==", + "dev": true + }, + "react-test-renderer": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.3.0.tgz", + "integrity": "sha512-7FAfgIT8Kvew36b2VFXnhMxjKkwAnicD1dyopq7Ot19WAhfJkyhYNwPzM1AnCfYccvEhO0x4FbqJETyMGQwgIg==", + "dev": true, "requires": { - "hoek": "2.16.3" + "fbjs": "0.8.16", + "object-assign": "4.1.1", + "prop-types": "15.6.1", + "react-is": "16.3.0" } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "is-plain-obj": "1.1.0" + "load-json-file": "2.0.0", + "normalize-package-data": "2.3.8", + "path-type": "2.0.0" } }, - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } }, - "source-map-fixtures": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/source-map-fixtures/-/source-map-fixtures-2.1.0.tgz", - "integrity": "sha1-3o/egbhQnhVn0Kr9nJqOewW2mXQ=", - "dev": true + "readable-stream": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.10.tgz", + "integrity": "sha512-HQEnnoV404e0EtwB9yNiuk2tJ+egeVC8Y9QBAxzDg8DBJt4BzRp+yQuIb/t3FIWkSTmIi+sgx7yVv/ZM0GNoqw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } }, - "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "requires": { - "atob": "2.0.3", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.2.10", + "set-immediate-shim": "1.0.1" } }, - "source-map-support": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", - "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", "requires": { - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } + "indent-string": "3.2.0", + "strip-indent": "2.0.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" }, - "spawn-wrap": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", - "dev": true, + "regenerate-unicode-properties": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-5.1.3.tgz", + "integrity": "sha512-Yjy6t7jFQczDhYE+WVm7pg6gWYE258q4sUkk9qDErwXJIqx7jU9jGrMFHutJK/SRfcg7MEkXjGaYiVlOZyev/A==", "requires": { - "foreground-child": "1.5.6", - "mkdirp": "0.5.1", - "os-homedir": "1.0.2", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "which": "1.3.0" - }, - "dependencies": { - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - } + "regenerate": "1.3.3" } }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "requires": { - "spdx-license-ids": "1.2.2" + "is-equal-shallow": "0.1.3" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + "regex-not": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", + "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", + "requires": { + "extend-shallow": "2.0.1" + } + }, + "regexpu-core": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.1.3.tgz", + "integrity": "sha512-mB+njEzO7oezA57IbQxxd6fVPOeWKDmnGvJ485CwmfNchjHe5jWwqKepapmzUEj41yxIAqOg+C4LbXuJlkiO8A==", + "requires": { + "regenerate": "1.3.3", + "regenerate-unicode-properties": "5.1.3", + "regjsgen": "0.3.0", + "regjsparser": "0.2.1", + "unicode-match-property-ecmascript": "1.0.3", + "unicode-match-property-value-ecmascript": "1.0.1" + } }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "requires": { + "rc": "1.2.6", + "safe-buffer": "5.1.1" + } }, - "split-string": { + "registry-url": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { - "extend-shallow": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "2.0.4" - } - } + "rc": "1.2.6" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "regjsgen": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.3.0.tgz", + "integrity": "sha1-DuSj6SdkMM2iXx54nqbBW4ewy0M=" }, - "sshpk": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "regjsparser": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.2.1.tgz", + "integrity": "sha1-w3h1U/rwTndcMCEC7zRtmVAA7Bw=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "jsesc": "0.5.0" }, "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" } } }, - "stack-utils": { + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "requires": { + "es6-error": "4.0.2" + } + }, + "remove-trailing-separator": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=" + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", + "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=" }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "replace-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/replace-string/-/replace-string-1.1.0.tgz", + "integrity": "sha1-hwYhF/gj/lgAwwa6yyz6NZuTX+o=", + "dev": true + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, "requires": { - "is-descriptor": "0.1.6" + "hoek": "4.2.1" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, "requires": { - "kind-of": "3.2.2" + "boom": "5.2.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, "requires": { - "is-buffer": "1.1.5" + "hoek": "4.2.1" } } } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, "requires": { - "kind-of": "3.2.2" + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, "requires": { - "is-buffer": "1.1.5" + "delayed-stream": "1.0.0" } } } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "punycode": "1.4.1" } }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true } } }, - "string-width": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", - "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", + "require-precompiled": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/require-precompiled/-/require-precompiled-0.1.0.tgz", + "integrity": "sha1-WhtS63Dr7UPrmC6XTIWrWVceVvo=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "3.0.1" + "caller-path": "0.1.0", + "resolve-from": "1.0.1" }, "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true } } }, - "string_decoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", - "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "requires": { - "safe-buffer": "5.1.1" + "path-parse": "1.0.5" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "requires": { - "ansi-regex": "3.0.0" + "resolve-from": "3.0.0" }, "dependencies": { - "ansi-regex": { + "resolve-from": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" } } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-bom-buf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", - "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", - "requires": { - "is-utf8": "0.2.1" - } + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, - "strip-indent": { + "restore-cursor": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "supertap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz", - "integrity": "sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA==", - "requires": { - "arrify": "1.0.1", - "indent-string": "3.2.0", - "js-yaml": "3.10.0", - "serialize-error": "2.1.0", - "strip-ansi": "4.0.0" - } - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "requires": { - "has-flag": "3.0.0" - } - }, - "symbol-observable": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", - "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", - "dev": true, + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "requires": { - "ajv": "5.5.2", - "ajv-keywords": "2.1.1", - "chalk": "2.3.2", - "lodash": "4.17.4", - "slice-ansi": "1.0.0", - "string-width": "2.1.1" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - } + "onetime": "2.0.1", + "signal-exit": "3.0.2" } }, - "tap": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/tap/-/tap-11.1.3.tgz", - "integrity": "sha512-DwiAZiQWzALkGEbhhbdi+DqT2nqmqFAJZRvVSnHBpA8khjf7+6kcO76Bx39JA7kclIfQ9Uro1rO4dgP1AWUovw==", - "dev": true, - "requires": { - "bind-obj-methods": "2.0.0", - "bluebird": "3.5.1", - "clean-yaml-object": "0.1.0", - "color-support": "1.1.3", - "coveralls": "3.0.0", - "foreground-child": "1.5.6", - "fs-exists-cached": "1.0.0", - "function-loop": "1.0.1", - "glob": "7.1.2", - "isexe": "2.0.0", - "js-yaml": "3.11.0", - "minipass": "2.2.4", - "mkdirp": "0.5.1", - "nyc": "11.6.0", - "opener": "1.4.3", - "os-homedir": "1.0.2", - "own-or": "1.0.0", - "own-or-env": "1.0.1", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "source-map-support": "0.5.4", - "stack-utils": "1.0.1", - "tap-mocha-reporter": "3.0.7", - "tap-parser": "7.0.0", - "tmatch": "3.1.0", - "trivial-deferred": "1.0.1", - "tsame": "1.1.2", - "write-file-atomic": "2.3.0", - "yapool": "1.0.0" - }, - "dependencies": { - "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" - } - } + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "requires": { + "glob": "7.1.2" } }, - "tap-mocha-reporter": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.7.tgz", - "integrity": "sha512-GHVXJ38C3oPRpM3YUc43JlGdpVZYiKeT1fmAd3HH2+J+ZWwsNAUFvRRdoGsXLw9+gU9o+zXpBqhS/oXyRQYwlA==", + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "color-support": "1.1.3", - "debug": "2.6.9", - "diff": "1.4.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "js-yaml": "3.10.0", - "readable-stream": "2.2.10", - "tap-parser": "5.4.0", - "unicode-length": "1.0.3" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "tap-parser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", - "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", - "dev": true, - "requires": { - "events-to-array": "1.1.2", - "js-yaml": "3.10.0", - "readable-stream": "2.2.10" - } - } + "is-promise": "2.1.0" } }, - "tap-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-7.0.0.tgz", - "integrity": "sha512-05G8/LrzqOOFvZhhAk32wsGiPZ1lfUrl+iV7+OkKgfofZxiceZWMHkKmow71YsyVQ8IvGBP2EjcIjE5gL4l5lA==", + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", "dev": true, "requires": { - "events-to-array": "1.1.2", - "js-yaml": "3.10.0", - "minipass": "2.2.4" + "rx-lite": "4.0.8" } }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "ret": "0.1.15" } }, - "tar-pack": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", - "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", - "optional": true, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { - "debug": "2.6.9", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.10", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } + "semver": "5.5.0" } }, - "temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" }, - "temp-write": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz", - "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=", - "dev": true, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "optional": true + }, + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", "requires": { - "graceful-fs": "4.1.11", - "is-stream": "1.1.0", - "make-dir": "1.2.0", - "pify": "3.0.0", - "temp-dir": "1.0.0", - "uuid": "3.0.1" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "to-object-path": "0.3.0" } }, - "term-size": { + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { - "execa": "0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "4.0.2", - "shebang-command": "1.2.0", - "which": "1.2.14" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - } + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sinon": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "dev": true, + "requires": { + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.3.2", + "nise": "1.3.2", + "supports-color": "5.3.0", + "type-detect": "4.0.8" } }, - "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", - "dev": true, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "requires": { - "arrify": "1.0.1", - "micromatch": "3.1.10", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "require-main-filename": "1.0.1" + "is-fullwidth-code-point": "2.0.0" + } + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + }, + "snapdragon": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", + "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.6", + "source-map-resolve": "0.5.1", + "use": "2.0.2" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.1", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } }, "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "0.1.6" } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" + "kind-of": "3.2.2" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-extendable": "0.1.1" + "is-buffer": "1.1.5" } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-buffer": "1.1.5" } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true } } }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "3.2.2" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "1.1.0" + } + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + }, + "source-map-fixtures": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/source-map-fixtures/-/source-map-fixtures-2.1.0.tgz", + "integrity": "sha1-3o/egbhQnhVn0Kr9nJqOewW2mXQ=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "requires": { + "atob": "2.0.3", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "requires": { + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "3.0.2" + }, + "dependencies": { "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "1.0.0", "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" + "is-plain-object": "2.0.4" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-extendable": "0.1.1" + "is-buffer": "1.1.5" } } } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "kind-of": "3.2.2" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-extendable": "0.1.1" + "is-buffer": "1.1.5" } } } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "string-width": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", + "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "string_decoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-bom-buf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supertap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz", + "integrity": "sha512-HZJ3geIMPgVwKk2VsmO5YHqnnJYl6bV5A9JW2uzqV43WmpgliNEYbuvukfor7URpaqpxuw3CfZ3ONdVbZjCgIA==", + "requires": { + "arrify": "1.0.1", + "indent-string": "3.2.0", + "js-yaml": "3.10.0", + "serialize-error": "2.1.0", + "strip-ansi": "4.0.0" + } + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "requires": { + "has-flag": "3.0.0" + } + }, + "symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.3.2", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + } + } + }, + "tap": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/tap/-/tap-11.1.3.tgz", + "integrity": "sha512-DwiAZiQWzALkGEbhhbdi+DqT2nqmqFAJZRvVSnHBpA8khjf7+6kcO76Bx39JA7kclIfQ9Uro1rO4dgP1AWUovw==", + "dev": true, + "requires": { + "bind-obj-methods": "2.0.0", + "bluebird": "3.5.1", + "clean-yaml-object": "0.1.0", + "color-support": "1.1.3", + "coveralls": "3.0.0", + "foreground-child": "1.5.6", + "fs-exists-cached": "1.0.0", + "function-loop": "1.0.1", + "glob": "7.1.2", + "isexe": "2.0.0", + "js-yaml": "3.11.0", + "minipass": "2.2.4", + "mkdirp": "0.5.1", + "nyc": "11.6.0", + "opener": "1.4.3", + "os-homedir": "1.0.2", + "own-or": "1.0.0", + "own-or-env": "1.0.1", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "source-map-support": "0.5.4", + "stack-utils": "1.0.1", + "tap-mocha-reporter": "3.0.7", + "tap-parser": "7.0.0", + "tmatch": "3.1.0", + "trivial-deferred": "1.0.1", + "tsame": "1.1.2", + "write-file-atomic": "2.3.0", + "yapool": "1.0.0" + }, + "dependencies": { + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "argparse": "1.0.9", + "esprima": "4.0.0" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - } + "glob": "7.1.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", "dev": true, "requires": { "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.0", - "snapdragon": "0.8.1", - "to-regex": "3.0.2" + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + } + } + }, + "tap-mocha-reporter": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.7.tgz", + "integrity": "sha512-GHVXJ38C3oPRpM3YUc43JlGdpVZYiKeT1fmAd3HH2+J+ZWwsNAUFvRRdoGsXLw9+gU9o+zXpBqhS/oXyRQYwlA==", + "dev": true, + "requires": { + "color-support": "1.1.3", + "debug": "2.6.9", + "diff": "1.4.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "js-yaml": "3.10.0", + "readable-stream": "2.2.10", + "tap-parser": "5.4.0", + "unicode-length": "1.0.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "ms": "2.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "tap-parser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", + "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.3.8", - "path-type": "1.1.0" + "events-to-array": "1.1.2", + "js-yaml": "3.10.0", + "readable-stream": "2.2.10" } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, + } + } + }, + "tap-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-7.0.0.tgz", + "integrity": "sha512-05G8/LrzqOOFvZhhAk32wsGiPZ1lfUrl+iV7+OkKgfofZxiceZWMHkKmow71YsyVQ8IvGBP2EjcIjE5gL4l5lA==", + "dev": true, + "requires": { + "events-to-array": "1.1.2", + "js-yaml": "3.10.0", + "minipass": "2.2.4" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", + "optional": true, + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.10", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "optional": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "ms": "2.0.0" } }, - "strip-bom": { + "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true + } + } + }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "dev": true + }, + "temp-write": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz", + "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "is-stream": "1.1.0", + "make-dir": "1.2.0", + "pify": "3.0.0", + "temp-dir": "1.0.0", + "uuid": "3.0.1" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "is-utf8": "0.2.1" + "lru-cache": "4.0.2", + "shebang-command": "1.2.0", + "which": "1.2.14" } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - }, - "dependencies": { - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - } + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } } } @@ -9051,47 +9880,6 @@ "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", "dev": true }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "0.5.6", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", @@ -9407,12 +10195,6 @@ "isexe": "2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "wide-align": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", @@ -9472,56 +10254,12 @@ } } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -9774,12 +10512,6 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", @@ -9791,69 +10523,6 @@ "integrity": "sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o=", "dev": true }, - "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", - "dev": true, - "requires": { - "cliui": "4.0.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.0.0", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" - }, - "dependencies": { - "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - } - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - }, "zen-observable": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.8.tgz",