diff --git a/integration_tests/__tests__/__snapshots__/failures-test.js.snap b/integration_tests/__tests__/__snapshots__/failures-test.js.snap index 6f79bb9b56cf..75ae6bd1df25 100644 --- a/integration_tests/__tests__/__snapshots__/failures-test.js.snap +++ b/integration_tests/__tests__/__snapshots__/failures-test.js.snap @@ -103,3 +103,272 @@ Ran all test suites matching \\"assertion-count-test.js\\". ", } `; + +exports[`works with node assert 1`] = ` +Object { + "rest": " FAIL __tests__/node-assertion-error-test.js + ● assert + + assert.equal(received, expected) or assert(received) + + Expected value to be (operator: ==): + true + Received: + false + + at Object. (__tests__/node-assertion-error-test.js:16:3) + + ● assert with a message + + assert.equal(received, expected) or assert(received) + + Expected value to be (operator: ==): + true + Received: + false + + Message: + this is a message + + at Object. (__tests__/node-assertion-error-test.js:20:3) + + ● assert.ok + + assert.equal(received, expected) or assert(received) + + Expected value to be (operator: ==): + true + Received: + false + + at Object. (__tests__/node-assertion-error-test.js:24:10) + + ● assert.ok with a message + + assert.equal(received, expected) or assert(received) + + Expected value to be (operator: ==): + true + Received: + false + + Message: + this is a message + + at Object. (__tests__/node-assertion-error-test.js:28:10) + + ● assert.equal + + assert.equal(received, expected) or assert(received) + + Expected value to be (operator: ==): + 2 + Received: + 1 + + at Object. (__tests__/node-assertion-error-test.js:32:10) + + ● assert.notEqual + + assert.notEqual(received, expected) + + Expected value not to be (operator: !=): + 1 + Received: + 1 + + Difference: + + Compared values have no visual difference. + + at Object. (__tests__/node-assertion-error-test.js:36:10) + + ● assert.deepEqual + + assert.deepEqual(received, expected) + + Expected value to deeply equal to: + {\\"a\\": {\\"b\\": {\\"c\\": 6}}} + Received: + {\\"a\\": {\\"b\\": {\\"c\\": 5}}} + + Difference: + + - Expected + + Received + + Object { + \\"a\\": Object { + \\"b\\": Object { + - \\"c\\": 6, + + \\"c\\": 5, + }, + }, + } + + at Object. (__tests__/node-assertion-error-test.js:40:10) + + ● assert.deepEqual with a message + + assert.deepEqual(received, expected) + + Expected value to deeply equal to: + {\\"a\\": {\\"b\\": {\\"c\\": 7}}} + Received: + {\\"a\\": {\\"b\\": {\\"c\\": 5}}} + + Message: + this is a message + + Difference: + + - Expected + + Received + + Object { + \\"a\\": Object { + \\"b\\": Object { + - \\"c\\": 7, + + \\"c\\": 5, + }, + }, + } + + at Object. (__tests__/node-assertion-error-test.js:44:10) + + ● assert.notDeepEqual + + assert.notDeepEqual(received, expected) + + Expected value not to deeply equal to: + {\\"a\\": 1} + Received: + {\\"a\\": 1} + + Difference: + + Compared values have no visual difference. + + at Object. (__tests__/node-assertion-error-test.js:48:10) + + ● assert.strictEqual + + assert.strictEqual(received, expected) + + Expected value to be (operator: ===): + NaN + Received: + 1 + + at Object. (__tests__/node-assertion-error-test.js:52:10) + + ● assert.notStrictEqual + + assert.notStrictEqual(received, expected) + + Expected value not to be (operator: !==): + 1 + Received: + 1 + + Message: + My custom error message + + Difference: + + Compared values have no visual difference. + + at Object. (__tests__/node-assertion-error-test.js:56:10) + + ● assert.deepStrictEqual + + assert.deepStrictEqual(received, expected) + + Expected value to deeply and strictly equal to: + {\\"a\\": 2} + Received: + {\\"a\\": 1} + + Difference: + + - Expected + + Received + + Object { + - \\"a\\": 2, + + \\"a\\": 1, + } + + at Object. (__tests__/node-assertion-error-test.js:60:10) + + ● assert.notDeepStrictEqual + + assert.notDeepStrictEqual(received, expected) + + Expected value not to deeply and strictly equal to: + {\\"a\\": 1} + Received: + {\\"a\\": 1} + + Difference: + + Compared values have no visual difference. + + at Object. (__tests__/node-assertion-error-test.js:64:10) + + ● assert.ifError + + Error + 1 thrown + + ● assert.doesNotThrow + + assert.doesNotThrow(function) + + Expected the function not to throw an error. + Instead, it threw: + [Error: err!] + + Message: + Got unwanted exception.. + + at Object. (__tests__/node-assertion-error-test.js:72:10) + + ● assert.throws + + assert.throws(function) + + Expected the function to throw an error. + But it didn't throw anything. + + Message: + Missing expected exception.. + + at Object. (__tests__/node-assertion-error-test.js:78:10) + + ✕ assert + ✕ assert with a message + ✕ assert.ok + ✕ assert.ok with a message + ✕ assert.equal + ✕ assert.notEqual + ✕ assert.deepEqual + ✕ assert.deepEqual with a message + ✕ assert.notDeepEqual + ✕ assert.strictEqual + ✕ assert.notStrictEqual + ✕ assert.deepStrictEqual + ✕ assert.notDeepStrictEqual + ✕ assert.ifError + ✕ assert.doesNotThrow + ✕ assert.throws + +", + "summary": "Test Suites: 1 failed, 1 total +Tests: 16 failed, 16 total +Snapshots: 0 total +Time: <> +Ran all test suites matching \\"node-assertion-error-test.js\\". +", +} +`; diff --git a/integration_tests/__tests__/failures-test.js b/integration_tests/__tests__/failures-test.js index 5e427357fab5..ff5626343191 100644 --- a/integration_tests/__tests__/failures-test.js +++ b/integration_tests/__tests__/failures-test.js @@ -21,7 +21,11 @@ skipOnWindows.suite(); // snapshot tests fail on different machines. This function makes sure // this extra line is always removed. const stripInconsistentStackLines = summary => { - summary.rest = summary.rest.replace(/\n^.*process\._tickCallback.*$/gm, ''); + summary.rest = summary.rest + .replace(/\n^.*process\._tickCallback.*$/gm, '') + .replace(/\n^.*_throws.*$/gm, '') + .replace(/\n^.*Function\..*(throws|doesNotThrow).*$/gm, '') + .replace(/(\n^.*Object.)\.test(.*$)/gm, '$1$2'); return summary; }; @@ -36,3 +40,8 @@ test('throwing not Error objects', () => { stderr = runJest(dir, ['assertion-count-test.js']).stderr; expect(stripInconsistentStackLines(extractSummary(stderr))).toMatchSnapshot(); }); + +test('works with node assert', () => { + const {stderr} = runJest(dir, ['node-assertion-error-test.js']); + expect(stripInconsistentStackLines(extractSummary(stderr))).toMatchSnapshot(); +}); diff --git a/integration_tests/failures/__tests__/node-assertion-error-test.js b/integration_tests/failures/__tests__/node-assertion-error-test.js new file mode 100644 index 000000000000..a0979f0d49ff --- /dev/null +++ b/integration_tests/failures/__tests__/node-assertion-error-test.js @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails oncall+jsinfra + */ + +'use strict'; + +const assert = require('assert'); + +test('assert', () => { + assert(false); +}); + +test('assert with a message', () => { + assert(false, 'this is a message'); +}); + +test('assert.ok', () => { + assert.ok(false); +}); + +test('assert.ok with a message', () => { + assert.ok(false, 'this is a message'); +}); + +test('assert.equal', () => { + assert.equal(1, 2); +}); + +test('assert.notEqual', () => { + assert.notEqual(1, 1); +}); + +test('assert.deepEqual', () => { + assert.deepEqual({a: {b: {c: 5}}}, {a: {b: {c: 6}}}); +}); + +test('assert.deepEqual with a message', () => { + assert.deepEqual({a: {b: {c: 5}}}, {a: {b: {c: 7}}}, 'this is a message'); +}); + +test('assert.notDeepEqual', () => { + assert.notDeepEqual({a: 1}, {a: 1}); +}); + +test('assert.strictEqual', () => { + assert.strictEqual(1, NaN); +}); + +test('assert.notStrictEqual', () => { + assert.notStrictEqual(1, 1, 'My custom error message'); +}); + +test('assert.deepStrictEqual', () => { + assert.deepStrictEqual({a: 1}, {a: 2}); +}); + +test('assert.notDeepStrictEqual', () => { + assert.notDeepStrictEqual({a: 1}, {a: 1}); +}); + +test('assert.ifError', () => { + assert.ifError(1); +}); + +test('assert.doesNotThrow', () => { + assert.doesNotThrow(() => { + throw Error('err!'); + }); +}); + +test('assert.throws', () => { + assert.throws(() => {}); +}); diff --git a/packages/jest-jasmine2/package.json b/packages/jest-jasmine2/package.json index a4220b2f6ea2..a322d5f148b1 100644 --- a/packages/jest-jasmine2/package.json +++ b/packages/jest-jasmine2/package.json @@ -8,7 +8,9 @@ "license": "BSD-3-Clause", "main": "build/index.js", "dependencies": { + "chalk": "^1.1.3", "graceful-fs": "^4.1.11", + "jest-diff": "^19.0.0", "jest-matcher-utils": "^19.0.0", "jest-matchers": "^19.0.0", "jest-message-util": "^19.0.0", diff --git a/packages/jest-jasmine2/src/assert-support.js b/packages/jest-jasmine2/src/assert-support.js new file mode 100644 index 000000000000..6afaa6638898 --- /dev/null +++ b/packages/jest-jasmine2/src/assert-support.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ + +'use strict'; + +const { + printReceived, + printExpected, +} = require('jest-matcher-utils'); +const chalk = require('chalk'); +const diff = require('jest-diff'); + +declare class AssertionError extends Error { + name: string, + actual: ?string, + expected: ?string, + operator: ?string, + message: string, + generatedMessage: boolean, +} +import type {DiffOptions} from 'jest-diff/src/diffStrings'; + +const assertOperatorsMap = { + '!=': 'notEqual', + '!==': 'notStrictEqual', + '==': 'equal', + '===': 'strictEqual', +}; + +const humanReadableOperators = { + deepEqual: 'to deeply equal', + deepStrictEqual: 'to deeply and strictly equal', + notDeepEqual: 'not to deeply equal', + notDeepStrictEqual: 'not to deeply and strictly equal', +}; + +const getOperatorName = (operator: ?string, stack: string) => { + if (typeof operator === 'string') { + return assertOperatorsMap[operator] || operator; + } + if (stack.match('.doesNotThrow')) { + return 'doesNotThrow'; + } + if (stack.match('.throws')) { + return 'throws'; + } + return ''; +}; + +const operatorMessage = (operator: ?string, negator: boolean) => + typeof operator === 'string' && + (operator.startsWith('!') || operator.startsWith('=') + ? `${negator ? 'not ' : ''}to be (operator: ${operator}):\n` + : `${humanReadableOperators[operator] || operator} to:\n`); + +const assertThrowingMatcherHint = (operatorName: string) => { + return chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('function') + + chalk.dim(')'); +}; + +const assertMatcherHint = (operator: ?string, operatorName: string) => { + let message = chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('received') + + chalk.dim(', ') + + chalk.green('expected') + + chalk.dim(')'); + + if (operator === '==') { + message += ' or ' + + chalk.dim('assert') + + chalk.dim('(') + + chalk.red('received') + + chalk.dim(') '); + } + + return message; +}; + +function assertionErrorMessage(error: AssertionError, options: DiffOptions) { + const {expected, actual, message, operator, stack} = error; + const diffString = diff(expected, actual, options); + const negator = typeof operator === 'string' && + (operator.startsWith('!') || operator.startsWith('not')); + const hasCustomMessage = !error.generatedMessage; + const operatorName = getOperatorName(operator, stack); + + if (operatorName === 'doesNotThrow') { + return assertThrowingMatcherHint(operatorName) + + '\n\n' + + chalk.reset(`Expected the function not to throw an error.\n`) + + chalk.reset(`Instead, it threw:\n`) + + ` ${printReceived(actual)}` + + chalk.reset(hasCustomMessage ? '\n\nMessage:\n ' + message : '') + + stack.replace(/AssertionError(.*)/g, ''); + } + + if (operatorName === 'throws') { + return assertThrowingMatcherHint(operatorName) + + '\n\n' + + chalk.reset(`Expected the function to throw an error.\n`) + + chalk.reset(`But it didn't throw anything.`) + + chalk.reset(hasCustomMessage ? '\n\nMessage:\n ' + message : '') + + stack.replace(/AssertionError(.*)/g, ''); + } + + return assertMatcherHint(operator, operatorName) + + '\n\n' + + chalk.reset(`Expected value ${operatorMessage(operator, negator)}`) + + ` ${printExpected(expected)}\n` + + chalk.reset(`Received:\n`) + + ` ${printReceived(actual)}` + + chalk.reset(hasCustomMessage ? '\n\nMessage:\n ' + message : '') + + (diffString ? `\n\nDifference:\n\n${diffString}` : '') + + stack.replace(/AssertionError(.*)/g, ''); +} + +module.exports = assertionErrorMessage; diff --git a/packages/jest-jasmine2/src/jasmine/Spec.js b/packages/jest-jasmine2/src/jasmine/Spec.js index 24eb7f8f88fb..6b800f921874 100644 --- a/packages/jest-jasmine2/src/jasmine/Spec.js +++ b/packages/jest-jasmine2/src/jasmine/Spec.js @@ -110,16 +110,21 @@ Spec.prototype.execute = function(onComplete, enabled) { } }; -Spec.prototype.onException = function onException(e) { - if (Spec.isPendingSpecException(e)) { - this.pend(extractCustomPendingMessage(e)); +Spec.prototype.onException = function onException(error) { + if (Spec.isPendingSpecException(error)) { + this.pend(extractCustomPendingMessage(error)); return; } - if (e instanceof ExpectationFailed) { + if (error instanceof ExpectationFailed) { return; } + if (error instanceof require('assert').AssertionError) { + const assertionErrorMessage = require('../assert-support'); + error = assertionErrorMessage(error, {expand: this.expand}); + } + this.addExpectationResult( false, { @@ -127,7 +132,7 @@ Spec.prototype.onException = function onException(e) { passed: false, expected: '', actual: '', - error: e, + error, }, true, );