From 142f85fc6af91fcc308e6d50a61d26787b5ca184 Mon Sep 17 00:00:00 2001 From: Michael Rienstra Date: Thu, 9 Sep 2021 10:54:42 -0700 Subject: [PATCH] chore(expect): add all print utils to main expect export (expect.print) (print utils from src/print) --- packages/expect/src/index.ts | 10 +- packages/expect/src/matchers.ts | 33 ++--- packages/expect/src/print.ts | 194 ++++++++++++------------- packages/expect/src/toThrowMatchers.ts | 21 +-- packages/expect/src/types.ts | 42 ++++-- 5 files changed, 141 insertions(+), 159 deletions(-) diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 6f514edf2d20..6f24e6aa2fbf 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -31,10 +31,7 @@ import { setState, } from './jestMatchersObject'; import matchers from './matchers'; -import { - printReceivedStringContainExpectedResult, - printReceivedStringContainExpectedSubstring, -} from './print'; +import print from './print'; import spyMatchers from './spyMatchers'; import toThrowMatchers, { createMatcher as createThrowMatcher, @@ -362,10 +359,7 @@ expect.extend = (matchers: MatchersObject): void => setMatchers(matchers, false, expect); expect.matchers = matchers; -expect.print = { - printReceivedStringContainExpectedResult, - printReceivedStringContainExpectedSubstring, -}; +expect.print = print; expect.anything = anything; expect.any = any; diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index d0ec1480dab4..82a7884646d7 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -28,16 +28,7 @@ import { stringify, } from 'jest-matcher-utils'; import {equals} from './jasmineUtils'; -import { - printCloseTo, - printExpectedConstructorName, - printExpectedConstructorNameNot, - printReceivedArrayContainExpectedItem, - printReceivedConstructorName, - printReceivedConstructorNameNot, - printReceivedStringContainExpectedResult, - printReceivedStringContainExpectedSubstring, -} from './print'; +import print from './print'; import type {MatcherState, MatchersObject} from './types'; import { getObjectSubset, @@ -183,14 +174,14 @@ const matchers: MatchersObject = { ? '' : `Received: ${printReceived(received)}\n` + '\n' + - printCloseTo(receivedDiff, expectedDiff, precision, isNot)) + print.closeTo(receivedDiff, expectedDiff, precision, isNot)) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected: ${printExpected(expected)}\n` + `Received: ${printReceived(received)}\n` + '\n' + - printCloseTo(receivedDiff, expectedDiff, precision, isNot); + print.closeTo(receivedDiff, expectedDiff, precision, isNot); return {message, pass}; }, @@ -302,10 +293,10 @@ const matchers: MatchersObject = { ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - printExpectedConstructorNameNot('Expected constructor', expected) + + print.expectedConstructorNameNot('Expected constructor', expected) + (typeof received.constructor === 'function' && received.constructor !== expected - ? printReceivedConstructorNameNot( + ? print.receivedConstructorNameNot( 'Received constructor', received.constructor, expected, @@ -314,14 +305,14 @@ const matchers: MatchersObject = { : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - printExpectedConstructorName('Expected constructor', expected) + + print.expectedConstructorName('Expected constructor', expected) + (isPrimitive(received) || Object.getPrototypeOf(received) === null ? `\nReceived value has no prototype\nReceived value: ${printReceived( received, )}` : typeof received.constructor !== 'function' ? `\nReceived value: ${printReceived(received)}` - : printReceivedConstructorName( + : print.receivedConstructorName( 'Received constructor', received.constructor, )); @@ -509,7 +500,7 @@ const matchers: MatchersObject = { )}\n` + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ isNot - ? printReceivedStringContainExpectedSubstring( + ? print.receivedStringContainExpectedSubstring( received, index, String(expected).length, @@ -539,7 +530,7 @@ const matchers: MatchersObject = { )}\n` + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ isNot && Array.isArray(received) - ? printReceivedArrayContainExpectedItem(received, index) + ? print.receivedArrayContainExpectedItem(received, index) : printReceived(received) }` + (!isNot && @@ -595,7 +586,7 @@ const matchers: MatchersObject = { )}\n` + `${printLabel(labelReceived)}${isNot ? ' ' : ''}${ isNot && Array.isArray(received) - ? printReceivedArrayContainExpectedItem(received, index) + ? print.receivedArrayContainExpectedItem(received, index) : printReceived(received) }` ); @@ -859,7 +850,7 @@ const matchers: MatchersObject = { ? matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected substring: not ${printExpected(expected)}\n` + - `Received string: ${printReceivedStringContainExpectedSubstring( + `Received string: ${print.receivedStringContainExpectedSubstring( received, received.indexOf(expected), expected.length, @@ -867,7 +858,7 @@ const matchers: MatchersObject = { : matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected pattern: not ${printExpected(expected)}\n` + - `Received string: ${printReceivedStringContainExpectedResult( + `Received string: ${print.receivedStringContainExpectedResult( received, typeof expected.exec === 'function' ? expected.exec(received) diff --git a/packages/expect/src/print.ts b/packages/expect/src/print.ts index a9f84e5243f1..ae78a0857cb3 100644 --- a/packages/expect/src/print.ts +++ b/packages/expect/src/print.ts @@ -15,116 +15,12 @@ import { printReceived, stringify, } from 'jest-matcher-utils'; +import type {PrintObject} from './types'; // Format substring but do not enclose in double quote marks. // The replacement is compatible with pretty-format package. const printSubstring = (val: string): string => val.replace(/"|\\/g, '\\$&'); -export const printReceivedStringContainExpectedSubstring = ( - received: string, - start: number, - length: number, // not end -): string => - RECEIVED_COLOR( - '"' + - printSubstring(received.slice(0, start)) + - INVERTED_COLOR(printSubstring(received.slice(start, start + length))) + - printSubstring(received.slice(start + length)) + - '"', - ); - -export const printReceivedStringContainExpectedResult = ( - received: string, - result: RegExpExecArray | null, -): string => - result === null - ? printReceived(received) - : printReceivedStringContainExpectedSubstring( - received, - result.index, - result[0].length, - ); - -// The serialized array is compatible with pretty-format package min option. -// However, items have default stringify depth (instead of depth - 1) -// so expected item looks consistent by itself and enclosed in the array. -export const printReceivedArrayContainExpectedItem = ( - received: Array, - index: number, -): string => - RECEIVED_COLOR( - '[' + - received - .map((item, i) => { - const stringified = stringify(item); - return i === index ? INVERTED_COLOR(stringified) : stringified; - }) - .join(', ') + - ']', - ); - -export const printCloseTo = ( - receivedDiff: number, - expectedDiff: number, - precision: number, - isNot: boolean, -): string => { - const receivedDiffString = stringify(receivedDiff); - const expectedDiffString = receivedDiffString.includes('e') - ? // toExponential arg is number of digits after the decimal point. - expectedDiff.toExponential(0) - : 0 <= precision && precision < 20 - ? // toFixed arg is number of digits after the decimal point. - // It may be a value between 0 and 20 inclusive. - // Implementations may optionally support a larger range of values. - expectedDiff.toFixed(precision + 1) - : stringify(expectedDiff); - - return ( - `Expected precision: ${isNot ? ' ' : ''} ${stringify(precision)}\n` + - `Expected difference: ${isNot ? 'not ' : ''}< ${EXPECTED_COLOR( - expectedDiffString, - )}\n` + - `Received difference: ${isNot ? ' ' : ''} ${RECEIVED_COLOR( - receivedDiffString, - )}` - ); -}; - -export const printExpectedConstructorName = ( - label: string, - expected: Function, -): string => printConstructorName(label, expected, false, true) + '\n'; - -export const printExpectedConstructorNameNot = ( - label: string, - expected: Function, -): string => printConstructorName(label, expected, true, true) + '\n'; - -export const printReceivedConstructorName = ( - label: string, - received: Function, -): string => printConstructorName(label, received, false, false) + '\n'; - -// Do not call function if received is equal to expected. -export const printReceivedConstructorNameNot = ( - label: string, - received: Function, - expected: Function, -): string => - typeof expected.name === 'string' && - expected.name.length !== 0 && - typeof received.name === 'string' && - received.name.length !== 0 - ? printConstructorName(label, received, true, false) + - ` ${ - Object.getPrototypeOf(received) === expected - ? 'extends' - : 'extends … extends' - } ${EXPECTED_COLOR(expected.name)}` + - '\n' - : printConstructorName(label, received, false, false) + '\n'; - const printConstructorName = ( label: string, constructor: Function, @@ -140,3 +36,91 @@ const printConstructorName = ( ? EXPECTED_COLOR(constructor.name) : RECEIVED_COLOR(constructor.name) }`; + +const print: PrintObject = { + closeTo: (receivedDiff, expectedDiff, precision, isNot) => { + const receivedDiffString = stringify(receivedDiff); + const expectedDiffString = receivedDiffString.includes('e') + ? // toExponential arg is number of digits after the decimal point. + expectedDiff.toExponential(0) + : 0 <= precision && precision < 20 + ? // toFixed arg is number of digits after the decimal point. + // It may be a value between 0 and 20 inclusive. + // Implementations may optionally support a larger range of values. + expectedDiff.toFixed(precision + 1) + : stringify(expectedDiff); + + return ( + `Expected precision: ${isNot ? ' ' : ''} ${stringify(precision)}\n` + + `Expected difference: ${isNot ? 'not ' : ''}< ${EXPECTED_COLOR( + expectedDiffString, + )}\n` + + `Received difference: ${isNot ? ' ' : ''} ${RECEIVED_COLOR( + receivedDiffString, + )}` + ); + }, + + expectedConstructorName: (label, expected) => + printConstructorName(label, expected, false, true) + '\n', + + expectedConstructorNameNot: (label, expected) => + printConstructorName(label, expected, true, true) + '\n', + + // The serialized array is compatible with pretty-format package min option. + // However, items have default stringify depth (instead of depth - 1) + // so expected item looks consistent by itself and enclosed in the array. + receivedArrayContainExpectedItem: (received, index) => + RECEIVED_COLOR( + '[' + + received + .map((item, i) => { + const stringified = stringify(item); + return i === index ? INVERTED_COLOR(stringified) : stringified; + }) + .join(', ') + + ']', + ), + + receivedConstructorName: (label, received) => + printConstructorName(label, received, false, false) + '\n', + + // Do not call function if received is equal to expected. + receivedConstructorNameNot: (label, received, expected): string => + typeof expected.name === 'string' && + expected.name.length !== 0 && + typeof received.name === 'string' && + received.name.length !== 0 + ? printConstructorName(label, received, true, false) + + ` ${ + Object.getPrototypeOf(received) === expected + ? 'extends' + : 'extends … extends' + } ${EXPECTED_COLOR(expected.name)}` + + '\n' + : printConstructorName(label, received, false, false) + '\n', + + receivedStringContainExpectedResult: (received, result) => + result === null + ? printReceived(received) + : print.receivedStringContainExpectedSubstring( + received, + result.index, + result[0].length, + ), + + receivedStringContainExpectedSubstring: ( + received, + start, + length, // not end + ) => + RECEIVED_COLOR( + '"' + + printSubstring(received.slice(0, start)) + + INVERTED_COLOR(printSubstring(received.slice(start, start + length))) + + printSubstring(received.slice(start + length)) + + '"', + ), +}; + +export default print; diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index 8be9a79ae5fd..aa73566ffeae 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -20,14 +20,7 @@ import { printWithType, } from 'jest-matcher-utils'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; -import { - printExpectedConstructorName, - printExpectedConstructorNameNot, - printReceivedConstructorName, - printReceivedConstructorNameNot, - printReceivedStringContainExpectedResult, - printReceivedStringContainExpectedSubstring, -} from './print'; +import print from './print'; import type { ExpectationResult, MatcherState, @@ -271,12 +264,12 @@ const toThrowExpectedClass = ( ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - printExpectedConstructorNameNot('Expected constructor', expected) + + print.expectedConstructorNameNot('Expected constructor', expected) + (thrown !== null && thrown.value != null && typeof thrown.value.constructor === 'function' && thrown.value.constructor !== expected - ? printReceivedConstructorNameNot( + ? print.receivedConstructorNameNot( 'Received constructor', thrown.value.constructor, expected, @@ -290,12 +283,12 @@ const toThrowExpectedClass = ( : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - printExpectedConstructorName('Expected constructor', expected) + + print.expectedConstructorName('Expected constructor', expected) + (thrown === null ? '\n' + DID_NOT_THROW : (thrown.value != null && typeof thrown.value.constructor === 'function' - ? printReceivedConstructorName( + ? print.receivedConstructorName( 'Received constructor', thrown.value.constructor, ) @@ -389,7 +382,7 @@ const formatReceived = ( if (index !== -1) { return ( label + - printReceivedStringContainExpectedSubstring( + print.receivedStringContainExpectedSubstring( message, index, expected.length, @@ -400,7 +393,7 @@ const formatReceived = ( } else if (expected instanceof RegExp) { return ( label + - printReceivedStringContainExpectedResult( + print.receivedStringContainExpectedResult( message, typeof expected.exec === 'function' ? expected.exec(message) : null, ) + diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 9e920e745d4a..47a31eeed5b8 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -29,6 +29,36 @@ export type RawMatcherFn = { export type ThrowingMatcherFn = (actual: any) => void; export type PromiseMatcherFn = (actual: any) => Promise; +export type PrintObject = { + closeTo: ( + receivedDiff: number, + expectedDiff: number, + precision: number, + isNot: boolean, + ) => string; + expectedConstructorName: (label: string, expected: Function) => string; + expectedConstructorNameNot: (label: string, expected: Function) => string; + receivedArrayContainExpectedItem: ( + received: Array, + index: number, + ) => string; + receivedConstructorName: (label: string, received: Function) => string; + receivedConstructorNameNot: ( + label: string, + received: Function, + expected: Function, + ) => string; + receivedStringContainExpectedResult: ( + received: string, + result: RegExpExecArray | null, + ) => string; + receivedStringContainExpectedSubstring: ( + received: string, + start: number, + length: number, + ) => string; +}; + export type Tester = (a: any, b: any) => boolean | undefined; export type MatcherState = { @@ -84,17 +114,7 @@ export type Expect = { [id: string]: AsymmetricMatcher; not: {[id: string]: AsymmetricMatcher}; matchers: MatchersObject; - print: { - printReceivedStringContainExpectedResult( - received: string, - result: RegExpExecArray | null, - ): string; - printReceivedStringContainExpectedSubstring( - received: string, - start: number, - length: number, - ): string; - }; + print: PrintObject; }; interface Constructable {