Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve chai support (with detailed output, to match jest exceptions) #8454

Merged
merged 13 commits into from
May 17, 2019
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions e2e/__tests__/__snapshots__/chaiAssertionLibrary.ts.snap
Original file line number Diff line number Diff line change
@@ -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)
`;
20 changes: 20 additions & 0 deletions e2e/__tests__/chaiAssertionLibrary.ts
Original file line number Diff line number Diff line change
@@ -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();
});
26 changes: 26 additions & 0 deletions e2e/chai-assertion-library-errors/__tests__/chai_assertion.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
9 changes: 9 additions & 0 deletions e2e/chai-assertion-library-errors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"jest": {
"testEnvironment": "node",
"verbose": false
},
"dependencies": {
"chai": "^4.2.0"
}
}
47 changes: 47 additions & 0 deletions e2e/chai-assertion-library-errors/yarn.lock
Original file line number Diff line number Diff line change
@@ -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==
54 changes: 36 additions & 18 deletions packages/jest-circus/src/formatNodeAssertErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
Expand Down Expand Up @@ -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 +=
Expand Down Expand Up @@ -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)}` +
Expand All @@ -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 : '') +
Expand All @@ -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`) +
Expand All @@ -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')
);
rpgeeganage marked this conversation as resolved.
Show resolved Hide resolved
}

function buildHintString(hint: string) {
return hint ? hint + '\n\n' : '';
}

export default formatNodeAssertErrors;
41 changes: 24 additions & 17 deletions packages/jest-jasmine2/src/assertionErrorMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 +=
Expand Down Expand Up @@ -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)}` +
Expand All @@ -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 : '') +
Expand All @@ -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`) +
Expand All @@ -129,4 +132,8 @@ function assertionErrorMessage(
);
}

function buildHintString(hint: string) {
return hint ? hint + '\n\n' : '';
}

export default assertionErrorMessage;
5 changes: 4 additions & 1 deletion packages/jest-jasmine2/src/jasmine/Env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down
Loading