|
|
'use strict';
Object.defineProperty(exports, '__esModule', { value: true }); exports.matcherHint = exports.matcherErrorMessage = exports.getLabelPrinter = exports.pluralize = exports.diff = exports.printDiffOrStringify = exports.ensureExpectedIsNonNegativeInteger = exports.ensureNumbers = exports.ensureExpectedIsNumber = exports.ensureActualIsNumber = exports.ensureNoExpected = exports.printWithType = exports.printExpected = exports.printReceived = exports.highlightTrailingWhitespace = exports.stringify = exports.SUGGEST_TO_CONTAIN_EQUAL = exports.DIM_COLOR = exports.BOLD_WEIGHT = exports.INVERTED_COLOR = exports.RECEIVED_COLOR = exports.EXPECTED_COLOR = void 0;
var _chalk = _interopRequireDefault(require('chalk'));
var _jestDiff = _interopRequireWildcard(require('jest-diff'));
var _jestGetType = _interopRequireDefault(require('jest-get-type'));
var _prettyFormat = _interopRequireDefault(require('pretty-format'));
var _Replaceable = _interopRequireDefault(require('./Replaceable'));
var _deepCyclicCopyReplaceable = _interopRequireDefault( require('./deepCyclicCopyReplaceable') );
function _getRequireWildcardCache() { if (typeof WeakMap !== 'function') return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) { return {default: obj}; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; }
/** * 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. */
/* eslint-disable local/ban-types-eventually */ const { AsymmetricMatcher, DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent } = _prettyFormat.default.plugins; const PLUGINS = [ ReactTestComponent, ReactElement, DOMElement, DOMCollection, Immutable, AsymmetricMatcher ]; const EXPECTED_COLOR = _chalk.default.green; exports.EXPECTED_COLOR = EXPECTED_COLOR; const RECEIVED_COLOR = _chalk.default.red; exports.RECEIVED_COLOR = RECEIVED_COLOR; const INVERTED_COLOR = _chalk.default.inverse; exports.INVERTED_COLOR = INVERTED_COLOR; const BOLD_WEIGHT = _chalk.default.bold; exports.BOLD_WEIGHT = BOLD_WEIGHT; const DIM_COLOR = _chalk.default.dim; exports.DIM_COLOR = DIM_COLOR; const MULTILINE_REGEXP = /\n/; const SPACE_SYMBOL = '\u{00B7}'; // middle dot
const NUMBERS = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen' ];
const SUGGEST_TO_CONTAIN_EQUAL = _chalk.default.dim( 'Looks like you wanted to test for object/array equality with the stricter `toContain` matcher. You probably need to use `toContainEqual` instead.' );
exports.SUGGEST_TO_CONTAIN_EQUAL = SUGGEST_TO_CONTAIN_EQUAL;
const stringify = (object, maxDepth = 10) => { const MAX_LENGTH = 10000; let result;
try { result = (0, _prettyFormat.default)(object, { maxDepth, min: true, plugins: PLUGINS }); } catch { result = (0, _prettyFormat.default)(object, { callToJSON: false, maxDepth, min: true, plugins: PLUGINS }); }
return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(maxDepth / 2)) : result; };
exports.stringify = stringify;
const highlightTrailingWhitespace = text => text.replace(/\s+$/gm, _chalk.default.inverse('$&')); // Instead of inverse highlight which now implies a change,
// replace common spaces with middle dot at the end of any line.
exports.highlightTrailingWhitespace = highlightTrailingWhitespace;
const replaceTrailingSpaces = text => text.replace(/\s+$/gm, spaces => SPACE_SYMBOL.repeat(spaces.length));
const printReceived = object => RECEIVED_COLOR(replaceTrailingSpaces(stringify(object)));
exports.printReceived = printReceived;
const printExpected = value => EXPECTED_COLOR(replaceTrailingSpaces(stringify(value)));
exports.printExpected = printExpected;
const printWithType = (name, value, print) => { const type = (0, _jestGetType.default)(value); const hasType = type !== 'null' && type !== 'undefined' ? `${name} has type: ${type}\n` : ''; const hasValue = `${name} has value: ${print(value)}`; return hasType + hasValue; };
exports.printWithType = printWithType;
const ensureNoExpected = (expected, matcherName, options) => { if (typeof expected !== 'undefined') { // Prepend maybe not only for backward compatibility.
const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( matcherHint(matcherString, undefined, '', options), // Because expected is omitted in hint above,
// expected is black instead of green in message below.
'this matcher must not have an expected argument', printWithType('Expected', expected, printExpected) ) ); } }; /** * Ensures that `actual` is of type `number | bigint` */
exports.ensureNoExpected = ensureNoExpected;
const ensureActualIsNumber = (actual, matcherName, options) => { if (typeof actual !== 'number' && typeof actual !== 'bigint') { // Prepend maybe not only for backward compatibility.
const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( matcherHint(matcherString, undefined, undefined, options), `${RECEIVED_COLOR('received')} value must be a number or bigint`, printWithType('Received', actual, printReceived) ) ); } }; /** * Ensures that `expected` is of type `number | bigint` */
exports.ensureActualIsNumber = ensureActualIsNumber;
const ensureExpectedIsNumber = (expected, matcherName, options) => { if (typeof expected !== 'number' && typeof expected !== 'bigint') { // Prepend maybe not only for backward compatibility.
const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( matcherHint(matcherString, undefined, undefined, options), `${EXPECTED_COLOR('expected')} value must be a number or bigint`, printWithType('Expected', expected, printExpected) ) ); } }; /** * Ensures that `actual` & `expected` are of type `number | bigint` */
exports.ensureExpectedIsNumber = ensureExpectedIsNumber;
const ensureNumbers = (actual, expected, matcherName, options) => { ensureActualIsNumber(actual, matcherName, options); ensureExpectedIsNumber(expected, matcherName, options); };
exports.ensureNumbers = ensureNumbers;
const ensureExpectedIsNonNegativeInteger = (expected, matcherName, options) => { if ( typeof expected !== 'number' || !Number.isSafeInteger(expected) || expected < 0 ) { // Prepend maybe not only for backward compatibility.
const matcherString = (options ? '' : '[.not]') + matcherName; throw new Error( matcherErrorMessage( matcherHint(matcherString, undefined, undefined, options), `${EXPECTED_COLOR('expected')} value must be a non-negative integer`, printWithType('Expected', expected, printExpected) ) ); } }; // Given array of diffs, return concatenated string:
// * include common substrings
// * exclude change substrings which have opposite op
// * include change substrings which have argument op
// with inverse highlight only if there is a common substring
exports.ensureExpectedIsNonNegativeInteger = ensureExpectedIsNonNegativeInteger;
const getCommonAndChangedSubstrings = (diffs, op, hasCommonDiff) => diffs.reduce( (reduced, diff) => reduced + (diff[0] === _jestDiff.DIFF_EQUAL ? diff[1] : diff[0] !== op ? '' : hasCommonDiff ? INVERTED_COLOR(diff[1]) : diff[1]), '' );
const isLineDiffable = (expected, received) => { const expectedType = (0, _jestGetType.default)(expected); const receivedType = (0, _jestGetType.default)(received);
if (expectedType !== receivedType) { return false; }
if (_jestGetType.default.isPrimitive(expected)) { // Print generic line diff for strings only:
// * if neither string is empty
// * if either string has more than one line
return ( typeof expected === 'string' && typeof received === 'string' && expected.length !== 0 && received.length !== 0 && (MULTILINE_REGEXP.test(expected) || MULTILINE_REGEXP.test(received)) ); }
if ( expectedType === 'date' || expectedType === 'function' || expectedType === 'regexp' ) { return false; }
if (expected instanceof Error && received instanceof Error) { return false; }
if ( expectedType === 'object' && typeof expected.asymmetricMatch === 'function' ) { return false; }
if ( receivedType === 'object' && typeof received.asymmetricMatch === 'function' ) { return false; }
return true; };
const MAX_DIFF_STRING_LENGTH = 20000;
const printDiffOrStringify = ( expected, received, expectedLabel, receivedLabel, expand ) => { if ( typeof expected === 'string' && typeof received === 'string' && expected.length !== 0 && received.length !== 0 && expected.length <= MAX_DIFF_STRING_LENGTH && received.length <= MAX_DIFF_STRING_LENGTH && expected !== received ) { if (expected.includes('\n') || received.includes('\n')) { return (0, _jestDiff.diffStringsUnified)(expected, received, { aAnnotation: expectedLabel, bAnnotation: receivedLabel, changeLineTrailingSpaceColor: _chalk.default.bgYellow, commonLineTrailingSpaceColor: _chalk.default.bgYellow, emptyFirstOrLastLinePlaceholder: '↵', // U+21B5
expand, includeChangeCounts: true }); }
const diffs = (0, _jestDiff.diffStringsRaw)(expected, received, true); const hasCommonDiff = diffs.some(diff => diff[0] === _jestDiff.DIFF_EQUAL); const printLabel = getLabelPrinter(expectedLabel, receivedLabel); const expectedLine = printLabel(expectedLabel) + printExpected( getCommonAndChangedSubstrings( diffs, _jestDiff.DIFF_DELETE, hasCommonDiff ) ); const receivedLine = printLabel(receivedLabel) + printReceived( getCommonAndChangedSubstrings( diffs, _jestDiff.DIFF_INSERT, hasCommonDiff ) ); return expectedLine + '\n' + receivedLine; }
if (isLineDiffable(expected, received)) { const { replacedExpected, replacedReceived } = replaceMatchedToAsymmetricMatcher( (0, _deepCyclicCopyReplaceable.default)(expected), (0, _deepCyclicCopyReplaceable.default)(received), [], [] ); const difference = (0, _jestDiff.default)( replacedExpected, replacedReceived, { aAnnotation: expectedLabel, bAnnotation: receivedLabel, expand, includeChangeCounts: true } );
if ( typeof difference === 'string' && difference.includes('- ' + expectedLabel) && difference.includes('+ ' + receivedLabel) ) { return difference; } }
const printLabel = getLabelPrinter(expectedLabel, receivedLabel); const expectedLine = printLabel(expectedLabel) + printExpected(expected); const receivedLine = printLabel(receivedLabel) + (stringify(expected) === stringify(received) ? 'serializes to the same string' : printReceived(received)); return expectedLine + '\n' + receivedLine; }; // Sometimes, e.g. when comparing two numbers, the output from jest-diff
// does not contain more information than the `Expected:` / `Received:` already gives.
// In those cases, we do not print a diff to make the output shorter and not redundant.
exports.printDiffOrStringify = printDiffOrStringify;
const shouldPrintDiff = (actual, expected) => { if (typeof actual === 'number' && typeof expected === 'number') { return false; }
if (typeof actual === 'bigint' && typeof expected === 'bigint') { return false; }
if (typeof actual === 'boolean' && typeof expected === 'boolean') { return false; }
return true; };
function replaceMatchedToAsymmetricMatcher( replacedExpected, replacedReceived, expectedCycles, receivedCycles ) { if (!_Replaceable.default.isReplaceable(replacedExpected, replacedReceived)) { return { replacedExpected, replacedReceived }; }
if ( expectedCycles.includes(replacedExpected) || receivedCycles.includes(replacedReceived) ) { return { replacedExpected, replacedReceived }; }
expectedCycles.push(replacedExpected); receivedCycles.push(replacedReceived); const expectedReplaceable = new _Replaceable.default(replacedExpected); const receivedReplaceable = new _Replaceable.default(replacedReceived); expectedReplaceable.forEach((expectedValue, key) => { const receivedValue = receivedReplaceable.get(key);
if (isAsymmetricMatcher(expectedValue)) { if (expectedValue.asymmetricMatch(receivedValue)) { receivedReplaceable.set(key, expectedValue); } } else if (isAsymmetricMatcher(receivedValue)) { if (receivedValue.asymmetricMatch(expectedValue)) { expectedReplaceable.set(key, receivedValue); } } else if ( _Replaceable.default.isReplaceable(expectedValue, receivedValue) ) { const replaced = replaceMatchedToAsymmetricMatcher( expectedValue, receivedValue, expectedCycles, receivedCycles ); expectedReplaceable.set(key, replaced.replacedExpected); receivedReplaceable.set(key, replaced.replacedReceived); } }); return { replacedExpected: expectedReplaceable.object, replacedReceived: receivedReplaceable.object }; }
function isAsymmetricMatcher(data) { const type = (0, _jestGetType.default)(data); return type === 'object' && typeof data.asymmetricMatch === 'function'; }
const diff = (a, b, options) => shouldPrintDiff(a, b) ? (0, _jestDiff.default)(a, b, options) : null;
exports.diff = diff;
const pluralize = (word, count) => (NUMBERS[count] || count) + ' ' + word + (count === 1 ? '' : 's'); // To display lines of labeled values as two columns with monospace alignment:
// given the strings which will describe the values,
// return function which given each string, returns the label:
// string, colon, space, and enough padding spaces to align the value.
exports.pluralize = pluralize;
const getLabelPrinter = (...strings) => { const maxLength = strings.reduce( (max, string) => (string.length > max ? string.length : max), 0 ); return string => `${string}: ${' '.repeat(maxLength - string.length)}`; };
exports.getLabelPrinter = getLabelPrinter;
const matcherErrorMessage = (hint, generic, specific) => `${hint}\n\n${_chalk.default.bold('Matcher error')}: ${generic}${ typeof specific === 'string' ? '\n\n' + specific : '' }`; // Display assertion for the report when a test fails.
// New format: rejects/resolves, not, and matcher name have black color
// Old format: matcher name has dim color
exports.matcherErrorMessage = matcherErrorMessage;
const matcherHint = ( matcherName, received = 'received', expected = 'expected', options = {} ) => { const { comment = '', expectedColor = EXPECTED_COLOR, isDirectExpectCall = false, // seems redundant with received === ''
isNot = false, promise = '', receivedColor = RECEIVED_COLOR, secondArgument = '', secondArgumentColor = EXPECTED_COLOR } = options; let hint = ''; let dimString = 'expect'; // concatenate adjacent dim substrings
if (!isDirectExpectCall && received !== '') { hint += DIM_COLOR(dimString + '(') + receivedColor(received); dimString = ')'; }
if (promise !== '') { hint += DIM_COLOR(dimString + '.') + promise; dimString = ''; }
if (isNot) { hint += DIM_COLOR(dimString + '.') + 'not'; dimString = ''; }
if (matcherName.includes('.')) { // Old format: for backward compatibility,
// especially without promise or isNot options
dimString += matcherName; } else { // New format: omit period from matcherName arg
hint += DIM_COLOR(dimString + '.') + matcherName; dimString = ''; }
if (expected === '') { dimString += '()'; } else { hint += DIM_COLOR(dimString + '(') + expectedColor(expected);
if (secondArgument) { hint += DIM_COLOR(', ') + secondArgumentColor(secondArgument); }
dimString = ')'; }
if (comment !== '') { dimString += ' // ' + comment; }
if (dimString !== '') { hint += DIM_COLOR(dimString); }
return hint; };
exports.matcherHint = matcherHint;
|