diff --git a/lib/assert.js b/lib/assert.js index 17291f4db..cce42f324 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -16,6 +16,8 @@ class AssertionError extends Error { this.name = 'AssertionError'; this.assertion = opts.assertion; + this.fixedSource = opts.fixedSource; + this.improperUsage = opts.improperUsage || false; this.operator = opts.operator; this.values = opts.values || []; @@ -129,6 +131,7 @@ function wrapAssertions(callbacks) { } else if (typeof fn !== 'function') { fail(this, new AssertionError({ assertion: 'throws', + improperUsage: true, message: '`t.throws()` must be called with a function, Promise, or Observable', values: [formatAssertError.formatWithLabel('Called with:', fn)] })); @@ -199,6 +202,7 @@ function wrapAssertions(callbacks) { } else if (typeof fn !== 'function') { fail(this, new AssertionError({ assertion: 'notThrows', + improperUsage: true, message: '`t.notThrows()` must be called with a function, Promise, or Observable', values: [formatAssertError.formatWithLabel('Called with:', fn)] })); @@ -314,6 +318,7 @@ function wrapAssertions(callbacks) { if (typeof string !== 'string') { throw new AssertionError({ assertion: 'regex', + improperUsage: true, message: '`t.regex()` must be called with a string', values: [formatAssertError.formatWithLabel('Called with:', string)] }); @@ -321,6 +326,7 @@ function wrapAssertions(callbacks) { if (!(regex instanceof RegExp)) { throw new AssertionError({ assertion: 'regex', + improperUsage: true, message: '`t.regex()` must be called with a regular expression', values: [formatAssertError.formatWithLabel('Called with:', regex)] }); @@ -342,6 +348,7 @@ function wrapAssertions(callbacks) { if (typeof string !== 'string') { throw new AssertionError({ assertion: 'notRegex', + improperUsage: true, message: '`t.notRegex()` must be called with a string', values: [formatAssertError.formatWithLabel('Called with:', string)] }); @@ -349,6 +356,7 @@ function wrapAssertions(callbacks) { if (!(regex instanceof RegExp)) { throw new AssertionError({ assertion: 'notRegex', + improperUsage: true, message: '`t.notRegex()` must be called with a regular expression', values: [formatAssertError.formatWithLabel('Called with:', regex)] }); diff --git a/lib/main.js b/lib/main.js index 69e6f25bc..607233f20 100644 --- a/lib/main.js +++ b/lib/main.js @@ -13,8 +13,7 @@ const runner = new Runner({ match: opts.match }); -// Note that test files have require('ava') -worker.avaRequired = true; +worker.setRunner(runner); // If fail-fast is enabled, use this variable to detect // that no more tests should be logged diff --git a/lib/reporters/improper-usage-messages.js b/lib/reporters/improper-usage-messages.js new file mode 100644 index 000000000..0a2626638 --- /dev/null +++ b/lib/reporters/improper-usage-messages.js @@ -0,0 +1,21 @@ +'use strict'; +const chalk = require('chalk'); + +exports.forError = error => { + if (!error.improperUsage) { + return null; + } + + const assertion = error.assertion; + if (assertion !== 'throws' || !assertion === 'notThrows') { + return null; + } + + return `Try wrapping the first argument to \`t.${assertion}()\` in a function: + + ${chalk.cyan(`t.${assertion}(() => { `)}${chalk.grey('/* your code here */')}${chalk.cyan(' })')} + +Visit the following URL for more details: + + ${chalk.blue.underline('https://github.com/avajs/ava#throwsfunctionpromise-error-message')}`; +}; diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index 38bfe468f..df481a76a 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -12,6 +12,7 @@ const formatAssertError = require('../format-assert-error'); const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); +const improperUsageMessages = require('./improper-usage-messages'); // TODO(@jamestalamge): This should be fixed in log-update and ansi-escapes once we are confident it's a good solution. const CSI = '\u001B['; @@ -191,15 +192,20 @@ class MiniReporter { } } + if (test.error.message) { + status += '\n' + indentString(test.error.message, 2) + '\n'; + } + if (test.error.avaAssertionError) { const formatted = formatAssertError.formatSerializedError(test.error); if (formatted) { status += '\n' + indentString(formatted, 2); } - } - if (test.error.message) { - status += '\n' + indentString(test.error.message, 2) + '\n'; + const message = improperUsageMessages.forError(test.error); + if (message) { + status += '\n' + indentString(message, 2) + '\n'; + } } if (test.error.stack) { diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index d9b914fe4..1be43ce5e 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -8,6 +8,7 @@ const formatAssertError = require('../format-assert-error'); const extractStack = require('../extract-stack'); const codeExcerpt = require('../code-excerpt'); const colors = require('../colors'); +const improperUsageMessages = require('./improper-usage-messages'); class VerboseReporter { constructor(options) { @@ -111,15 +112,20 @@ class VerboseReporter { } } + if (test.error.message) { + output += '\n' + indentString(test.error.message, 2) + '\n'; + } + if (test.error.avaAssertionError) { const formatted = formatAssertError.formatSerializedError(test.error); if (formatted) { output += '\n' + indentString(formatted, 2); } - } - if (test.error.message) { - output += '\n' + indentString(test.error.message, 2) + '\n'; + const message = improperUsageMessages.forError(test.error); + if (message) { + output += '\n' + indentString(message, 2) + '\n'; + } } if (test.error.stack) { diff --git a/lib/runner.js b/lib/runner.js index 9df270a5c..b090db491 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -148,6 +148,9 @@ class Runner extends EventEmitter { return Promise.resolve(this.tests.build().run()).then(this._buildStats); } + attributeLeakedError(err) { + return this.tests.attributeLeakedError(err); + } } optionChain(chainableMethods, function (opts, args) { diff --git a/lib/serialize-error.js b/lib/serialize-error.js index a0053ac36..55717e161 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -15,13 +15,15 @@ function filter(propertyName, isRoot) { } const stackUtils = new StackUtils(); -function buildSource(stack) { +function extractSource(stack) { if (!stack) { return null; } const firstStackLine = extractStack(stack).split('\n')[0]; - const source = stackUtils.parseLine(firstStackLine); + return stackUtils.parseLine(firstStackLine); +} +function buildSource(source) { if (!source) { return null; } @@ -49,21 +51,29 @@ module.exports = error => { const stack = typeof error.stack === 'string' ? beautifyStack(error.stack) : null; - const source = buildSource(stack); + const retval = { avaAssertionError: isAvaAssertionError(error), - source + source: buildSource(extractSource(stack)) }; if (stack) { retval.stack = stack; } if (retval.avaAssertionError) { + retval.improperUsage = error.improperUsage; retval.message = error.message; retval.name = error.name; retval.statements = error.statements; retval.values = error.values; + if (error.fixedSource) { + const source = buildSource(error.fixedSource); + if (source) { + retval.source = source; + } + } + if (error.assertion) { retval.assertion = error.assertion; } diff --git a/lib/test-collection.js b/lib/test-collection.js index 6d1d9a6ce..fedf9f93e 100644 --- a/lib/test-collection.js +++ b/lib/test-collection.js @@ -28,6 +28,8 @@ class TestCollection extends EventEmitter { afterEachAlways: [] }; + this.pendingTestInstances = new Set(); + this._emitTestResult = this._emitTestResult.bind(this); } add(test) { @@ -100,8 +102,9 @@ class TestCollection extends EventEmitter { } }; } - _emitTestResult(test) { - this.emit('test', test); + _emitTestResult(result) { + this.pendingTestInstances.delete(result.result); + this.emit('test', result); } _buildHooks(hooks, testTitle, context) { return hooks.map(hook => { @@ -125,7 +128,7 @@ class TestCollection extends EventEmitter { contextRef = null; } - return new Test({ + const test = new Test({ contextRef, failWithoutAssertions: false, fn: hook.fn, @@ -133,13 +136,15 @@ class TestCollection extends EventEmitter { onResult: this._emitTestResult, title }); + this.pendingTestInstances.add(test); + return test; } _buildTest(test, contextRef) { if (!contextRef) { contextRef = null; } - return new Test({ + test = new Test({ contextRef, failWithoutAssertions: this.failWithoutAssertions, fn: test.fn, @@ -147,6 +152,8 @@ class TestCollection extends EventEmitter { onResult: this._emitTestResult, title: test.title }); + this.pendingTestInstances.add(test); + return test; } _buildTestWithHooks(test) { if (test.metadata.skipped || test.metadata.todo) { @@ -183,6 +190,14 @@ class TestCollection extends EventEmitter { } return finalTests; } + attributeLeakedError(err) { + for (const test of this.pendingTestInstances) { + if (test.attributeLeakedError(err)) { + return true; + } + } + return false; + } } module.exports = TestCollection; diff --git a/lib/test-worker.js b/lib/test-worker.js index 3f48c4bad..2df7f745d 100644 --- a/lib/test-worker.js +++ b/lib/test-worker.js @@ -24,7 +24,6 @@ const isObj = require('is-obj'); const adapter = require('./process-adapter'); const globals = require('./globals'); const serializeError = require('./serialize-error'); -const throwsHelper = require('./throws-helper'); const opts = adapter.opts; const testPath = opts.file; @@ -41,20 +40,38 @@ adapter.installPrecompilerHook(); const dependencies = []; adapter.installDependencyTracking(dependencies, testPath); -// Check if test files required ava and show error, when they didn't -exports.avaRequired = false; +// Set when main.js is required (since test files should have `require('ava')`). +let runner = null; +exports.setRunner = newRunner => { + runner = newRunner; +}; require(testPath); // eslint-disable-line import/no-dynamic-require // If AVA was not required, show an error -if (!exports.avaRequired) { +if (!runner) { adapter.send('no-tests', {avaRequired: false}); } -process.on('unhandledRejection', throwsHelper); +function attributeLeakedError(err) { + if (!runner) { + return false; + } + + return runner.attributeLeakedError(err); +} + +const attributedRejections = new Set(); +process.on('unhandledRejection', (reason, promise) => { + if (attributeLeakedError(reason)) { + attributedRejections.add(promise); + } +}); process.on('uncaughtException', exception => { - throwsHelper(exception); + if (attributeLeakedError(exception)) { + return; + } let serialized; try { @@ -85,7 +102,8 @@ process.on('ava-teardown', () => { } tearingDown = true; - let rejections = currentlyUnhandled(); + let rejections = currentlyUnhandled() + .filter(rejection => !attributedRejections.has(rejection.promise)); if (rejections.length > 0) { rejections = rejections.map(rejection => { diff --git a/lib/test.js b/lib/test.js index 241b5f1ac..026285cb0 100644 --- a/lib/test.js +++ b/lib/test.js @@ -8,7 +8,6 @@ const plur = require('plur'); const assert = require('./assert'); const formatAssertError = require('./format-assert-error'); const globals = require('./globals'); -const throwsHelper = require('./throws-helper'); class SkipApi { constructor(test) { @@ -60,6 +59,13 @@ class ExecutionContext { contextRef.context = context; } + + _throwsArgStart(assertion, file, line) { + this._test.trackThrows({assertion, file, line}); + } + _throwsArgEnd() { + this._test.trackThrows(null); + } } Object.defineProperty(ExecutionContext.prototype, 'context', {enumerable: true}); @@ -101,9 +107,11 @@ class Test { this.calledEnd = false; this.duration = null; this.endCallbackFinisher = null; + this.finishDueToAttributedError = null; this.finishDueToInactivity = null; this.finishing = false; this.pendingAssertions = []; + this.pendingThrowsAssertion = null; this.planCount = null; this.startedAt = 0; } @@ -204,6 +212,60 @@ class Test { } } + trackThrows(pending) { + this.pendingThrowsAssertion = pending; + } + + detectImproperThrows(err) { + if (!this.pendingThrowsAssertion) { + return false; + } + + const pending = this.pendingThrowsAssertion; + this.pendingThrowsAssertion = null; + + const values = []; + if (err) { + values.push(formatAssertError.formatWithLabel(`The following error was thrown, possibly before \`t.${pending.assertion}()\` could be called:`, err)); + } + + this.saveFirstError(new assert.AssertionError({ + assertion: pending.assertion, + fixedSource: {file: pending.file, line: pending.line}, + improperUsage: true, + message: `Improper usage of \`t.${pending.assertion}()\` detected`, + stack: err instanceof Error && err.stack, + values + })); + return true; + } + + waitForPendingThrowsAssertion() { + return new Promise(resolve => { + this.finishDueToAttributedError = () => { + resolve(this.finishPromised()); + }; + + this.finishDueToInactivity = () => { + this.detectImproperThrows(); + resolve(this.finishPromised()); + }; + + // Wait up to a second to see if an error can be attributed to the + // pending assertion. + globals.setTimeout(() => this.finishDueToInactivity(), 1000).unref(); + }); + } + + attributeLeakedError(err) { + if (!this.detectImproperThrows(err)) { + return false; + } + + this.finishDueToAttributedError(); + return true; + } + callFn() { try { return { @@ -223,13 +285,13 @@ class Test { const result = this.callFn(); if (!result.ok) { - throwsHelper(result.error); - - this.saveFirstError(new assert.AssertionError({ - message: 'Error thrown in test', - stack: result.error instanceof Error && result.error.stack, - values: [formatAssertError.formatWithLabel('Error:', result.error)] - })); + if (!this.detectImproperThrows(result.error)) { + this.saveFirstError(new assert.AssertionError({ + message: 'Error thrown in test', + stack: result.error instanceof Error && result.error.stack, + values: [formatAssertError.formatWithLabel('Error:', result.error)] + })); + } return this.finish(); } @@ -260,6 +322,10 @@ class Test { resolve(this.finishPromised()); }; + this.finishDueToAttributedError = () => { + resolve(this.finishPromised()); + }; + this.finishDueToInactivity = () => { this.saveFirstError(new Error('`t.end()` was never called')); resolve(this.finishPromised()); @@ -269,6 +335,10 @@ class Test { if (promise) { return new Promise(resolve => { + this.finishDueToAttributedError = () => { + resolve(this.finishPromised()); + }; + this.finishDueToInactivity = () => { const err = returnedObservable ? new Error('Observable returned by test never completed') : @@ -279,13 +349,13 @@ class Test { promise .catch(err => { - throwsHelper(err); - - this.saveFirstError(new assert.AssertionError({ - message: 'Rejected promise returned by test', - stack: err instanceof Error && err.stack, - values: [formatAssertError.formatWithLabel('Rejection reason:', err)] - })); + if (!this.detectImproperThrows(err)) { + this.saveFirstError(new assert.AssertionError({ + message: 'Rejected promise returned by test', + stack: err instanceof Error && err.stack, + values: [formatAssertError.formatWithLabel('Rejection reason:', err)] + })); + } }) .then(() => resolve(this.finishPromised())); }); @@ -296,6 +366,11 @@ class Test { finish() { this.finishing = true; + + if (!this.assertError && this.pendingThrowsAssertion) { + return this.waitForPendingThrowsAssertion(); + } + this.verifyPlan(); this.verifyAssertions(); diff --git a/lib/throws-helper.js b/lib/throws-helper.js deleted file mode 100644 index 8d2c10165..000000000 --- a/lib/throws-helper.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; -const fs = require('fs'); -const path = require('path'); -const chalk = require('chalk'); -const globals = require('./globals'); - -module.exports = error => { - if (!error || !error._avaThrowsHelperData) { - return; - } - - const data = error._avaThrowsHelperData; - const codeFrame = require('babel-code-frame'); - let frame = ''; - - try { - const rawLines = fs.readFileSync(data.filename, 'utf8'); - frame = codeFrame(rawLines, data.line, data.column, {highlightCode: true}); - } catch (err) { - console.warn(err); - } - - console.error( - [ - 'Improper usage of t.throws detected at ' + chalk.bold.yellow('%s (%d:%d)') + ':', - frame, - 'The first argument to t.throws should be wrapped in a function:', - chalk.cyan(' t.throws(function() {\n %s\n })'), - 'Visit the following URL for more details:', - ' ' + chalk.blue.underline('https://github.com/avajs/ava#throwsfunctionpromise-error-message') - ].join('\n\n'), - path.relative(globals.options.baseDir, data.filename), - data.line, - data.column, - data.source - ); -}; diff --git a/package.json b/package.json index fbc82fb91..ee945df7f 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ ], "dependencies": { "@ava/babel-preset-stage-4": "^1.0.0", - "@ava/babel-preset-transform-test-files": "^2.0.0", + "@ava/babel-preset-transform-test-files": "^3.0.0", "@ava/pretty-format": "^1.1.0", "arr-flatten": "^1.0.1", "array-union": "^1.0.1", diff --git a/test/cli.js b/test/cli.js index 42cbbaf3d..7073ecdf6 100644 --- a/test/cli.js +++ b/test/cli.js @@ -98,25 +98,91 @@ test('throwing a named function will report the to the console', t => { }); test('improper use of t.throws will be reported to the console', t => { - execCli('fixture/improper-t-throws.js', (err, stdout, stderr) => { + execCli('fixture/improper-t-throws/throws.js', (err, stdout, stderr) => { t.ok(err); - t.match(stderr, /Improper usage of t\.throws detected at .*improper-t-throws.js \(4:10\)/); + t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); + t.match(stderr, /should be detected/); + t.match(stderr, /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, stderr) => { t.ok(err); - t.match(stderr, /Improper usage of t\.throws detected at .*improper-t-throws-promise.js \(5:11\)/); + t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); + t.match(stderr, /should be detected/); + t.match(stderr, /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) => { + 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.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, stderr) => { + 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.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) => { + 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.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) => { + 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.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) => { + 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.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) => { + 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.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) => { t.ok(err); - t.match(stderr, /Improper usage of t\.throws detected at .*improper-t-throws-async-callback.js \(5:11\)/); + t.match(stderr, /Improper usage of `t\.throws\(\)` detected/); + t.notMatch(stderr, /should be detected/); + t.match(stderr, /Try wrapping the first argument/); t.end(); }); }); diff --git a/test/fixture/improper-t-throws-async-callback.js b/test/fixture/improper-t-throws/async-callback.js similarity index 82% rename from test/fixture/improper-t-throws-async-callback.js rename to test/fixture/improper-t-throws/async-callback.js index 94802d414..aeeff6258 100644 --- a/test/fixture/improper-t-throws-async-callback.js +++ b/test/fixture/improper-t-throws/async-callback.js @@ -1,4 +1,4 @@ -import test from '../../'; +import test from '../../..'; test.cb(t => { setTimeout(() => { diff --git a/test/fixture/improper-t-throws/caught-and-leaked-slowly.js b/test/fixture/improper-t-throws/caught-and-leaked-slowly.js new file mode 100644 index 000000000..1258fff50 --- /dev/null +++ b/test/fixture/improper-t-throws/caught-and-leaked-slowly.js @@ -0,0 +1,15 @@ +import test from '../../..'; + +test(t => { + try { + t.throws(throwSync()); + } catch (err) { + setTimeout(() => { + throw err; + }, 500); + } +}); + +function throwSync() { + throw new Error('should be detected'); +} diff --git a/test/fixture/improper-t-throws/caught-and-leaked-too-slowly.js b/test/fixture/improper-t-throws/caught-and-leaked-too-slowly.js new file mode 100644 index 000000000..772a5b14d --- /dev/null +++ b/test/fixture/improper-t-throws/caught-and-leaked-too-slowly.js @@ -0,0 +1,15 @@ +import test from '../../..'; + +test(t => { + try { + t.throws(throwSync()); + } catch (err) { + setTimeout(() => { + throw err; + }, 1500); + } +}); + +function throwSync() { + throw new Error('should be detected'); +} diff --git a/test/fixture/improper-t-throws/caught-and-leaked.js b/test/fixture/improper-t-throws/caught-and-leaked.js new file mode 100644 index 000000000..5768e226e --- /dev/null +++ b/test/fixture/improper-t-throws/caught-and-leaked.js @@ -0,0 +1,15 @@ +import test from '../../..'; + +test(t => { + try { + t.throws(throwSync()); + } catch (err) { + setImmediate(() => { + throw err; + }); + } +}); + +function throwSync() { + throw new Error('should be detected'); +} diff --git a/test/fixture/improper-t-throws/caught.js b/test/fixture/improper-t-throws/caught.js new file mode 100644 index 000000000..491b39b0e --- /dev/null +++ b/test/fixture/improper-t-throws/caught.js @@ -0,0 +1,11 @@ +import test from '../../..'; + +test(t => { + try { + t.throws(throwSync()); + } catch (err) {} +}); + +function throwSync() { + throw new Error('should be detected'); +} diff --git a/test/fixture/improper-t-throws/leaked-from-promise.js b/test/fixture/improper-t-throws/leaked-from-promise.js new file mode 100644 index 000000000..f9cc36158 --- /dev/null +++ b/test/fixture/improper-t-throws/leaked-from-promise.js @@ -0,0 +1,17 @@ +import test from '../../..'; + +test(t => { + try { + t.throws(throwSync()); + } catch (err) { + setImmediate(() => { + throw err; + }); + } + + return new Promise(() => {}); +}); + +function throwSync() { + throw new Error('should be detected'); +} diff --git a/test/fixture/improper-t-throws-promise.js b/test/fixture/improper-t-throws/promise.js similarity index 83% rename from test/fixture/improper-t-throws-promise.js rename to test/fixture/improper-t-throws/promise.js index 7fb6bcbdd..55d727776 100644 --- a/test/fixture/improper-t-throws-promise.js +++ b/test/fixture/improper-t-throws/promise.js @@ -1,4 +1,4 @@ -import test from '../../'; +import test from '../../..'; test(t => { return Promise.resolve().then(() => { diff --git a/test/fixture/improper-t-throws.js b/test/fixture/improper-t-throws/throws.js similarity index 78% rename from test/fixture/improper-t-throws.js rename to test/fixture/improper-t-throws/throws.js index 46b7b70e9..fadcd5fb0 100644 --- a/test/fixture/improper-t-throws.js +++ b/test/fixture/improper-t-throws/throws.js @@ -1,4 +1,4 @@ -import test from '../../'; +import test from '../../..'; test(t => { t.throws(throwSync()); diff --git a/test/fixture/improper-t-throws-unhandled-rejection.js b/test/fixture/improper-t-throws/unhandled-rejection.js similarity index 85% rename from test/fixture/improper-t-throws-unhandled-rejection.js rename to test/fixture/improper-t-throws/unhandled-rejection.js index 39a487d1e..5790c0f86 100644 --- a/test/fixture/improper-t-throws-unhandled-rejection.js +++ b/test/fixture/improper-t-throws/unhandled-rejection.js @@ -1,4 +1,4 @@ -import test from '../../'; +import test from '../../..'; test.cb(t => { Promise.resolve().then(() => { diff --git a/test/reporters/mini.js b/test/reporters/mini.js index cd8b346bb..96c237adf 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -395,7 +395,6 @@ test('results with errors', t => { }; const output = reporter.finish(runStatus); - compareLineOutput(t, output, flatten([ '', ' ' + chalk.red('1 failed'), @@ -405,9 +404,9 @@ test('results with errors', t => { '', indentString(codeExcerpt(err1.source), 2).split('\n'), '', - indentString(formatSerializedError(err1), 2).split('\n'), /failure one/, '', + indentString(formatSerializedError(err1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -418,8 +417,9 @@ test('results with errors', t => { '', indentString(codeExcerpt(err2.source), 2).split('\n'), '', - indentString(formatSerializedError(err2), 2).split('\n'), - /failure two/ + /failure two/, + '', + indentString(formatSerializedError(err2), 2).split('\n') ])); t.end(); }); @@ -466,9 +466,9 @@ test('results with errors and disabled code excerpts', t => { '', ' ' + chalk.bold.white('failed one'), '', - indentString(formatSerializedError(err1), 2).split('\n'), /failure one/, '', + indentString(formatSerializedError(err1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -479,8 +479,9 @@ test('results with errors and disabled code excerpts', t => { '', indentString(codeExcerpt(err2.source), 2).split('\n'), '', - indentString(formatSerializedError(err2), 2).split('\n'), - /failure two/ + /failure two/, + '', + indentString(formatSerializedError(err2), 2).split('\n') ])); t.end(); }); @@ -530,9 +531,9 @@ test('results with errors and broken code excerpts', t => { ' ' + chalk.bold.white('failed one'), ' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`), '', - indentString(formatSerializedError(err1), 2).split('\n'), /failure one/, '', + indentString(formatSerializedError(err1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -543,8 +544,9 @@ test('results with errors and broken code excerpts', t => { '', indentString(codeExcerpt(err2.source), 2).split('\n'), '', - indentString(formatSerializedError(err2), 2).split('\n'), - /failure two/ + /failure two/, + '', + indentString(formatSerializedError(err2), 2).split('\n') ])); t.end(); }); diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index 216ca9e97..7bda0ed50 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -412,9 +412,9 @@ test('results with errors', t => { '', indentString(codeExcerpt(error1.source), 2).split('\n'), '', - indentString(formatSerializedError(error1), 2).split('\n'), /error one message/, '', + indentString(formatSerializedError(error1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -425,8 +425,9 @@ test('results with errors', t => { '', indentString(codeExcerpt(error2.source), 2).split('\n'), '', - indentString(formatSerializedError(error2), 2).split('\n'), - /error two message/ + /error two message/, + '', + indentString(formatSerializedError(error2), 2).split('\n') ])); t.end(); }); @@ -470,9 +471,9 @@ test('results with errors and disabled code excerpts', t => { '', ' ' + chalk.bold.white('fail one'), '', - indentString(formatSerializedError(error1), 2).split('\n'), /error one message/, '', + indentString(formatSerializedError(error1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -483,8 +484,9 @@ test('results with errors and disabled code excerpts', t => { '', indentString(codeExcerpt(error2.source), 2).split('\n'), '', - indentString(formatSerializedError(error2), 2).split('\n'), - /error two message/ + /error two message/, + '', + indentString(formatSerializedError(error2), 2).split('\n') ])); t.end(); }); @@ -531,9 +533,9 @@ test('results with errors and disabled code excerpts', t => { ' ' + chalk.bold.white('fail one'), ' ' + chalk.grey(`${error1.source.file}:${error1.source.line}`), '', - indentString(formatSerializedError(error1), 2).split('\n'), /error one message/, '', + indentString(formatSerializedError(error1), 2).split('\n'), stackLineRegex, compareLineOutput.SKIP_UNTIL_EMPTY_LINE, '', @@ -544,8 +546,9 @@ test('results with errors and disabled code excerpts', t => { '', indentString(codeExcerpt(error2.source), 2).split('\n'), '', - indentString(formatSerializedError(error2), 2).split('\n'), - /error two message/ + /error two message/, + '', + indentString(formatSerializedError(error2), 2).split('\n') ])); t.end(); });