From 6d31fb195bb962e46d4cb080627ebbf19966e550 Mon Sep 17 00:00:00 2001 From: Ruwan Pradeep Geeganage Date: Fri, 17 May 2019 18:41:27 +0200 Subject: [PATCH] Improve chai support (with detailed output, to match jest exceptions) (#8454) * added fix to improve chai output * fix linting issue * update the change log * fix changelog lint * added extra check to see error is not undefined * fixed the comments * fix tsc issue * fix the incorrect test case * remove assert.(a,b) message if operator not set * remove assert.(a,b) message if operator not set * fix assert message * fix linting * removed unwanted empty string in final message --- CHANGELOG.md | 2 + .../chaiAssertionLibrary.ts.snap | 85 +++++++++++++++++++ e2e/__tests__/chaiAssertionLibrary.ts | 20 +++++ .../__tests__/chai_assertion.js | 26 ++++++ .../package.json | 9 ++ e2e/chai-assertion-library-errors/yarn.lock | 47 ++++++++++ .../jest-circus/src/formatNodeAssertErrors.ts | 54 ++++++++---- .../src/assertionErrorMessage.ts | 41 +++++---- packages/jest-jasmine2/src/jasmine/Env.ts | 5 +- packages/jest-jasmine2/src/jasmine/Spec.ts | 14 ++- 10 files changed, 263 insertions(+), 40 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/chaiAssertionLibrary.ts.snap create mode 100644 e2e/__tests__/chaiAssertionLibrary.ts create mode 100644 e2e/chai-assertion-library-errors/__tests__/chai_assertion.js create mode 100644 e2e/chai-assertion-library-errors/package.json create mode 100644 e2e/chai-assertion-library-errors/yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e413a7e791..e3413014fea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-cli]` Improve chai support (with detailed output, to match jest exceptions) ([#8454](https://github.com/facebook/jest/pull/8454)) + ### Fixes - `[babel-plugin-jest-hoist]` Expand list of whitelisted globals in global mocks ([#8429](https://github.com/facebook/jest/pull/8429) diff --git a/e2e/__tests__/__snapshots__/chaiAssertionLibrary.ts.snap b/e2e/__tests__/__snapshots__/chaiAssertionLibrary.ts.snap new file mode 100644 index 000000000000..1a5f6f2363cc --- /dev/null +++ b/e2e/__tests__/__snapshots__/chaiAssertionLibrary.ts.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`chai assertion errors should display properly 1`] = ` +FAIL __tests__/chai_assertion.js + ● chai.js assertion library test › expect + + Expected value "hello sunshine" + Received: + "hello world" + + Message: + expected 'hello world' to equal 'hello sunshine' + + Difference: + + - Expected + + Received + + - hello sunshine + + hello world + + 11 | describe('chai.js assertion library test', () => { + 12 | it('expect', () => { + > 13 | chai.expect('hello world').to.equal('hello sunshine'); + | ^ + 14 | }); + 15 | + 16 | it('should', () => { + + at Object.equal (__tests__/chai_assertion.js:13:35) + + ● chai.js assertion library test › should + + Expected value "hello world" + Received: + "hello sunshine" + + Message: + expected 'hello sunshine' to equal 'hello world' + + Difference: + + - Expected + + Received + + - hello world + + hello sunshine + + 18 | const expectedString = 'hello world'; + 19 | const actualString = 'hello sunshine'; + > 20 | actualString.should.equal(expectedString); + | ^ + 21 | }); + 22 | + 23 | it('assert', () => { + + at Object.equal (__tests__/chai_assertion.js:20:25) + + ● chai.js assertion library test › assert + + Expected value "hello sunshine" + Received: + "hello world" + + Message: + expected 'hello world' to equal 'hello sunshine' + + Difference: + + - Expected + + Received + + - hello sunshine + + hello world + + 22 | + 23 | it('assert', () => { + > 24 | chai.assert.strictEqual('hello world', 'hello sunshine'); + | ^ + 25 | }); + 26 | }); + 27 | + + at Object.strictEqual (__tests__/chai_assertion.js:24:17) +`; diff --git a/e2e/__tests__/chaiAssertionLibrary.ts b/e2e/__tests__/chaiAssertionLibrary.ts new file mode 100644 index 000000000000..5e76ff3dd28a --- /dev/null +++ b/e2e/__tests__/chaiAssertionLibrary.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import {wrap} from 'jest-snapshot-serializer-raw'; +import runJest from '../runJest'; +import {extractSummary, run} from '../Utils'; + +test('chai assertion errors should display properly', () => { + const dir = path.resolve(__dirname, '../chai-assertion-library-errors'); + run('yarn', dir); + + const {stderr} = runJest('chai-assertion-library-errors'); + const {rest} = extractSummary(stderr); + expect(wrap(rest)).toMatchSnapshot(); +}); diff --git a/e2e/chai-assertion-library-errors/__tests__/chai_assertion.js b/e2e/chai-assertion-library-errors/__tests__/chai_assertion.js new file mode 100644 index 000000000000..3b247bf94f75 --- /dev/null +++ b/e2e/chai-assertion-library-errors/__tests__/chai_assertion.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; +const chai = require('chai'); + +describe('chai.js assertion library test', () => { + it('expect', () => { + chai.expect('hello world').to.equal('hello sunshine'); + }); + + it('should', () => { + chai.should(); + const expectedString = 'hello world'; + const actualString = 'hello sunshine'; + actualString.should.equal(expectedString); + }); + + it('assert', () => { + chai.assert.strictEqual('hello world', 'hello sunshine'); + }); +}); diff --git a/e2e/chai-assertion-library-errors/package.json b/e2e/chai-assertion-library-errors/package.json new file mode 100644 index 000000000000..de82c8d2a783 --- /dev/null +++ b/e2e/chai-assertion-library-errors/package.json @@ -0,0 +1,9 @@ +{ + "jest": { + "testEnvironment": "node", + "verbose": false + }, + "dependencies": { + "chai": "^4.2.0" + } +} diff --git a/e2e/chai-assertion-library-errors/yarn.lock b/e2e/chai-assertion-library-errors/yarn.lock new file mode 100644 index 000000000000..612de38aeffa --- /dev/null +++ b/e2e/chai-assertion-library-errors/yarn.lock @@ -0,0 +1,47 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +chai@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== diff --git a/packages/jest-circus/src/formatNodeAssertErrors.ts b/packages/jest-circus/src/formatNodeAssertErrors.ts index 2084d335fae9..2d086e7a2558 100644 --- a/packages/jest-circus/src/formatNodeAssertErrors.ts +++ b/packages/jest-circus/src/formatNodeAssertErrors.ts @@ -60,7 +60,7 @@ const formatNodeAssertErrors = (event: Circus.Event, state: Circus.State) => { } else { error = errors; } - return error.code === 'ERR_ASSERTION' + return isAssertionError(error) ? {message: assertionErrorMessage(error, {expand: state.expand})} : errors; }); @@ -91,22 +91,28 @@ const operatorMessage = (operator: string | undefined) => { }; const assertThrowingMatcherHint = (operatorName: string) => - chalk.dim('assert') + - chalk.dim('.' + operatorName + '(') + - chalk.red('function') + - chalk.dim(')'); + operatorName + ? chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('function') + + chalk.dim(')') + : ''; const assertMatcherHint = ( operator: string | undefined | null, operatorName: string, ) => { - let message = - chalk.dim('assert') + - chalk.dim('.' + operatorName + '(') + - chalk.red('received') + - chalk.dim(', ') + - chalk.green('expected') + - chalk.dim(')'); + let message = ''; + + if (operatorName) { + message = + chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('received') + + chalk.dim(', ') + + chalk.green('expected') + + chalk.dim(')'); + } if (operator === '==') { message += @@ -134,8 +140,7 @@ function assertionErrorMessage( if (operatorName === 'doesNotThrow') { return ( - assertThrowingMatcherHint(operatorName) + - '\n\n' + + buildHintString(assertThrowingMatcherHint(operatorName)) + chalk.reset(`Expected the function not to throw an error.\n`) + chalk.reset(`Instead, it threw:\n`) + ` ${printReceived(actual)}` + @@ -146,8 +151,7 @@ function assertionErrorMessage( if (operatorName === 'throws') { return ( - assertThrowingMatcherHint(operatorName) + - '\n\n' + + buildHintString(assertThrowingMatcherHint(operatorName)) + 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 : '') + @@ -156,8 +160,7 @@ function assertionErrorMessage( } return ( - assertMatcherHint(operator, operatorName) + - '\n\n' + + buildHintString(assertMatcherHint(operator, operatorName)) + chalk.reset(`Expected value ${operatorMessage(operator)}`) + ` ${printExpected(expected)}\n` + chalk.reset(`Received:\n`) + @@ -168,4 +171,19 @@ function assertionErrorMessage( ); } +function isAssertionError( + error: Circus.TestError, +): error is AssertionErrorWithStack { + return ( + error && + (error instanceof AssertionError || + error.name === AssertionError.name || + error.code === 'ERR_ASSERTION') + ); +} + +function buildHintString(hint: string) { + return hint ? hint + '\n\n' : ''; +} + export default formatNodeAssertErrors; diff --git a/packages/jest-jasmine2/src/assertionErrorMessage.ts b/packages/jest-jasmine2/src/assertionErrorMessage.ts index 5617d7356379..1cb63a9ecb20 100644 --- a/packages/jest-jasmine2/src/assertionErrorMessage.ts +++ b/packages/jest-jasmine2/src/assertionErrorMessage.ts @@ -55,19 +55,25 @@ const operatorMessage = (operator: string | null) => { }; const assertThrowingMatcherHint = (operatorName: string) => - chalk.dim('assert') + - chalk.dim('.' + operatorName + '(') + - chalk.red('function') + - chalk.dim(')'); + operatorName + ? chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('function') + + chalk.dim(')') + : ''; const assertMatcherHint = (operator: string | null, operatorName: string) => { - let message = - chalk.dim('assert') + - chalk.dim('.' + operatorName + '(') + - chalk.red('received') + - chalk.dim(', ') + - chalk.green('expected') + - chalk.dim(')'); + let message = ''; + + if (operatorName) { + message = + chalk.dim('assert') + + chalk.dim('.' + operatorName + '(') + + chalk.red('received') + + chalk.dim(', ') + + chalk.green('expected') + + chalk.dim(')'); + } if (operator === '==') { message += @@ -95,8 +101,7 @@ function assertionErrorMessage( if (operatorName === 'doesNotThrow') { return ( - assertThrowingMatcherHint(operatorName) + - '\n\n' + + buildHintString(assertThrowingMatcherHint(operatorName)) + chalk.reset(`Expected the function not to throw an error.\n`) + chalk.reset(`Instead, it threw:\n`) + ` ${printReceived(actual)}` + @@ -107,8 +112,7 @@ function assertionErrorMessage( if (operatorName === 'throws') { return ( - assertThrowingMatcherHint(operatorName) + - '\n\n' + + buildHintString(assertThrowingMatcherHint(operatorName)) + 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 : '') + @@ -117,8 +121,7 @@ function assertionErrorMessage( } return ( - assertMatcherHint(operator, operatorName) + - '\n\n' + + buildHintString(assertMatcherHint(operator, operatorName)) + chalk.reset(`Expected value ${operatorMessage(operator)}`) + ` ${printExpected(expected)}\n` + chalk.reset(`Received:\n`) + @@ -129,4 +132,8 @@ function assertionErrorMessage( ); } +function buildHintString(hint: string) { + return hint ? hint + '\n\n' : ''; +} + export default assertionErrorMessage; diff --git a/packages/jest-jasmine2/src/jasmine/Env.ts b/packages/jest-jasmine2/src/jasmine/Env.ts index a4946b07cde1..5515b3c368bc 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.ts +++ b/packages/jest-jasmine2/src/jasmine/Env.ts @@ -663,7 +663,10 @@ export default function(j$: Jasmine) { let checkIsError; let message; - if (error instanceof AssertionError) { + if ( + error instanceof AssertionError || + (error && error.name === AssertionError.name) + ) { checkIsError = false; // @ts-ignore TODO Possible error: j$.Spec does not have expand property message = assertionErrorMessage(error, {expand: j$.Spec.expand}); diff --git a/packages/jest-jasmine2/src/jasmine/Spec.ts b/packages/jest-jasmine2/src/jasmine/Spec.ts index b385c3e16555..e22c9f885748 100644 --- a/packages/jest-jasmine2/src/jasmine/Spec.ts +++ b/packages/jest-jasmine2/src/jasmine/Spec.ts @@ -236,10 +236,9 @@ export default class Spec { passed: false, expected: '', actual: '', - error: - error instanceof AssertionError - ? assertionErrorMessage(error, {expand: this.expand}) - : error, + error: this.isAssertionError(error) + ? assertionErrorMessage(error, {expand: this.expand}) + : error, }, true, ); @@ -292,6 +291,13 @@ export default class Spec { getFullName() { return this.getSpecName(this); } + + isAssertionError(error: Error) { + return ( + error instanceof AssertionError || + (error && error.name === AssertionError.name) + ); + } } Spec.pendingSpecExceptionMessage = '=> marked Pending';