diff --git a/index.js.flow b/index.js.flow index 778b3e3db..c05fbd902 100644 --- a/index.js.flow +++ b/index.js.flow @@ -63,7 +63,7 @@ type AssertContext = { }; // Assert that function doesn't throw an error or promise resolves. notThrows: { - (value: PromiseLike, message?: string): Promise; + (value: PromiseLike, message?: string): Promise; (value: () => mixed, message?: string): void; }; // Assert that contents matches regex. diff --git a/lib/assert.js b/lib/assert.js index 0dae2073f..98876544b 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -1,158 +1,335 @@ 'use strict'; -const assert = require('core-assert'); +const coreAssert = require('core-assert'); const deepEqual = require('lodash.isequal'); const observableToPromise = require('observable-to-promise'); const indentString = require('indent-string'); const isObservable = require('is-observable'); const isPromise = require('is-promise'); const jestSnapshot = require('jest-snapshot'); +const enhanceAssert = require('./enhance-assert'); const snapshotState = require('./snapshot-state'); -const x = module.exports; -const noop = () => {}; - -Object.defineProperty(x, 'AssertionError', {value: assert.AssertionError}); - -function create(val, expected, operator, msg, fn) { - return { - actual: val, - expected, - message: msg || ' ', - operator, - stackStartFunction: fn - }; -} - -function test(ok, opts) { - if (!ok) { - const err = new assert.AssertionError(opts); - err.showOutput = ['fail', 'throws', 'notThrows'].indexOf(err.operator) === -1; - throw err; +class AssertionError extends Error { + constructor(opts) { + super(opts.message || ''); + this.name = 'AssertionError'; + + this.actual = opts.actual; + this.assertion = opts.assertion; + this.expected = opts.expected; + this.hasActual = 'actual' in opts; + this.hasExpected = 'expected' in opts; + this.operator = opts.operator; + + // Reserved for power-assert statements + this.statements = null; + + if (opts.stack) { + this.stack = opts.stack; + } else { + Error.captureStackTrace(this, opts.stackStartFunction); + } } } +exports.AssertionError = AssertionError; -x.pass = msg => { - test(true, create(true, true, 'pass', msg, x.pass)); -}; - -x.fail = msg => { - msg = msg || 'Test failed via t.fail()'; - test(false, create(false, false, 'fail', msg, x.fail)); -}; - -x.truthy = (val, msg) => { - test(val, create(val, true, '==', msg, x.truthy)); -}; - -x.falsy = (val, msg) => { - test(!val, create(val, false, '==', msg, x.falsy)); -}; - -x.true = (val, msg) => { - test(val === true, create(val, true, '===', msg, x.true)); -}; - -x.false = (val, msg) => { - test(val === false, create(val, false, '===', msg, x.false)); -}; - -x.is = (val, expected, msg) => { - test(val === expected, create(val, expected, '===', msg, x.is)); -}; - -x.not = (val, expected, msg) => { - test(val !== expected, create(val, expected, '!==', msg, x.not)); -}; - -x.deepEqual = (val, expected, msg) => { - test(deepEqual(val, expected), create(val, expected, '===', msg, x.deepEqual)); -}; +function wrapAssertions(callbacks) { + const pass = callbacks.pass; + const pending = callbacks.pending; + const fail = callbacks.fail; -x.notDeepEqual = (val, expected, msg) => { - test(!deepEqual(val, expected), create(val, expected, '!==', msg, x.notDeepEqual)); -}; - -x.throws = (fn, err, msg) => { - if (isObservable(fn)) { - fn = observableToPromise(fn); - } - - if (isPromise(fn)) { - return fn - .then(() => { - x.throws(noop, err, msg); - }, fnErr => { - return x.throws(() => { - throw fnErr; - }, err, msg); - }); - } + const noop = () => {}; + const makeNoop = () => noop; + const makeRethrow = reason => () => { + throw reason; + }; - if (typeof fn !== 'function') { - throw new TypeError('t.throws must be called with a function, Promise, or Observable'); - } + const assertions = { + pass() { + pass(this); + }, + + fail(message) { + fail(this, new AssertionError({ + assertion: 'fail', + message: message || 'Test failed via t.fail()', + stackStartFunction: assertions.fail + })); + }, + + is(actual, expected, message) { + if (actual === expected) { + pass(this); + } else { + fail(this, new AssertionError({ + actual, + assertion: 'is', + expected, + message, + operator: '===', + stackStartFunction: assertions.is + })); + } + }, + + not(actual, expected, message) { + if (actual === expected) { + fail(this, new AssertionError({ + actual, + assertion: 'not', + expected, + message, + operator: '!==', + stackStartFunction: assertions.not + })); + } else { + pass(this); + } + }, + + deepEqual(actual, expected, message) { + if (deepEqual(actual, expected)) { + pass(this); + } else { + fail(this, new AssertionError({ + actual, + assertion: 'deepEqual', + expected, + message, + stackStartFunction: assertions.deepEqual + })); + } + }, + + notDeepEqual(actual, expected, message) { + if (deepEqual(actual, expected)) { + fail(this, new AssertionError({ + actual, + assertion: 'notDeepEqual', + expected, + message, + stackStartFunction: assertions.notDeepEqual + })); + } else { + pass(this); + } + }, + + throws(fn, err, message) { + let promise; + if (isPromise(fn)) { + promise = fn; + } else if (isObservable(fn)) { + promise = observableToPromise(fn); + } else if (typeof fn !== 'function') { + fail(this, new AssertionError({ + actual: fn, + message: '`t.throws()` must be called with a function, Promise, or Observable' + })); + return; + } - try { - if (typeof err === 'string') { - const errMsg = err; - err = err => err.message === errMsg; - } + let coreAssertThrowsErrorArg; + if (typeof err === 'string') { + const expectedMessage = err; + coreAssertThrowsErrorArg = error => error.message === expectedMessage; + } else { + // Assume it's a constructor function or regular expression + coreAssertThrowsErrorArg = err; + } - let result; + const test = fn => { + try { + let retval; + coreAssert.throws(() => { + try { + fn(); + } catch (err) { + retval = err; + throw err; + } + }, coreAssertThrowsErrorArg); + return retval; + } catch (err) { + throw new AssertionError({ + assertion: 'throws', + message, + stackStartFunction: assertions.throws + }); + } + }; + + if (promise) { + const result = promise.then(makeNoop, makeRethrow).then(test); + pending(this, result); + return result; + } - assert.throws(() => { try { - fn(); + const retval = test(fn); + pass(this); + return retval; } catch (err) { - result = err; - throw err; + fail(this, err); + } + }, + + notThrows(fn, message) { + let promise; + if (isPromise(fn)) { + promise = fn; + } else if (isObservable(fn)) { + promise = observableToPromise(fn); + } else if (typeof fn !== 'function') { + fail(this, new AssertionError({ + actual: fn, + message: '`t.notThrows()` must be called with a function, Promise, or Observable' + })); + return; } - }, err, msg); - - return result; - } catch (err) { - test(false, create(err.actual, err.expected, 'throws', err.message, x.throws)); - } -}; - -x.notThrows = (fn, msg) => { - if (isObservable(fn)) { - fn = observableToPromise(fn); - } - - if (isPromise(fn)) { - return fn - .catch(err => { - x.notThrows(() => { - throw err; - }, msg); - }); - } - - if (typeof fn !== 'function') { - throw new TypeError('t.notThrows must be called with a function, Promise, or Observable'); - } - try { - assert.doesNotThrow(fn, msg); - } catch (err) { - test(false, create(err.actual, err.expected, 'notThrows', err.message, x.notThrows)); - } -}; + const test = fn => { + try { + coreAssert.doesNotThrow(fn); + } catch (err) { + throw new AssertionError({ + actual: err.actual, + assertion: 'notThrows', + message, + stackStartFunction: assertions.notThrows + }); + } + }; + + if (promise) { + const result = promise + .then( + noop, + reason => test(makeRethrow(reason))); + pending(this, result); + return result; + } -x.regex = (contents, regex, msg) => { - test(regex.test(contents), create(regex, contents, '===', msg, x.regex)); -}; + try { + test(fn); + pass(this); + } catch (err) { + fail(this, err); + } + }, + + ifError(actual, message) { + if (actual) { + fail(this, new AssertionError({ + actual, + assertion: 'ifError', + message, + stackStartFunction: assertions.ifError + })); + } else { + pass(this); + } + }, + + snapshot(actual, optionalMessage) { + const result = snapshot(this, actual, optionalMessage); + if (result.pass) { + pass(this); + } else { + fail(this, new AssertionError({ + actual, + assertion: 'snapshot', + expected: result.expected, + message: result.message, + stackStartFunction: assertions.snapshot + })); + } + } + }; -x.notRegex = (contents, regex, msg) => { - test(!regex.test(contents), create(regex, contents, '!==', msg, x.notRegex)); -}; + const enhancedAssertions = enhanceAssert(pass, fail, { + truthy(actual, message) { + if (!actual) { + throw new AssertionError({ + actual, + assertion: 'truthy', + expected: true, + message, + operator: '==', + stackStartFunction: enhancedAssertions.truthy + }); + } + }, + + falsy(actual, message) { + if (actual) { + throw new AssertionError({ + actual, + assertion: 'falsy', + expected: false, + message, + operator: '==', + stackStartFunction: enhancedAssertions.falsy + }); + } + }, + + true(actual, message) { + if (actual !== true) { + throw new AssertionError({ + actual, + assertion: 'true', + expected: true, + message, + operator: '===', + stackStartFunction: enhancedAssertions.true + }); + } + }, + + false(actual, message) { + if (actual !== false) { + throw new AssertionError({ + actual, + assertion: 'false', + expected: false, + message, + operator: '===', + stackStartFunction: enhancedAssertions.false + }); + } + }, + + regex(actual, expected, message) { + if (!expected.test(actual)) { + throw new AssertionError({ + actual, + assertion: 'regex', + expected, + message, + stackStartFunction: enhancedAssertions.regex + }); + } + }, + + notRegex(actual, expected, message) { + if (expected.test(actual)) { + throw new AssertionError({ + actual, + assertion: 'notRegex', + expected, + message, + stackStartFunction: enhancedAssertions.notRegex + }); + } + } + }); -x.ifError = (err, msg) => { - test(!err, create(err, 'Error', '!==', msg, x.ifError)); -}; + return Object.assign(assertions, enhancedAssertions); +} +exports.wrapAssertions = wrapAssertions; -x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) { +function snapshot(executionContext, tree, optionalMessage, match, snapshotStateGetter) { // Set defaults - this allows tests to mock deps easily const toMatchSnapshot = match || jestSnapshot.toMatchSnapshot; const getState = snapshotStateGetter || snapshotState.get; @@ -161,7 +338,7 @@ x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) { const context = { dontThrow() {}, - currentTestName: this.title, + currentTestName: executionContext.title, snapshotState: state }; @@ -191,9 +368,10 @@ x._snapshot = function (tree, optionalMessage, match, snapshotStateGetter) { } } - test(result.pass, create(tree, expected, 'snapshot', message, x.snapshot)); -}; - -x.snapshot = function (tree, optionalMessage) { - x._snapshot.call(this, tree, optionalMessage); -}; + return { + pass: result.pass, + expected, + message + }; +} +exports.snapshot = snapshot; diff --git a/lib/enhance-assert.js b/lib/enhance-assert.js index 172a5269f..9065f9e38 100644 --- a/lib/enhance-assert.js +++ b/lib/enhance-assert.js @@ -13,33 +13,6 @@ const PATTERNS = [ 't.notRegex(contents, regex, [message])' ]; -const NON_ENHANCED_PATTERNS = [ - 't.pass([message])', - 't.fail([message])', - 't.throws(fn, [message])', - 't.notThrows(fn, [message])', - 't.ifError(error, [message])', - 't.snapshot(contents, [message])', - 't.is(value, expected, [message])', - 't.not(value, expected, [message])', - 't.deepEqual(value, expected, [message])', - 't.notDeepEqual(value, expected, [message])' -]; - -const enhanceAssert = opts => { - const empower = require('empower-core'); - const enhanced = empower(opts.assert, { - destructive: false, - onError: opts.onError, - onSuccess: opts.onSuccess, - patterns: PATTERNS, - wrapOnlyPatterns: NON_ENHANCED_PATTERNS, - bindReceiver: false - }); - - return enhanced; -}; - const isRangeMatch = (a, b) => { return (a[0] === b[0] && a[1] === b[1]) || (a[0] > b[0] && a[0] < b[1]) || @@ -55,22 +28,33 @@ const computeStatement = (tokens, range) => { const getNode = (ast, path) => dotProp.get(ast, path.replace(/\//g, '.')); -const formatter = () => { - return context => { - const ast = JSON.parse(context.source.ast); - const tokens = JSON.parse(context.source.tokens); - const args = context.args[0].events; - - return args - .map(arg => { - const range = getNode(ast, arg.espath).range; - return [computeStatement(tokens, range), arg.value]; - }) - .reverse(); - }; +const formatter = context => { + const ast = JSON.parse(context.source.ast); + const tokens = JSON.parse(context.source.tokens); + const args = context.args[0].events; + + return args + .map(arg => { + const range = getNode(ast, arg.espath).range; + return [computeStatement(tokens, range), arg.value]; + }) + .reverse(); }; +const enhanceAssert = (pass, fail, assertions) => { + const empower = require('empower-core'); + return empower(assertions, { + destructive: true, + onError(event) { + const error = event.error; + error.statements = formatter(event.powerAssertContext); + fail(this, error); + }, + onSuccess() { + pass(this); + }, + patterns: PATTERNS, + bindReceiver: false + }); +}; module.exports = enhanceAssert; -module.exports.PATTERNS = PATTERNS; -module.exports.NON_ENHANCED_PATTERNS = NON_ENHANCED_PATTERNS; -module.exports.formatter = formatter; diff --git a/lib/format-assert-error.js b/lib/format-assert-error.js index 55654274b..9e9f0979e 100644 --- a/lib/format-assert-error.js +++ b/lib/format-assert-error.js @@ -25,51 +25,64 @@ const cleanUp = line => { return ` ${line}`; }; -module.exports = err => { - if (err.statements) { - const statements = JSON.parse(err.statements); +module.exports = error => { + if (error.statements) { + const statements = JSON.parse(error.statements); return statements .map(statement => `${statement[0]}\n${chalk.grey('=>')} ${statement[1]}`) .join('\n\n') + '\n'; } - if ((err.actualType === 'object' || err.actualType === 'array') && err.actualType === err.expectedType) { - const patch = diff.createPatch('string', err.actual, err.expected); - const msg = patch - .split('\n') - .slice(4) - .map(cleanUp) - .filter(Boolean) - .join('\n'); + if (error.actual && error.expected && error.actual.type === error.expected.type) { + const type = error.actual.type; + if (type === 'array' || type === 'object') { + const patch = diff.createPatch('string', error.actual.formatted, error.expected.formatted); + const msg = patch + .split('\n') + .slice(4) + .map(cleanUp) + .filter(Boolean) + .join('\n') + .trimRight(); - return `Difference:\n\n${msg}`; - } + return `Difference:\n\n${msg}\n`; + } + + if (type === 'string') { + const diffMatchPatch = new DiffMatchPatch(); + const patch = diffMatchPatch.diff_main(stripAnsi(error.actual.formatted), stripAnsi(error.expected.formatted)); + const msg = patch + .map(part => { + if (part[0] === 1) { + return chalk.bgGreen.black(part[1]); + } - if (err.actualType === 'string' && err.expectedType === 'string') { - const diffMatchPatch = new DiffMatchPatch(); - const patch = diffMatchPatch.diff_main(stripAnsi(err.actual), stripAnsi(err.expected)); - const msg = patch - .map(part => { - if (part[0] === 1) { - return chalk.bgGreen.black(part[1]); - } + if (part[0] === -1) { + return chalk.bgRed.black(part[1]); + } - if (part[0] === -1) { - return chalk.bgRed.black(part[1]); - } + return chalk.red(part[1]); + }) + .join('') + .trimRight(); - return chalk.red(part[1]); - }) - .join(''); + return `Difference:\n\n${msg}\n`; + } + } - return `Difference:\n\n${msg}\n`; + let retval = null; + if (error.actual) { + retval = `Actual:\n\n${indentString(error.actual.formatted, 2).trimRight()}\n`; + } + if (error.expected) { + if (retval) { + retval += '\n'; + } else { + retval = ''; + } + retval += `Expected:\n\n${indentString(error.expected.formatted, 2).trimRight()}\n`; } - return [ - 'Actual:\n', - `${indentString(err.actual, 2)}\n`, - 'Expected:\n', - `${indentString(err.expected, 2)}\n` - ].join('\n'); + return retval; }; diff --git a/lib/reporters/mini.js b/lib/reporters/mini.js index f5b30ffd6..2c71e141b 100644 --- a/lib/reporters/mini.js +++ b/lib/reporters/mini.js @@ -179,10 +179,9 @@ class MiniReporter { return; } - const title = test.error ? test.title : 'Unhandled Error'; const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n'; - status += beforeSpacing + ' ' + colors.title(title) + '\n'; + status += beforeSpacing + ' ' + colors.title(test.title) + '\n'; if (test.error.source) { status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; @@ -192,12 +191,14 @@ class MiniReporter { } } - if (test.error.showOutput) { - status += '\n' + indentString(formatAssertError(test.error), 2); + if (test.error.avaAssertionError) { + const formatted = formatAssertError(test.error); + if (formatted) { + status += '\n' + indentString(formatted, 2); + } } - // `.trim()` is needed, because default `err.message` is ' ' (see lib/assert.js) - if (test.error.message.trim()) { + if (test.error.message) { status += '\n' + indentString(test.error.message, 2) + '\n'; } diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index 0dc4efeff..4aed8f41a 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -11,22 +11,29 @@ function getSourceFromStack(stack) { } function dumpError(error, includeMessage) { - const obj = {}; + const obj = Object.assign({}, error.object); if (error.name) { obj.name = error.name; } if (includeMessage && error.message) { obj.message = error.message; } - if (error.operator) { - obj.operator = error.operator; - } - if (typeof error.actual === 'string') { // Be sure to print empty strings, which are falsy - obj.actual = stripAnsi(error.actual); - } - if (typeof error.expected === 'string') { // Be sure to print empty strings, which are falsy - obj.expected = stripAnsi(error.expected); + + if (error.avaAssertionError) { + if (error.assertion) { + obj.assertion = error.assertion; + } + if (error.operator) { + obj.operator = error.operator; + } + if (error.actual) { + obj.actual = stripAnsi(error.actual.formatted); + } + if (error.expected) { + obj.expected = stripAnsi(error.expected.formatted); + } } + if (error.stack) { obj.at = getSourceFromStack(error.stack); } diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js index 4d6e127cb..4b9c92120 100644 --- a/lib/reporters/verbose.js +++ b/lib/reporters/verbose.js @@ -111,12 +111,14 @@ class VerboseReporter { } } - if (test.error.showOutput) { - output += '\n' + indentString(formatAssertError(test.error), 2); + if (test.error.avaAssertionError) { + const formatted = formatAssertError(test.error); + if (formatted) { + output += '\n' + indentString(formatted, 2); + } } - // `.trim()` is needed, because default `err.message` is ' ' (see lib/assert.js) - if (test.error.message.trim()) { + if (test.error.message) { output += '\n' + indentString(test.error.message, 2) + '\n'; } diff --git a/lib/run-status.js b/lib/run-status.js index 70791c67f..6526f7bdc 100644 --- a/lib/run-status.js +++ b/lib/run-status.js @@ -1,7 +1,6 @@ 'use strict'; const EventEmitter = require('events'); const chalk = require('chalk'); -const isObj = require('is-obj'); const flatten = require('arr-flatten'); const figures = require('figures'); const autoBind = require('auto-bind'); @@ -17,16 +16,6 @@ function sum(arr, key) { return result; } -function normalizeError(err) { - if (!isObj(err) || typeof err.message !== 'string') { - err = { - message: String(err) - }; - } - - return err; -} - class RunStatus extends EventEmitter { constructor(opts) { super(); @@ -68,7 +57,6 @@ class RunStatus extends EventEmitter { this.rejectionCount += data.rejections.length; data.rejections.forEach(err => { - err = normalizeError(err); err.type = 'rejection'; err.file = data.file; this.emit('error', err, this); @@ -77,7 +65,7 @@ class RunStatus extends EventEmitter { } handleExceptions(data) { this.exceptionCount++; - const err = normalizeError(data.exception); + const err = data.exception; err.type = 'exception'; err.file = data.file; this.emit('error', err, this); @@ -99,10 +87,6 @@ class RunStatus extends EventEmitter { test.title = this.prefixTitle(test.file) + test.title; if (test.error) { - if (test.error.name !== 'AssertionError') { - test.error.message = `Error: ${test.error.message}`; - } - this.errors.push(test); } diff --git a/lib/serialize-error.js b/lib/serialize-error.js index cbb0a2ac4..bb16e476f 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -4,6 +4,7 @@ const cleanYamlObject = require('clean-yaml-object'); const StackUtils = require('stack-utils'); const prettyFormat = require('@ava/pretty-format'); const reactTestPlugin = require('@ava/pretty-format/plugins/ReactTestComponent'); +const assert = require('./assert'); const beautifyStack = require('./beautify-stack'); const extractStack = require('./extract-stack'); @@ -15,75 +16,97 @@ function serializeValue(value) { }); } -function filter(propertyName, isRoot, source, target) { - if (!isRoot) { - return true; - } +function isAvaAssertionError(source) { + return source instanceof assert.AssertionError; +} - if ((propertyName === 'name' || propertyName === 'stack') && - typeof source[propertyName] !== 'string') { - return false; - } +function filter(propertyName, isRoot) { + return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack'); +} - if (propertyName === 'stack') { - target.stack = beautifyStack(source.stack); - return false; +const stackUtils = new StackUtils(); +function buildSource(stack) { + if (!stack) { + return null; } - if (propertyName === 'statements') { - if (source.showOutput) { - target.statements = JSON.stringify(source[propertyName].map(statement => { - const path = statement[0]; - const value = serializeValue(statement[1]); - - return [path, value]; - })); - } - - return false; + const firstStackLine = extractStack(stack).split('\n')[0]; + const source = stackUtils.parseLine(firstStackLine); + if (!source) { + return null; } - if (propertyName === 'actual' || propertyName === 'expected') { - if (source.showOutput) { - const value = source[propertyName]; - target[propertyName + 'Type'] = typeof value; - target[propertyName] = serializeValue(value); - } + // Assume the CWD is the project directory. This holds since this function + // is only called in test workers, which are created with their working + // directory set to the project directory. + const projectDir = process.cwd(); - return false; - } + const file = path.resolve(projectDir, source.file.trim()); + const rel = path.relative(projectDir, file); - return true; -} + const isWithinProject = rel.split(path.sep)[0] !== '..'; + const isDependency = isWithinProject && path.dirname(rel).split(path.sep).indexOf('node_modules') > -1; -const stackUtils = new StackUtils(); + return { + isDependency, + isWithinProject, + file, + line: source.line + }; +} module.exports = error => { - const err = cleanYamlObject(error, filter); - - if (err.stack) { - const firstStackLine = extractStack(err.stack).split('\n')[0]; - const source = stackUtils.parseLine(firstStackLine); - if (source) { - // Assume the CWD is the project directory. This holds since this function - // is only called in test workers, which are created with their working - // directory set to the project directory. - const projectDir = process.cwd(); - - const file = path.resolve(projectDir, source.file.trim()); - const rel = path.relative(projectDir, file); + const stack = typeof error.stack === 'string' ? + beautifyStack(error.stack) : + null; + const source = buildSource(stack); + const retval = { + avaAssertionError: isAvaAssertionError(error), + source + }; + if (stack) { + retval.stack = stack; + } - const isWithinProject = rel.split(path.sep)[0] !== '..'; - const isDependency = isWithinProject && path.dirname(rel).split(path.sep).indexOf('node_modules') > -1; + if (retval.avaAssertionError) { + retval.message = error.message; + retval.name = error.name; - err.source = { - isDependency, - isWithinProject, - file, - line: source.line + if (error.assertion) { + retval.assertion = error.assertion; + } + if (error.operator) { + retval.operator = error.operator; + } + if (error.hasActual) { + retval.actual = { + type: typeof error.actual, + formatted: serializeValue(error.actual) + }; + } + if (error.hasExpected) { + retval.expected = { + type: typeof error.expected, + formatted: serializeValue(error.expected) }; } + if (error.statements) { + retval.statements = JSON.stringify(error.statements.map(statement => { + const path = statement[0]; + const value = serializeValue(statement[1]); + + return [path, value]; + })); + } + } else { + retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties + if (typeof error.message === 'string') { + retval.message = error.message; + } + if (typeof error.name === 'string') { + retval.name = error.name; + } } - return err; + return retval; }; diff --git a/lib/test-worker.js b/lib/test-worker.js index 1209a9ea4..d9c7309b2 100644 --- a/lib/test-worker.js +++ b/lib/test-worker.js @@ -1,5 +1,6 @@ 'use strict'; /* eslint-disable import/order */ +const isObj = require('is-obj'); const process = require('./process-adapter'); const opts = process.opts; @@ -44,6 +45,7 @@ process.on('uncaughtException', exception => { // Avoid using serializeError const err = new Error('Failed to serialize uncaught exception'); serialized = { + avaAssertionError: false, name: err.name, message: err.message, stack: err.stack @@ -91,7 +93,13 @@ process.on('ava-teardown', () => { } rejections = rejections.map(rejection => { - return serializeError(rejection.reason); + let reason = rejection.reason; + if (!isObj(reason) || typeof reason.message !== 'string') { + reason = { + message: String(reason) + }; + } + return serializeError(reason); }); send('unhandledRejections', {rejections}); diff --git a/lib/test.js b/lib/test.js index 2e4e869c8..d3b4a8bc3 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,5 +1,4 @@ 'use strict'; -const inspect = require('util').inspect; const isGeneratorFn = require('is-generator-fn'); const maxTimeout = require('max-timeout'); const Promise = require('bluebird'); @@ -10,27 +9,16 @@ const isPromise = require('is-promise'); const isObservable = require('is-observable'); const plur = require('plur'); const assert = require('./assert'); -const enhanceAssert = require('./enhance-assert'); const globals = require('./globals'); const throwsHelper = require('./throws-helper'); -const formatter = enhanceAssert.formatter(); - class SkipApi { constructor(test) { this._test = test; } } -function skipFn() { - return this._test._assert(null); -} - -Object.keys(assert).forEach(el => { - SkipApi.prototype[el] = skipFn; -}); - -class PublicApi { +class ExecutionContext { constructor(test) { this._test = test; this.skip = new SkipApi(test); @@ -43,6 +31,12 @@ class PublicApi { Error.stackTraceLimit = limitBefore; this._test.plan(ct, obj.stack); } + get end() { + return this._test.end; + } + get title() { + return this._test.title; + } get context() { const contextRef = this._test.contextRef; return contextRef && contextRef.context; @@ -58,61 +52,31 @@ class PublicApi { contextRef.context = context; } } +Object.defineProperty(ExecutionContext.prototype, 'context', {enumerable: true}); -function onAssertionEvent(event) { - if (event.assertionThrew) { - if (event.powerAssertContext) { - event.error.statements = formatter(event.powerAssertContext); - event.error.message = event.originalMessage || ''; - } - this._test._setAssertError(event.error); - this._test._assert(null); - return null; - } - - let ret = event.returnValue; - - if (isObservable(ret)) { - ret = observableToPromise(ret); - } +{ + const assertions = assert.wrapAssertions({ + pass(executionContext) { + executionContext._test._assertionPassed(); + }, - if (isPromise(ret)) { - const promise = ret.then(null, err => { - err.originalMessage = event.originalMessage; - throw err; - }); + pending(executionContext, promise) { + executionContext._test._assertionPending(promise); + }, - this._test._assert(promise); + fail(executionContext, error) { + executionContext._test._assertionFailed(error); + } + }); + Object.assign(ExecutionContext.prototype, assertions); - return promise; + function skipFn() { + this._test._assertionPassed(); } - - this._test._assert(null); - - return ret; -} - -Object.assign(PublicApi.prototype, enhanceAssert({ - assert, - onSuccess: onAssertionEvent, - onError: onAssertionEvent -})); - -// Getters -[ - 'assertCount', - 'title', - 'end' -] - .forEach(name => { - Object.defineProperty(PublicApi.prototype, name, { - get() { - return this._test[name]; - } - }); + Object.keys(assertions).forEach(el => { + SkipApi.prototype[el] = skipFn; }); - -Object.defineProperty(PublicApi.prototype, 'context', {enumerable: true}); +} class Test { constructor(title, fn, contextRef, report) { @@ -122,11 +86,14 @@ class Test { title = null; } - assert.is(typeof fn, 'function', 'You must provide a callback'); + if (typeof fn !== 'function') { + throw new Error('You must provide a callback'); + } this.title = title || fnName(fn) || '[anonymous]'; this.fn = isGeneratorFn(fn) ? co.wrap(fn) : fn; - this.assertions = []; + this.pendingAssertions = []; + this.assertCount = 0; this.planCount = null; this.duration = null; this.assertError = undefined; @@ -148,19 +115,19 @@ class Test { this.title = '[anonymous]'; } } - get assertCount() { - return this.assertions.length; + _assertionPassed() { + this.assertCount++; } - _assert(promise) { - if (isPromise(promise)) { - this.sync = false; - } - - this.assertions.push(promise); + _assertionPending(promise) { + this.sync = false; + this.assertCount++; + this.pendingAssertions.push(promise); + } + _assertionFailed(error) { + this._setAssertError(error); + this.assertCount++; } _setAssertError(err) { - throwsHelper(err); - if (this.assertError !== undefined) { return; } @@ -182,19 +149,20 @@ class Test { let ret; try { - ret = this.fn(this._publicApi()); + ret = this.fn(this._createExecutionContext()); } catch (err) { this.threwSync = true; + throwsHelper(err); - if (err instanceof Error) { - this._setAssertError(err); - } else { - this._setAssertError(new assert.AssertionError({ + let error = err; + if (!(err instanceof assert.AssertionError)) { + error = new assert.AssertionError({ actual: err, - message: `Non-error thrown with value: ${inspect(err, {depth: null})}`, - operator: 'catch' - })); + message: `Error thrown in test`, + stack: err instanceof Error && err.stack + }); } + this._setAssertError(error); } return ret; @@ -244,11 +212,13 @@ class Test { return Promise.resolve(ret).then( () => this.exit(), err => { - if (!(err instanceof Error)) { + throwsHelper(err); + + if (!(err instanceof assert.AssertionError)) { err = new assert.AssertionError({ actual: err, - message: `Promise rejected with: ${inspect(err, {depth: null})}`, - operator: 'promise' + message: 'Rejected promise returned by test', + stack: err instanceof Error && err.stack }); } @@ -293,11 +263,10 @@ class Test { } _end(err) { if (err) { - if (!(err instanceof Error)) { + if (!(err instanceof assert.AssertionError)) { err = new assert.AssertionError({ actual: err, - message: 'Callback called with an error: ' + inspect(err, {depth: null}), - operator: 'callback' + message: 'Callback called with an error' }); } @@ -316,15 +285,15 @@ class Test { this.exit(); } _checkPlanCount() { - if (this.assertError === undefined && this.planCount !== null && this.planCount !== this.assertions.length) { + if (this.assertError === undefined && this.planCount !== null && this.planCount !== this.assertCount) { this._setAssertError(new assert.AssertionError({ - actual: this.assertions.length, + actual: this.assertCount, + assertion: 'plan', expected: this.planCount, - message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertions.length}.`, - operator: 'plan' + message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`, + operator: '===', + stack: this.planStack })); - - this.assertError.stack = this.planStack; } } exit() { @@ -343,7 +312,7 @@ class Test { return result; } - Promise.all(this.assertions) + Promise.all(this.pendingAssertions) .catch(err => { this._setAssertError(err); }) @@ -361,8 +330,8 @@ class Test { return this.promise().promise; } - _publicApi() { - return new PublicApi(this); + _createExecutionContext() { + return new ExecutionContext(this); } } diff --git a/test/assert.js b/test/assert.js index e0abdf307..685b76055 100644 --- a/test/assert.js +++ b/test/assert.js @@ -3,9 +3,19 @@ const test = require('tap').test; const sinon = require('sinon'); const assert = require('../lib/assert'); +const assertions = assert.wrapAssertions({ + pass() {}, + + pending() {}, + + fail(_, error) { + throw error; + } +}); + test('.pass()', t => { t.doesNotThrow(() => { - assert.pass(); + assertions.pass(); }); t.end(); @@ -13,7 +23,7 @@ test('.pass()', t => { test('.fail()', t => { t.throws(() => { - assert.fail(); + assertions.fail(); }); t.end(); @@ -21,13 +31,13 @@ test('.fail()', t => { test('.truthy()', t => { t.throws(() => { - assert.truthy(0); - assert.truthy(false); + assertions.truthy(0); + assertions.truthy(false); }); t.doesNotThrow(() => { - assert.truthy(1); - assert.truthy(true); + assertions.truthy(1); + assertions.truthy(true); }); t.end(); @@ -35,13 +45,13 @@ test('.truthy()', t => { test('.falsy()', t => { t.throws(() => { - assert.falsy(1); - assert.falsy(true); + assertions.falsy(1); + assertions.falsy(true); }); t.doesNotThrow(() => { - assert.falsy(0); - assert.falsy(false); + assertions.falsy(0); + assertions.falsy(false); }); t.end(); @@ -49,23 +59,23 @@ test('.falsy()', t => { test('.true()', t => { t.throws(() => { - assert.true(1); + assertions.true(1); }); t.throws(() => { - assert.true(0); + assertions.true(0); }); t.throws(() => { - assert.true(false); + assertions.true(false); }); t.throws(() => { - assert.true('foo'); + assertions.true('foo'); }); t.doesNotThrow(() => { - assert.true(true); + assertions.true(true); }); t.end(); @@ -73,23 +83,23 @@ test('.true()', t => { test('.false()', t => { t.throws(() => { - assert.false(0); + assertions.false(0); }); t.throws(() => { - assert.false(1); + assertions.false(1); }); t.throws(() => { - assert.false(true); + assertions.false(true); }); t.throws(() => { - assert.false('foo'); + assertions.false('foo'); }); t.doesNotThrow(() => { - assert.false(false); + assertions.false(false); }); t.end(); @@ -97,11 +107,11 @@ test('.false()', t => { test('.is()', t => { t.doesNotThrow(() => { - assert.is('foo', 'foo'); + assertions.is('foo', 'foo'); }); t.throws(() => { - assert.is('foo', 'bar'); + assertions.is('foo', 'bar'); }); t.end(); @@ -109,11 +119,11 @@ test('.is()', t => { test('.not()', t => { t.doesNotThrow(() => { - assert.not('foo', 'bar'); + assertions.not('foo', 'bar'); }); t.throws(() => { - assert.not('foo', 'foo'); + assertions.not('foo', 'foo'); }); t.end(); @@ -124,11 +134,11 @@ test('.deepEqual()', t => { // used to test deep object equality t.throws(() => { - assert.deepEqual({a: false}, {a: 0}); + assertions.deepEqual({a: false}, {a: 0}); }); t.doesNotThrow(() => { - assert.deepEqual({ + assertions.deepEqual({ a: 'a', b: 'b' }, { @@ -138,7 +148,7 @@ test('.deepEqual()', t => { }); t.doesNotThrow(() => { - assert.deepEqual({ + assertions.deepEqual({ a: 'a', b: 'b', c: { @@ -154,17 +164,17 @@ test('.deepEqual()', t => { }); t.throws(() => { - assert.deepEqual([1, 2, 3], [1, 2, 3, 4]); + assertions.deepEqual([1, 2, 3], [1, 2, 3, 4]); }); t.doesNotThrow(() => { - assert.deepEqual([1, 2, 3], [1, 2, 3]); + assertions.deepEqual([1, 2, 3], [1, 2, 3]); }); t.throws(() => { const fnA = a => a; const fnB = a => a; - assert.deepEqual(fnA, fnB); + assertions.deepEqual(fnA, fnB); }); t.doesNotThrow(() => { @@ -176,7 +186,7 @@ test('.deepEqual()', t => { const y2 = {x: x2}; x2.y = y2; - assert.deepEqual(x1, x2); + assertions.deepEqual(x1, x2); }); t.doesNotThrow(() => { @@ -187,7 +197,7 @@ test('.deepEqual()', t => { const x = new Foo(1); const y = new Foo(1); - assert.deepEqual(x, y); + assertions.deepEqual(x, y); }); t.throws(() => { @@ -202,11 +212,11 @@ test('.deepEqual()', t => { const x = new Foo(1); const y = new Bar(1); - assert.deepEqual(x, y); + assertions.deepEqual(x, y); }); t.throws(() => { - assert.deepEqual({ + assertions.deepEqual({ a: 'a', b: 'b', c: { @@ -222,73 +232,73 @@ test('.deepEqual()', t => { }); t.throws(() => { - assert.deepEqual({}, []); + assertions.deepEqual({}, []); }); t.throws(() => { - assert.deepEqual({0: 'a', 1: 'b'}, ['a', 'b']); + assertions.deepEqual({0: 'a', 1: 'b'}, ['a', 'b']); }); t.throws(() => { - assert.deepEqual({a: 1}, {a: 1, b: undefined}); + assertions.deepEqual({a: 1}, {a: 1, b: undefined}); }); t.throws(() => { - assert.deepEqual(new Date('1972-08-01'), null); + assertions.deepEqual(new Date('1972-08-01'), null); }); t.throws(() => { - assert.deepEqual(new Date('1972-08-01'), undefined); + assertions.deepEqual(new Date('1972-08-01'), undefined); }); t.doesNotThrow(() => { - assert.deepEqual(new Date('1972-08-01'), new Date('1972-08-01')); + assertions.deepEqual(new Date('1972-08-01'), new Date('1972-08-01')); }); t.doesNotThrow(() => { - assert.deepEqual({x: new Date('1972-08-01')}, {x: new Date('1972-08-01')}); + assertions.deepEqual({x: new Date('1972-08-01')}, {x: new Date('1972-08-01')}); }); t.throws(() => { - assert.deepEqual(() => {}, () => {}); + assertions.deepEqual(() => {}, () => {}); }); t.doesNotThrow(() => { - assert.deepEqual(undefined, undefined); - assert.deepEqual({x: undefined}, {x: undefined}); - assert.deepEqual({x: [undefined]}, {x: [undefined]}); + assertions.deepEqual(undefined, undefined); + assertions.deepEqual({x: undefined}, {x: undefined}); + assertions.deepEqual({x: [undefined]}, {x: [undefined]}); }); t.doesNotThrow(() => { - assert.deepEqual(null, null); - assert.deepEqual({x: null}, {x: null}); - assert.deepEqual({x: [null]}, {x: [null]}); + assertions.deepEqual(null, null); + assertions.deepEqual({x: null}, {x: null}); + assertions.deepEqual({x: [null]}, {x: [null]}); }); t.doesNotThrow(() => { - assert.deepEqual(0, 0); - assert.deepEqual(1, 1); - assert.deepEqual(3.14, 3.14); + assertions.deepEqual(0, 0); + assertions.deepEqual(1, 1); + assertions.deepEqual(3.14, 3.14); }); t.throws(() => { - assert.deepEqual(0, 1); + assertions.deepEqual(0, 1); }); t.throws(() => { - assert.deepEqual(1, -1); + assertions.deepEqual(1, -1); }); t.throws(() => { - assert.deepEqual(3.14, 2.72); + assertions.deepEqual(3.14, 2.72); }); t.throws(() => { - assert.deepEqual({0: 'a', 1: 'b'}, ['a', 'b']); + assertions.deepEqual({0: 'a', 1: 'b'}, ['a', 'b']); }); t.doesNotThrow(() => { - assert.deepEqual( + assertions.deepEqual( [ {foo: {z: 100, y: 200, x: 300}}, 'bar', @@ -305,7 +315,7 @@ test('.deepEqual()', t => { }); t.doesNotThrow(() => { - assert.deepEqual( + assertions.deepEqual( {x: {a: 1, b: 2}, y: {c: 3, d: 4}}, {y: {d: 4, c: 3}, x: {b: 2, a: 1}} ); @@ -314,29 +324,29 @@ test('.deepEqual()', t => { // Regression test end here t.doesNotThrow(() => { - assert.deepEqual({a: 'a'}, {a: 'a'}); + assertions.deepEqual({a: 'a'}, {a: 'a'}); }); t.doesNotThrow(() => { - assert.deepEqual(['a', 'b'], ['a', 'b']); + assertions.deepEqual(['a', 'b'], ['a', 'b']); }); t.throws(() => { - assert.deepEqual({a: 'a'}, {a: 'b'}); + assertions.deepEqual({a: 'a'}, {a: 'b'}); }); t.throws(() => { - assert.deepEqual(['a', 'b'], ['a', 'a']); + assertions.deepEqual(['a', 'b'], ['a', 'a']); }); t.throws(() => { - assert.deepEqual([['a', 'b'], 'c'], [['a', 'b'], 'd']); + assertions.deepEqual([['a', 'b'], 'c'], [['a', 'b'], 'd']); }); t.throws(() => { const circular = ['a', 'b']; circular.push(circular); - assert.deepEqual([circular, 'c'], [circular, 'd']); + assertions.deepEqual([circular, 'c'], [circular, 'd']); }); t.end(); @@ -344,19 +354,19 @@ test('.deepEqual()', t => { test('.notDeepEqual()', t => { t.doesNotThrow(() => { - assert.notDeepEqual({a: 'a'}, {a: 'b'}); + assertions.notDeepEqual({a: 'a'}, {a: 'b'}); }); t.doesNotThrow(() => { - assert.notDeepEqual(['a', 'b'], ['c', 'd']); + assertions.notDeepEqual(['a', 'b'], ['c', 'd']); }); t.throws(() => { - assert.notDeepEqual({a: 'a'}, {a: 'a'}); + assertions.notDeepEqual({a: 'a'}, {a: 'a'}); }); t.throws(() => { - assert.notDeepEqual(['a', 'b'], ['a', 'b']); + assertions.notDeepEqual(['a', 'b'], ['a', 'b']); }); t.end(); @@ -364,11 +374,11 @@ test('.notDeepEqual()', t => { test('.throws()', t => { t.throws(() => { - assert.throws(() => {}); + assertions.throws(() => {}); }); t.doesNotThrow(() => { - assert.throws(() => { + assertions.throws(() => { throw new Error('foo'); }); }); @@ -378,7 +388,7 @@ test('.throws()', t => { test('.throws() returns the thrown error', t => { const expected = new Error(); - const actual = assert.throws(() => { + const actual = assertions.throws(() => { throw expected; }); @@ -390,7 +400,7 @@ test('.throws() returns the thrown error', t => { test('.throws() returns the rejection reason of promise', t => { const expected = new Error(); - return assert.throws(Promise.reject(expected)).then(actual => { + return assertions.throws(Promise.reject(expected)).then(actual => { t.is(actual, expected); t.end(); }); @@ -400,10 +410,10 @@ test('.throws should throw if passed a bad value', t => { t.plan(1); t.throws(() => { - assert.throws('not a function'); + assertions.throws('not a function'); }, { - name: 'TypeError', - message: /t\.throws must be called with a function, Promise, or Observable/ + name: 'AssertionError', + message: /`t\.throws\(\)` must be called with a function, Promise, or Observable/ }); }); @@ -411,20 +421,20 @@ test('.notThrows should throw if passed a bad value', t => { t.plan(1); t.throws(() => { - assert.notThrows('not a function'); + assertions.notThrows('not a function'); }, { - name: 'TypeError', - message: /t\.notThrows must be called with a function, Promise, or Observable/ + name: 'AssertionError', + message: /`t\.notThrows\(\)` must be called with a function, Promise, or Observable/ }); }); test('.notThrows()', t => { t.doesNotThrow(() => { - assert.notThrows(() => {}); + assertions.notThrows(() => {}); }); t.throws(() => { - assert.notThrows(() => { + assertions.notThrows(() => { throw new Error('foo'); }); }); @@ -432,13 +442,19 @@ test('.notThrows()', t => { t.end(); }); +test('.notThrows() returns undefined for a fulfilled promise', t => { + return assertions.notThrows(Promise.resolve(Symbol(''))).then(actual => { + t.is(actual, undefined); + }); +}); + test('.regex()', t => { t.doesNotThrow(() => { - assert.regex('abc', /^abc$/); + assertions.regex('abc', /^abc$/); }); t.throws(() => { - assert.regex('foo', /^abc$/); + assertions.regex('foo', /^abc$/); }); t.end(); @@ -446,11 +462,11 @@ test('.regex()', t => { test('.notRegex()', t => { t.doesNotThrow(() => { - assert.notRegex('abc', /def/); + assertions.notRegex('abc', /def/); }); t.throws(() => { - assert.notRegex('abc', /abc/); + assertions.notRegex('abc', /abc/); }); t.end(); @@ -458,11 +474,11 @@ test('.notRegex()', t => { test('.ifError()', t => { t.throws(() => { - assert.ifError(new Error()); + assertions.ifError(new Error()); }); t.doesNotThrow(() => { - assert.ifError(null); + assertions.ifError(null); }); t.end(); @@ -477,11 +493,11 @@ test('.deepEqual() should not mask RangeError from underlying assert', t => { const b = new Circular(); t.throws(() => { - assert.notDeepEqual(a, b); + assertions.notDeepEqual(a, b); }); t.doesNotThrow(() => { - assert.deepEqual(a, b); + assertions.deepEqual(a, b); }); t.end(); @@ -493,12 +509,14 @@ test('snapshot makes a snapshot using a library and global options', t => { const stateGetter = sinon.stub().returns(state); const matchStub = sinon.stub().returns({pass: true}); - assert.title = 'Test name'; + const test = { + title: 'Test name' + }; t.plan(4); t.doesNotThrow(() => { - assert._snapshot('tree', undefined, matchStub, stateGetter); + assert.snapshot(test, 'tree', undefined, matchStub, stateGetter); }); t.ok(stateGetter.called); @@ -509,9 +527,6 @@ test('snapshot makes a snapshot using a library and global options', t => { }); t.ok(saveSpy.calledOnce); - - delete assert.title; - t.end(); }); @@ -521,7 +536,9 @@ test('snapshot handles jsx tree', t => { const stateGetter = sinon.stub().returns(state); const matchStub = sinon.stub().returns({pass: true}); - assert.title = 'Test name'; + const test = { + title: 'Test name' + }; t.plan(5); @@ -534,7 +551,7 @@ test('snapshot handles jsx tree', t => { Object.defineProperty(tree, '$$typeof', {value: Symbol.for('react.test.json')}); - assert._snapshot(tree, undefined, matchStub, stateGetter); + assert.snapshot(test, tree, undefined, matchStub, stateGetter); }); t.ok(stateGetter.called); @@ -554,8 +571,5 @@ test('snapshot handles jsx tree', t => { }); t.ok(saveSpy.calledOnce); - - delete assert.title; - t.end(); }); diff --git a/test/format-assert-error.js b/test/format-assert-error.js index de66872b0..8bb22b80e 100644 --- a/test/format-assert-error.js +++ b/test/format-assert-error.js @@ -26,10 +26,14 @@ test('render statements', t => { test('diff objects', t => { const err = { - actual: prettyFormat({a: 1}), - expected: prettyFormat({a: 2}), - actualType: 'object', - expectedType: 'object' + actual: { + type: 'object', + formatted: prettyFormat({a: 1}) + }, + expected: { + type: 'object', + formatted: prettyFormat({a: 2}) + } }; t.is(format(err), [ @@ -38,17 +42,21 @@ test('diff objects', t => { `${chalk.red('-')} a: 1,`, `${chalk.green('+')} a: 2,`, ' }', - ' ' + '' ].join('\n')); t.end(); }); test('diff arrays', t => { const err = { - actual: prettyFormat([1]), - expected: prettyFormat([2]), - actualType: 'array', - expectedType: 'array' + actual: { + type: 'array', + formatted: prettyFormat([1]) + }, + expected: { + type: 'array', + formatted: prettyFormat([2]) + } }; t.is(format(err), [ @@ -57,17 +65,21 @@ test('diff arrays', t => { `${chalk.red('-')} 1,`, `${chalk.green('+')} 2,`, ' ]', - ' ' + '' ].join('\n')); t.end(); }); test('diff strings', t => { const err = { - actual: 'abc', - expected: 'abd', - actualType: 'string', - expectedType: 'string' + actual: { + type: 'string', + formatted: 'abc' + }, + expected: { + type: 'string', + formatted: 'abd' + } }; t.is(format(err), [ @@ -77,19 +89,53 @@ test('diff strings', t => { t.end(); }); -test('diff different types', t => { +test('print different types', t => { const err = { - actual: prettyFormat([1, 2, 3]), - expected: prettyFormat({a: 1, b: 2, c: 3}), - actualType: 'array', - expectedType: 'object' + actual: { + type: 'array', + formatted: prettyFormat([1, 2, 3]) + }, + expected: { + type: 'object', + formatted: prettyFormat({a: 1, b: 2, c: 3}) + } }; t.is(format(err), [ 'Actual:\n', - `${indentString(err.actual, 2)}\n`, + `${indentString(err.actual.formatted, 2)}\n`, 'Expected:\n', - `${indentString(err.expected, 2)}\n` + `${indentString(err.expected.formatted, 2)}\n` + ].join('\n')); + t.end(); +}); + +test('print actual even if no expected', t => { + const err = { + actual: { + type: 'array', + formatted: prettyFormat([1, 2, 3]) + } + }; + + t.is(format(err), [ + 'Actual:\n', + `${indentString(err.actual.formatted, 2)}\n` + ].join('\n')); + t.end(); +}); + +test('print expected even if no actual', t => { + const err = { + expected: { + type: 'array', + formatted: prettyFormat([1, 2, 3]) + } + }; + + t.is(format(err), [ + 'Expected:\n', + `${indentString(err.expected.formatted, 2)}\n` ].join('\n')); t.end(); }); diff --git a/test/promise.js b/test/promise.js index 85c01c119..aa50a542c 100644 --- a/test/promise.js +++ b/test/promise.js @@ -293,8 +293,8 @@ test('reject', t => { }); }).run().then(result => { t.is(result.passed, false); - t.is(result.reason.name, 'Error'); - t.is(result.reason.message, 'unicorn'); + t.is(result.reason.actual.name, 'Error'); + t.is(result.reason.actual.message, 'unicorn'); t.end(); }); }); @@ -303,7 +303,8 @@ test('reject with non-Error', t => { ava(() => Promise.reject('failure')).run().then(result => { t.is(result.passed, false); t.is(result.reason.name, 'AssertionError'); - t.is(result.reason.message, 'Promise rejected with: \'failure\''); + t.is(result.reason.message, 'Rejected promise returned by test'); + t.is(result.reason.actual, 'failure'); t.end(); }); }); diff --git a/test/reporters/mini.js b/test/reporters/mini.js index 6d74c627f..40e6b20ef 100644 --- a/test/reporters/mini.js +++ b/test/reporters/mini.js @@ -363,21 +363,29 @@ test('results with errors', t => { err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); err1.source = source(err1Path); - err1.showOutput = true; - err1.actual = JSON.stringify('abc'); - err1.actualType = 'string'; - err1.expected = JSON.stringify('abd'); - err1.expectedType = 'string'; + err1.avaAssertionError = true; + err1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + err1.expected = { + type: 'string', + formatted: JSON.stringify('abd') + }; const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); err2.source = source(err2Path); - err2.showOutput = true; - err2.actual = JSON.stringify([1]); - err2.actualType = 'array'; - err2.expected = JSON.stringify([2]); - err2.expectedType = 'array'; + err2.avaAssertionError = true; + err2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + err2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = miniReporter(); reporter.failCount = 1; @@ -425,21 +433,29 @@ test('results with errors', t => { test('results with errors and disabled code excerpts', t => { const err1 = new Error('failure one'); err1.stack = beautifyStack(err1.stack); - err1.showOutput = true; - err1.actual = JSON.stringify('abc'); - err1.actualType = 'string'; - err1.expected = JSON.stringify('abd'); - err1.expectedType = 'string'; + err1.avaAssertionError = true; + err1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + err1.expected = { + type: 'string', + formatted: JSON.stringify('abd') + }; const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); err2.source = source(err2Path); - err2.showOutput = true; - err2.actual = JSON.stringify([1]); - err2.actualType = 'array'; - err2.expected = JSON.stringify([2]); - err2.expectedType = 'array'; + err2.avaAssertionError = true; + err2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + err2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = miniReporter({color: true}); reporter.failCount = 1; @@ -486,83 +502,29 @@ test('results with errors and broken code excerpts', t => { err1.stack = beautifyStack(err1.stack); const err1Path = tempWrite.sync('a();'); err1.source = source(err1Path, 10); - err1.showOutput = true; - err1.actual = JSON.stringify('abc'); - err1.actualType = 'string'; - err1.expected = JSON.stringify('abd'); - err1.expectedType = 'string'; - - const err2 = new Error('failure two'); - err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; - const err2Path = tempWrite.sync('b();'); - err2.source = source(err2Path); - err2.showOutput = true; - err2.actual = JSON.stringify([1]); - err2.actualType = 'array'; - err2.expected = JSON.stringify([2]); - err2.expectedType = 'array'; - - const reporter = miniReporter({color: true}); - reporter.failCount = 1; - - const runStatus = { - errors: [{ - title: 'failed one', - error: err1 - }, { - title: 'failed two', - error: err2 - }] + err1.avaAssertionError = true; + err1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + err1.expected = { + type: 'string', + formatted: JSON.stringify('abd') }; - - const output = reporter.finish(runStatus); - - compareLineOutput(t, output, flatten([ - '', - ' ' + chalk.red('1 failed'), - '', - ' ' + chalk.bold.white('failed one'), - ' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`), - '', - indentString(formatAssertError(err1), 2).split('\n'), - /failure one/, - '', - stackLineRegex, - compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + chalk.bold.white('failed two'), - ' ' + chalk.grey(`${err2.source.file}:${err2.source.line}`), - '', - indentString(codeExcerpt(err2.source), 2).split('\n'), - '', - indentString(formatAssertError(err2), 2).split('\n'), - /failure two/ - ])); - t.end(); -}); - -test('results with errors and disabled assert output', t => { - const err1 = new Error('failure one'); - err1.stack = beautifyStack(err1.stack); - const err1Path = tempWrite.sync('a();'); - err1.source = source(err1Path); - err1.showOutput = false; - err1.actual = JSON.stringify('abc'); - err1.actualType = 'string'; - err1.expected = JSON.stringify('abd'); - err1.expectedType = 'string'; const err2 = new Error('failure two'); err2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b();'); err2.source = source(err2Path); - err2.showOutput = true; - err2.actual = JSON.stringify([1]); - err2.actualType = 'array'; - err2.expected = JSON.stringify([2]); - err2.expectedType = 'array'; + err2.avaAssertionError = true; + err2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + err2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = miniReporter({color: true}); reporter.failCount = 1; @@ -586,8 +548,7 @@ test('results with errors and disabled assert output', t => { ' ' + chalk.bold.white('failed one'), ' ' + chalk.grey(`${err1.source.file}:${err1.source.line}`), '', - indentString(codeExcerpt(err1.source), 2).split('\n'), - '', + indentString(formatAssertError(err1), 2).split('\n'), /failure one/, '', stackLineRegex, diff --git a/test/reporters/tap.js b/test/reporters/tap.js index c9619bd3e..514fa8177 100644 --- a/test/reporters/tap.js +++ b/test/reporters/tap.js @@ -36,9 +36,11 @@ test('failing test', t => { error: { name: 'AssertionError', message: 'false == true', + avaAssertionError: true, + assertion: 'true', operator: '==', - expected: 'true', - actual: 'false', + expected: {formatted: 'true'}, + actual: {formatted: 'false'}, stack: ['', 'Test.fn (test.js:1:2)'].join('\n') } }); @@ -48,6 +50,7 @@ not ok 1 - failing --- name: AssertionError message: false == true + assertion: 'true' operator: == actual: 'false' expected: 'true' @@ -64,14 +67,16 @@ test('multiline strings in YAML block', t => { const actualOutput = reporter.test({ title: 'multiline', error: { - actual: 'hello\nworld' + object: { + foo: 'hello\nworld' + } } }); const expectedOutput = `# multiline not ok 1 - multiline --- - actual: |- + foo: |- hello world ...`; @@ -86,8 +91,9 @@ test('strips ANSI from actual and expected values', t => { const actualOutput = reporter.test({ title: 'strip ansi', error: { - actual: '\u001b[31mhello\u001b[39m', - expected: '\u001b[32mworld\u001b[39m' + avaAssertionError: true, + actual: {formatted: '\u001b[31mhello\u001b[39m'}, + expected: {formatted: '\u001b[32mworld\u001b[39m'} } }); diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js index 67b978ebf..a1503602e 100644 --- a/test/reporters/verbose.js +++ b/test/reporters/verbose.js @@ -373,21 +373,29 @@ test('results with errors', t => { error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a()'); error1.source = source(err1Path); - error1.showOutput = true; - error1.actual = JSON.stringify('abc'); - error1.actualType = 'string'; - error1.expected = JSON.stringify('abd'); - error1.expectedType = 'string'; + error1.avaAssertionError = true; + error1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + error1.expected = { + type: 'string', + formatted: JSON.stringify('abd') + }; const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); error2.source = source(err2Path); - error2.showOutput = true; - error2.actual = JSON.stringify([1]); - error2.actualType = 'array'; - error2.expected = JSON.stringify([2]); - error2.expectedType = 'array'; + error2.avaAssertionError = true; + error2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + error2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = createReporter({color: true}); const runStatus = createRunStatus(); @@ -432,21 +440,29 @@ test('results with errors', t => { test('results with errors and disabled code excerpts', t => { const error1 = new Error('error one message'); error1.stack = beautifyStack(error1.stack); - error1.showOutput = true; - error1.actual = JSON.stringify('abc'); - error1.actualType = 'string'; - error1.expected = JSON.stringify('abd'); - error1.expectedType = 'string'; + error1.avaAssertionError = true; + error1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + error1.expected = { + type: 'string', + formatted: JSON.stringify('abd') + }; const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); error2.source = source(err2Path); - error2.showOutput = true; - error2.actual = JSON.stringify([1]); - error2.actualType = 'array'; - error2.expected = JSON.stringify([2]); - error2.expectedType = 'array'; + error2.avaAssertionError = true; + error2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + error2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = createReporter({color: true}); const runStatus = createRunStatus(); @@ -490,21 +506,29 @@ test('results with errors and disabled code excerpts', t => { error1.stack = beautifyStack(error1.stack); const err1Path = tempWrite.sync('a();'); error1.source = source(err1Path, 10); - error1.showOutput = true; - error1.actual = JSON.stringify('abc'); - error1.actualType = 'string'; - error1.expected = JSON.stringify('abd'); - error1.expectedType = 'string'; + error1.avaAssertionError = true; + error1.actual = { + type: 'string', + formatted: JSON.stringify('abc') + }; + error1.expected = { + type: 'string', + formatted: JSON.stringify('abd') + }; const error2 = new Error('error two message'); error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; const err2Path = tempWrite.sync('b()'); error2.source = source(err2Path); - error2.showOutput = true; - error2.actual = JSON.stringify([1]); - error2.actualType = 'array'; - error2.expected = JSON.stringify([2]); - error2.expectedType = 'array'; + error2.avaAssertionError = true; + error2.actual = { + type: 'array', + formatted: JSON.stringify([1]) + }; + error2.expected = { + type: 'array', + formatted: JSON.stringify([2]) + }; const reporter = createReporter({color: true}); const runStatus = createRunStatus(); @@ -544,66 +568,6 @@ test('results with errors and disabled code excerpts', t => { t.end(); }); -test('results with errors and disabled assert output', t => { - const error1 = new Error('error one message'); - error1.stack = beautifyStack(error1.stack); - const err1Path = tempWrite.sync('a();'); - error1.source = source(err1Path); - error1.showOutput = false; - error1.actual = JSON.stringify('abc'); - error1.actualType = 'string'; - error1.expected = JSON.stringify('abd'); - error1.expectedType = 'string'; - - const error2 = new Error('error two message'); - error2.stack = 'error message\nTest.fn (test.js:1:1)\n'; - const err2Path = tempWrite.sync('b();'); - error2.source = source(err2Path); - error2.showOutput = true; - error2.actual = JSON.stringify([1]); - error2.actualType = 'array'; - error2.expected = JSON.stringify([2]); - error2.expectedType = 'array'; - - 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([ - '', - ' ' + chalk.red('1 test failed') + time, - '', - ' ' + chalk.bold.white('fail one'), - ' ' + chalk.grey(`${error1.source.file}:${error1.source.line}`), - '', - indentString(codeExcerpt(error1.source), 2).split('\n'), - '', - /error one message/, - '', - stackLineRegex, - compareLineOutput.SKIP_UNTIL_EMPTY_LINE, - '', - '', - '', - ' ' + chalk.bold.white('fail two'), - ' ' + chalk.grey(`${error2.source.file}:${error2.source.line}`), - '', - indentString(codeExcerpt(error2.source), 2).split('\n'), - '', - indentString(formatAssertError(error2), 2).split('\n'), - /error two message/ - ])); - t.end(); -}); - test('results when fail-fast is enabled', t => { const reporter = createReporter(); const runStatus = createRunStatus(); diff --git a/test/run-status.js b/test/run-status.js index 9c6a5e5ac..5ad30c563 100644 --- a/test/run-status.js +++ b/test/run-status.js @@ -86,33 +86,3 @@ test('calculate remaining test count', t => { t.is(runStatus.remainingCount, 5); t.end(); }); - -test('handle non-object rejections', t => { - const runStatus = new RunStatus(); - - runStatus.on('error', err => { - t.deepEqual(err, { - file: 'foo.js', - message: '42', - type: 'rejection' - }); - t.end(); - }); - - runStatus.handleRejections({file: 'foo.js', rejections: [42]}); -}); - -test('handle non-object exceptions', t => { - const runStatus = new RunStatus(); - - runStatus.on('error', err => { - t.deepEqual(err, { - file: 'bar.js', - message: '/ab/g', - type: 'exception' - }); - t.end(); - }); - - runStatus.handleExceptions({file: 'bar.js', exception: /ab/g}); -}); diff --git a/test/serialize-error.js b/test/serialize-error.js index 305ebd36d..14cb10e53 100644 --- a/test/serialize-error.js +++ b/test/serialize-error.js @@ -9,6 +9,7 @@ const sourceMapSupport = require('source-map-support'); const tempWrite = require('temp-write'); 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'); @@ -27,7 +28,9 @@ test('serialize standard props', t => { const err = new Error('Hello'); const serializedErr = serialize(err); - t.is(Object.keys(serializedErr).length, 4); + t.is(Object.keys(serializedErr).length, 6); + t.is(serializedErr.avaAssertionError, false); + t.deepEqual(serializedErr.object, {}); t.is(serializedErr.name, 'Error'); t.is(serializedErr.stack, beautifyStack(err.stack)); t.is(serializedErr.message, 'Hello'); @@ -38,6 +41,12 @@ test('serialize standard props', t => { t.end(); }); +test('additional error properties are preserved', t => { + const serializedErr = serialize(Object.assign(new Error(), {foo: 'bar'})); + t.deepEqual(serializedErr.object, {foo: 'bar'}); + t.end(); +}); + test('source file is an absolute path', t => { const err = new Error('Hello'); const serializedErr = serialize(err); @@ -117,9 +126,17 @@ test('determines whether source file, if within the project, is a dependency', t t.end(); }); -test('serialize statements', t => { - const err = new Error(); - err.showOutput = true; +test('sets avaAssertionError to true if indeed an assertion error', t => { + const err = new avaAssert.AssertionError({}); + const serializedErr = serialize(err); + t.true(serializedErr.avaAssertionError); + t.end(); +}); + +test('serialize statements of assertion errors', t => { + const err = new avaAssert.AssertionError({ + assertion: 'true' + }); err.statements = [ ['actual.a[0]', 1], ['actual.a', [1]], @@ -127,8 +144,6 @@ test('serialize statements', t => { ]; const serializedErr = serialize(err); - - t.true(serializedErr.showOutput); t.deepEqual(serializedErr.statements, JSON.stringify([ ['actual.a[0]', serializeValue(1)], ['actual.a', serializeValue([1])], @@ -137,57 +152,33 @@ test('serialize statements', t => { t.end(); }); -test('skip statements if output is off', t => { - const err = new Error(); - err.showOutput = false; - err.statements = [ - ['actual.a[0]', 1], - ['actual.a', [1]], - ['actual', {a: [1]}] - ]; - - const serializedErr = serialize(err); - - t.false(serializedErr.showOutput); - t.notOk(serializedErr.statements); - t.end(); -}); - -test('serialize actual and expected props', t => { - const err = new Error(); - err.showOutput = true; - err.actual = 1; - err.expected = 'a'; +test('serialize actual and expected props of assertion errors', t => { + const err = new avaAssert.AssertionError({ + assertion: 'is', + actual: 1, + expected: 'a' + }); const serializedErr = serialize(err); - - t.true(serializedErr.showOutput); - t.is(serializedErr.actual, serializeValue(1)); - t.is(serializedErr.expected, serializeValue('a')); - t.is(serializedErr.actualType, 'number'); - t.is(serializedErr.expectedType, 'string'); + t.is(serializedErr.actual.formatted, serializeValue(1)); + t.is(serializedErr.expected.formatted, serializeValue('a')); + t.is(serializedErr.actual.type, 'number'); + t.is(serializedErr.expected.type, 'string'); t.end(); }); -test('skip actual and expected if output is off', t => { - const err = new Error(); - err.showOutput = false; - err.actual = 1; - err.expected = 'a'; +test('only serialize actual and expected props of assertion errors if error was created with one', t => { + const err = new avaAssert.AssertionError({}); const serializedErr = serialize(err); - - t.false(serializedErr.showOutput); - t.notOk(serializedErr.actual); - t.notOk(serializedErr.expected); - t.notOk(serializedErr.actualType); - t.notOk(serializedErr.expectedType); + t.is(serializedErr.actual, undefined); + t.is(serializedErr.expected, undefined); t.end(); }); test('does not call toJSON() when serializing actual and expected', t => { - const err = Object.assign(new Error(), { - showOutput: true, + const err = new avaAssert.AssertionError({ + assertion: 'is', actual: { foo: 'bar', toJSON() { @@ -207,7 +198,7 @@ test('does not call toJSON() when serializing actual and expected', t => { }); const serializedErr = serialize(err); - t.notSame(serializedErr.actual, serializedErr.expected); + t.notSame(serializedErr.actual.formatted, serializedErr.expected); t.end(); }); diff --git a/test/test.js b/test/test.js index 95ebbf1bb..63dfb481f 100644 --- a/test/test.js +++ b/test/test.js @@ -131,14 +131,15 @@ test('run more assertions than planned', t => { t.end(); }); -test('handle non-assertion errors', t => { +test('wrap non-assertion errors', t => { + const err = new Error(); const result = ava(() => { - throw new Error(); + throw err; }).run(); t.is(result.passed, false); - t.is(result.reason.name, 'Error'); - t.true(result.reason instanceof Error); + t.is(result.reason.name, 'AssertionError'); + t.is(result.reason.actual, err); t.end(); }); @@ -157,7 +158,9 @@ test('end can be used as callback with error', t => { a.end(err); }).run().then(result => { t.is(result.passed, false); - t.is(result.reason, err); + t.is(result.reason.name, 'AssertionError'); + t.is(result.reason.actual, err); + t.is(result.reason.message, 'Callback called with an error'); t.end(); }); }); @@ -171,20 +174,20 @@ test('end can be used as callback with a non-error as its error argument', t => t.ok(result.reason); t.is(result.reason.name, 'AssertionError'); t.is(result.reason.actual, nonError); - t.is(result.reason.message, 'Callback called with an error: { foo: \'bar\' }'); + t.is(result.reason.message, 'Callback called with an error'); t.end(); }); }); test('handle non-assertion errors even when planned', t => { + const err = new Error('bar'); const result = ava(a => { a.plan(1); - throw new Error('bar'); + throw err; }).run(); t.is(result.passed, false); - t.is(result.reason.name, 'Error'); - t.is(result.reason.message, 'bar'); + t.is(result.reason.actual, err); t.end(); }); @@ -264,7 +267,7 @@ test('handle throws without error', t => { t.is(result.passed, false); t.ok(result.reason); - t.is(actual, null); + t.is(actual, undefined); t.end(); }); @@ -288,7 +291,9 @@ test('fails if a bad value is passed to t.throws', t => { t.is(result.passed, false); t.ok(result.reason); - t.is(result.reason.name, 'TypeError'); + t.is(result.reason.name, 'AssertionError'); + t.is(result.reason.message, '`t.throws()` must be called with a function, Promise, or Observable'); + t.is(result.reason.actual, 'not a function'); t.end(); }); @@ -427,9 +432,8 @@ test('fails with thrown falsy value', t => { t.is(result.passed, false); t.is(result.reason.actual, 0); - t.is(result.reason.message, 'Non-error thrown with value: 0'); + t.is(result.reason.message, 'Error thrown in test'); t.is(result.reason.name, 'AssertionError'); - t.is(result.reason.operator, 'catch'); t.end(); }); @@ -441,9 +445,8 @@ test('fails with thrown non-error object', t => { t.is(result.passed, false); t.is(result.reason.actual, obj); - t.is(result.reason.message, 'Non-error thrown with value: { foo: \'bar\' }'); + t.is(result.reason.message, 'Error thrown in test'); t.is(result.reason.name, 'AssertionError'); - t.is(result.reason.operator, 'catch'); t.end(); }); @@ -489,12 +492,13 @@ test('end should not be called multiple times', t => { }); test('cb test that throws sync', t => { + const err = new Error('foo'); const result = ava.cb(() => { - throw new Error('foo'); + throw err; }).run(); t.is(result.passed, false); - t.is(result.reason.message, 'foo'); + t.is(result.reason.actual, err); t.end(); }); @@ -575,9 +579,10 @@ test('number of assertions matches t.plan when the test exits, but before all pr }, 5); }).run().then(result => { t.is(result.passed, false); - t.is(result.reason.operator, 'plan'); + t.is(result.reason.assertion, 'plan'); t.is(result.reason.actual, 3); t.is(result.reason.expected, 2); + t.is(result.reason.operator, '==='); t.end(); }); }); @@ -592,9 +597,10 @@ test('number of assertions doesn\'t match plan when the test exits, but before a }, 5); }).run().then(result => { t.is(result.passed, false); - t.is(result.reason.operator, 'plan'); + t.is(result.reason.assertion, 'plan'); t.is(result.reason.actual, 2); t.is(result.reason.expected, 3); + t.is(result.reason.operator, '==='); t.end(); }); }); diff --git a/types/base.d.ts b/types/base.d.ts index a619b71c2..448e7aaaa 100644 --- a/types/base.d.ts +++ b/types/base.d.ts @@ -65,7 +65,7 @@ export interface AssertContext { /** * Assert that function doesn't throw an error or promise resolves. */ - notThrows(value: PromiseLike, message?: string): Promise; + notThrows(value: PromiseLike, message?: string): Promise; notThrows(value: () => void, message?: string): void; /** * Assert that contents matches regex.