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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
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
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