Skip to content

Commit

Permalink
[WIP] Failure messages
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronabramov committed Jun 8, 2017
1 parent 92e432a commit 8719053
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 3 deletions.
1 change: 1 addition & 0 deletions integration_tests/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const cleanupStackTrace = (output: string) => {
.replace(/\n.*at.*next_tick\.js.*$/gm, '')
.replace(/\n.*at Promise \(<anonymous>\).*$/gm, '')
.replace(/\n.*at <anonymous>.*$/gm, '')
.replace(/\n.*at Generator.next \(<anonymous>\).*$/gm, '')
.replace(/^.*at.*[\s][\(]?(\S*\:\d*\:\d*).*$/gm, ' at $1');
};

Expand Down
3 changes: 2 additions & 1 deletion packages/jest-circus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dependencies": {
"jest-snapshot": "^20.0.3",
"jest-matchers": "^20.0.3",
"jest-message-util": "^20.0.3"
"jest-message-util": "^20.0.3",
"jest-diff": "^20.0.3"
},
"devDependencies": {
"jest-runtime": "^20.0.3"
Expand Down
149 changes: 149 additions & 0 deletions packages/jest-circus/src/formatNodeAssertErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* 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.
*
* @flow
*/

import type {DiffOptions} from 'jest-diff/src/diffStrings';
import type {Event, State} from '../types';

const {printReceived, printExpected} = require('jest-matcher-utils');
const chalk = require('chalk');
const diff = require('jest-diff');

type AssertionError = {|
actual: ?string,
expected: ?string,
generatedMessage: boolean,
message: string,
name: string,
operator: ?string,
stack: string,
|};

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',
};

module.exports = (event: Event, state: State) => {
switch (event.name) {
case 'test_failure':
case 'test_success': {
event.test.errors = event.test.errors.map(error => {
return error instanceof require('assert').AssertionError
? assertionErrorMessage(error, {expand: state.expand})
: error;
});
break;
}
}
};

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, '')
);
}
7 changes: 6 additions & 1 deletion packages/jest-circus/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ import type {Event, State, EventHandler} from '../types';

const {makeDescribe} = require('./utils');
const eventHandler = require('./eventHandler');
const formatNodeAssertErrors = require('./formatNodeAssertErrors');

const eventHandlers: Array<EventHandler> = [eventHandler];
const eventHandlers: Array<EventHandler> = [
eventHandler,
formatNodeAssertErrors,
];

const ROOT_DESCRIBE_BLOCK_NAME = 'ROOT_DESCRIBE_BLOCK';
const STATE_SYM = Symbol('JEST_STATE_SYMBOL');

const ROOT_DESCRIBE_BLOCK = makeDescribe(ROOT_DESCRIBE_BLOCK_NAME);
const INITIAL_STATE: State = {
currentDescribeBlock: ROOT_DESCRIBE_BLOCK,
expand: undefined,
hasFocusedTests: false,
rootDescribeBlock: ROOT_DESCRIBE_BLOCK,
testTimeout: 5000,
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-circus/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ const _formatError = (error: ?Exception): string => {
} else if (error.message) {
return error.message;
} else {
return String(error);
return `${String(error)} thrown`;
}
};

Expand Down
1 change: 1 addition & 0 deletions packages/jest-circus/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type State = {|
hasFocusedTests: boolean, // that are defined using test.only
rootDescribeBlock: DescribeBlock,
testTimeout: number,
expand?: boolean, // expand error messages
|};

export type DescribeBlock = {|
Expand Down

0 comments on commit 8719053

Please sign in to comment.