diff --git a/CHANGELOG.md b/CHANGELOG.md index f1775c014981..ea2447dc955a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - `[jest-matcher-utils]` Add `BigInt` support to `ensureNumbers` `ensureActualIsNumber`, `ensureExpectedIsNumber` ([#8382](https://github.com/facebook/jest/pull/8382)) - `[jest-runner]` Warn if a worker had to be force exited ([#8206](https://github.com/facebook/jest/pull/8206)) - `[jest-snapshot]` Display change counts in annotation lines ([#8982](https://github.com/facebook/jest/pull/8982)) +- `[jest-snapshot]` [**BREAKING**] Improve report when the matcher has properties ([#9104](https://github.com/facebook/jest/pull/9104)) - `[@jest/test-result]` Create method to create empty `TestResult` ([#8867](https://github.com/facebook/jest/pull/8867)) - `[jest-worker]` [**BREAKING**] Return a promise from `end()`, resolving with the information whether workers exited gracefully ([#8206](https://github.com/facebook/jest/pull/8206)) - `[jest-reporters]` Transform file paths into hyperlinks ([#8980](https://github.com/facebook/jest/pull/8980)) diff --git a/e2e/__tests__/toMatchSnapshot.test.ts b/e2e/__tests__/toMatchSnapshot.test.ts index 59336c948bce..2c5c1dce8bdc 100644 --- a/e2e/__tests__/toMatchSnapshot.test.ts +++ b/e2e/__tests__/toMatchSnapshot.test.ts @@ -255,7 +255,7 @@ test('handles property matchers with hint', () => { expect(stderr).toMatch( 'Snapshot name: `handles property matchers with hint: descriptive hint 1`', ); - expect(stderr).toMatch('Expected properties:'); + expect(stderr).toMatch('Expected properties'); expect(stderr).toMatch('Snapshots: 1 failed, 1 total'); expect(exitCode).toBe(1); } @@ -287,7 +287,7 @@ test('handles property matchers with deep properties', () => { expect(stderr).toMatch( 'Snapshot name: `handles property matchers with deep properties 1`', ); - expect(stderr).toMatch('Expected properties:'); + expect(stderr).toMatch('Expected properties'); expect(stderr).toMatch('Snapshots: 1 failed, 1 total'); expect(exitCode).toBe(1); } diff --git a/packages/jest-snapshot/src/State.ts b/packages/jest-snapshot/src/State.ts index ea0e886d9a0f..af3e659ab54f 100644 --- a/packages/jest-snapshot/src/State.ts +++ b/packages/jest-snapshot/src/State.ts @@ -10,6 +10,7 @@ import {Config} from '@jest/types'; import {getStackTraceLines, getTopFrame} from 'jest-message-util'; import { + addExtraLineBreaks, getSnapshotData, keyToTestName, removeExtraLineBreaks, @@ -198,7 +199,7 @@ export default class SnapshotState { this._uncheckedKeys.delete(key); } - const receivedSerialized = serialize(received); + const receivedSerialized = addExtraLineBreaks(serialize(received)); const expected = isInline ? inlineSnapshot : this._snapshotData[key]; const pass = expected === receivedSerialized; const hasSnapshot = expected !== undefined; diff --git a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap index 9d9621f48717..b32622fee2b7 100644 --- a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap +++ b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap @@ -38,7 +38,7 @@ exports[`matcher error toMatchSnapshot Expected properties must be an object (no Matcher error: Expected properties must be an object Expected properties has type: function -Expected properties has value: [Function properties] +Expected properties has value: [Function] `; exports[`matcher error toMatchSnapshot Expected properties must be an object (null) with hint 1`] = ` @@ -67,6 +67,23 @@ Snapshot state must be initialized Snapshot state has value: undefined `; +exports[`matcher error toMatchSnapshot received value must be an object (non-null) 1`] = ` +expect(received).toMatchSnapshot(properties) + +Matcher error: received value must be an object when the matcher has properties + +Received has type: string +Received has value: "string" +`; + +exports[`matcher error toMatchSnapshot received value must be an object (null) 1`] = ` +expect(received).toMatchSnapshot(properties) + +Matcher error: received value must be an object when the matcher has properties + +Received has value: null +`; + exports[`matcher error toThrowErrorMatchingInlineSnapshot Inline snapshot must be a string 1`] = ` expect(received).toThrowErrorMatchingInlineSnapshot(snapshot) @@ -110,8 +127,15 @@ exports[`pass false toMatchInlineSnapshot with properties equals false with snap Snapshot name: \`with properties 1\` -Expected properties: {"id": "abcdef"} -Received value: {"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"} +- Expected properties - 1 ++ Received value + 3 + + Object { +- "id": "abcdef", ++ "id": "abcdefg", ++ "text": "Increase code coverage", ++ "type": "ADD_ITEM", + } `; exports[`pass false toMatchInlineSnapshot with properties equals false without snapshot 1`] = ` @@ -119,8 +143,15 @@ exports[`pass false toMatchInlineSnapshot with properties equals false without s Snapshot name: \`with properties 1\` -Expected properties: {"id": "abcdef"} -Received value: {"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"} +- Expected properties - 1 ++ Received value + 3 + + Object { +- "id": "abcdef", ++ "id": "abcdefg", ++ "text": "Increase code coverage", ++ "type": "ADD_ITEM", + } `; exports[`pass false toMatchInlineSnapshot with properties equals true 1`] = ` @@ -165,13 +196,29 @@ This is likely because this test is run in a continuous integration (CI) environ Received: "Write me if you can!" `; -exports[`pass false toMatchSnapshot with properties equals false 1`] = ` +exports[`pass false toMatchSnapshot with properties equals false isLineDiffable false 1`] = ` +expect(received).toMatchSnapshot(properties) + +Snapshot name: \`with properties 1\` + +Expected properties: {"name": "Error"} +Received value: [RangeError: Invalid array length] +`; + +exports[`pass false toMatchSnapshot with properties equals false isLineDiffable true 1`] = ` expect(received).toMatchSnapshot(properties) Snapshot name: \`with properties 1\` -Expected properties: {"id": "abcdef"} -Received value: {"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"} +- Expected properties - 1 ++ Received value + 3 + + Object { +- "id": "abcdef", ++ "id": "abcdefg", ++ "text": "Increase code coverage", ++ "type": "ADD_ITEM", + } `; exports[`pass false toMatchSnapshot with properties equals true 1`] = ` @@ -199,17 +246,17 @@ Snapshot: "inline snapshot" Received: "received" `; -exports[`printDiffOrStringified backtick single line expected and received 1`] = ` +exports[`printSnapshotAndReceived backtick single line expected and received 1`] = ` Snapshot: "var foo = \`backtick\`;" Received: "var foo = tag\`backtick\`;" `; -exports[`printDiffOrStringified empty string expected and received single line 1`] = ` +exports[`printSnapshotAndReceived empty string expected and received single line 1`] = ` Snapshot: "" Received: "single line string" `; -exports[`printDiffOrStringified empty string received and expected multi line 1`] = ` +exports[`printSnapshotAndReceived empty string received and expected multi line 1`] = ` - Snapshot - 3 + Received + 0 @@ -218,7 +265,7 @@ exports[`printDiffOrStringified empty string received and expected multi line 1` - string `; -exports[`printDiffOrStringified escape backslash in multi line string 1`] = ` +exports[`printSnapshotAndReceived escape backslash in multi line string 1`] = ` - Snapshot - 1 + Received + 2 @@ -227,22 +274,22 @@ exports[`printDiffOrStringified escape backslash in multi line string 1`] = ` + Back \\ slash `; -exports[`printDiffOrStringified escape backslash in single line string 1`] = ` +exports[`printSnapshotAndReceived escape backslash in single line string 1`] = ` Snapshot: "forward / slash and back \\\\ slash" Received: "Forward / slash and back \\\\ slash" `; -exports[`printDiffOrStringified escape double quote marks in string 1`] = ` +exports[`printSnapshotAndReceived escape double quote marks in string 1`] = ` Snapshot: "What does \\"oobleck\\" mean?" Received: "What does \\"ewbleck\\" mean?" `; -exports[`printDiffOrStringified escape regexp 1`] = ` +exports[`printSnapshotAndReceived escape regexp 1`] = ` Snapshot: /\\\\\\\\\\("\\)/g Received: /\\\\\\\\\\("\\)/ `; -exports[`printDiffOrStringified expand false 1`] = ` +exports[`printSnapshotAndReceived expand false 1`] = ` - Snapshot - 1 + Received + 3 @@ -259,7 +306,7 @@ exports[`printDiffOrStringified expand false 1`] = ` ↵ `; -exports[`printDiffOrStringified expand true 1`] = ` +exports[`printSnapshotAndReceived expand true 1`] = ` - Snapshot - 1 + Received + 3 @@ -286,7 +333,7 @@ exports[`printDiffOrStringified expand true 1`] = ` ↵ `; -exports[`printDiffOrStringified fallback to line diff 1`] = ` +exports[`printSnapshotAndReceived fallback to line diff 1`] = ` - Snapshot - 1 + Received + 8 @@ -306,7 +353,7 @@ exports[`printDiffOrStringified fallback to line diff 1`] = ` + ================================================================================ `; -exports[`printDiffOrStringified has no common after clean up chaff array 1`] = ` +exports[`printSnapshotAndReceived has no common after clean up chaff array 1`] = ` - Snapshot - 2 + Received + 2 @@ -318,44 +365,44 @@ exports[`printDiffOrStringified has no common after clean up chaff array 1`] = ` ] `; -exports[`printDiffOrStringified has no common after clean up chaff string single line 1`] = ` +exports[`printSnapshotAndReceived has no common after clean up chaff string single line 1`] = ` Snapshot: "delete" Received: "insert" `; -exports[`printDiffOrStringified isLineDiffable false asymmetric matcher 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false asymmetric matcher 1`] = ` Snapshot: null Received: Object { "asymmetricMatch": [Function], } `; -exports[`printDiffOrStringified isLineDiffable false boolean 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false boolean 1`] = ` Snapshot: true Received: false `; -exports[`printDiffOrStringified isLineDiffable false date 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false date 1`] = ` Snapshot: 2019-09-19T00:00:00.000Z Received: 2019-09-20T00:00:00.000Z `; -exports[`printDiffOrStringified isLineDiffable false error 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false error 1`] = ` Snapshot: [Error: Cannot spread fragment "NameAndAppearances" within itself.] Received: [Error: Cannot spread fragment "NameAndAppearancesAndFriends" within itself.] `; -exports[`printDiffOrStringified isLineDiffable false function 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false function 1`] = ` Snapshot: undefined Received: [Function] `; -exports[`printDiffOrStringified isLineDiffable false number 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false number 1`] = ` Snapshot: -0 Received: NaN `; -exports[`printDiffOrStringified isLineDiffable true array 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true array 1`] = ` - Snapshot - 0 + Received + 2 @@ -373,7 +420,7 @@ exports[`printDiffOrStringified isLineDiffable true array 1`] = ` ] `; -exports[`printDiffOrStringified isLineDiffable true object 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true object 1`] = ` - Snapshot - 2 + Received + 3 @@ -389,7 +436,7 @@ exports[`printDiffOrStringified isLineDiffable true object 1`] = ` } `; -exports[`printDiffOrStringified isLineDiffable true single line expected and multi line received 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true single line expected and multi line received 1`] = ` - Snapshot - 1 + Received + 3 @@ -399,7 +446,7 @@ exports[`printDiffOrStringified isLineDiffable true single line expected and mul + ] `; -exports[`printDiffOrStringified isLineDiffable true single line expected and received 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true single line expected and received 1`] = ` - Snapshot - 1 + Received + 1 @@ -407,7 +454,7 @@ exports[`printDiffOrStringified isLineDiffable true single line expected and rec + Object {} `; -exports[`printDiffOrStringified multi line small change in one line and other is unchanged 1`] = ` +exports[`printSnapshotAndReceived multi line small change in one line and other is unchanged 1`] = ` - Snapshot - 1 + Received + 1 @@ -416,7 +463,7 @@ exports[`printDiffOrStringified multi line small change in one line and other is Must be one of: 'Home' `; -exports[`printDiffOrStringified multi line small changes 1`] = ` +exports[`printSnapshotAndReceived multi line small changes 1`] = ` - Snapshot - 7 + Received + 7 @@ -437,12 +484,12 @@ exports[`printDiffOrStringified multi line small changes 1`] = ` + at Object.doesNotThrow (__tests__/assertionError.test.js:70:10) `; -exports[`printDiffOrStringified single line large changes 1`] = ` +exports[`printSnapshotAndReceived single line large changes 1`] = ` Snapshot: "Array length must be a finite positive integer" Received: "Invalid array length" `; -exports[`printDiffOrStringified without serialize backtick single line expected and multi line received 1`] = ` +exports[`printSnapshotAndReceived without serialize backtick single line expected and multi line received 1`] = ` - Snapshot - 1 + Received + 2 @@ -451,7 +498,7 @@ exports[`printDiffOrStringified without serialize backtick single line expected + tick\`; `; -exports[`printDiffOrStringified without serialize backtick single line expected and received 1`] = ` +exports[`printSnapshotAndReceived without serialize backtick single line expected and received 1`] = ` - Snapshot - 1 + Received + 1 @@ -459,7 +506,7 @@ exports[`printDiffOrStringified without serialize backtick single line expected + var foo = \`back\${x}tick\`; `; -exports[`printDiffOrStringified without serialize has no common after clean up chaff multi line 1`] = ` +exports[`printSnapshotAndReceived without serialize has no common after clean up chaff multi line 1`] = ` - Snapshot - 2 + Received + 2 @@ -469,7 +516,7 @@ exports[`printDiffOrStringified without serialize has no common after clean up c + 2 `; -exports[`printDiffOrStringified without serialize has no common after clean up chaff single line 1`] = ` +exports[`printSnapshotAndReceived without serialize has no common after clean up chaff single line 1`] = ` - Snapshot - 1 + Received + 1 @@ -477,7 +524,7 @@ exports[`printDiffOrStringified without serialize has no common after clean up c + insert `; -exports[`printDiffOrStringified without serialize prettier/pull/5590 1`] = ` +exports[`printSnapshotAndReceived without serialize prettier/pull/5590 1`] = ` - Snapshot - 1 + Received + 1 diff --git a/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts b/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts index 7f4898a756bd..6df0bab18d65 100644 --- a/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts +++ b/packages/jest-snapshot/src/__tests__/printSnapshot.test.ts @@ -11,8 +11,8 @@ import chalk from 'chalk'; import format = require('pretty-format'); import jestSnapshot = require('../index'); -import {printDiffOrStringified} from '../printSnapshot'; -import {stringify} from '../utils'; +import {printSnapshotAndReceived} from '../printSnapshot'; +import {serialize} from '../utils'; const convertAnsi = (val: string): string => val.replace(ansiRegex(), match => { @@ -168,6 +168,28 @@ describe('matcher error', () => { }).toThrowErrorMatchingSnapshot(); }); + describe('received value must be an object', () => { + const context = { + currentTestName: '', + isNot: false, + promise: '', + snapshotState: {}, + }; + const properties = {}; + + test('(non-null)', () => { + expect(() => { + toMatchSnapshot.call(context, 'string', properties); + }).toThrowErrorMatchingSnapshot(); + }); + + test('(null)', () => { + expect(() => { + toMatchSnapshot.call(context, null, properties); + }).toThrowErrorMatchingSnapshot(); + }); + }); + // Future test: Snapshot hint must be a string test('Snapshot state must be initialized', () => { @@ -433,7 +455,7 @@ describe('pass false', () => { const properties = {id}; const type = 'ADD_ITEM'; - test('equals false', () => { + describe('equals false', () => { const context = { currentTestName: 'with properties', equals: () => false, @@ -447,19 +469,32 @@ describe('pass false', () => { subsetEquality: () => {}, }, }; - const received = { - id: 'abcdefg', - text: 'Increase code coverage', - type, - }; - const {message, pass} = toMatchSnapshot.call( - context, - received, - properties, - ); - expect(pass).toBe(false); - expect(message()).toMatchSnapshot(); + test('isLineDiffable false', () => { + const {message, pass} = toMatchSnapshot.call( + context, + new RangeError('Invalid array length'), + {name: 'Error'}, + ); + expect(pass).toBe(false); + expect(message()).toMatchSnapshot(); + }); + + test('isLineDiffable true', () => { + const received = { + id: 'abcdefg', + text: 'Increase code coverage', + type, + }; + + const {message, pass} = toMatchSnapshot.call( + context, + received, + properties, + ); + expect(pass).toBe(false); + expect(message()).toMatchSnapshot(); + }); }); test('equals true', () => { @@ -564,16 +599,16 @@ describe('pass true', () => { }); }); -describe('printDiffOrStringified', () => { +describe('printSnapshotAndReceived', () => { // Simulate default serialization. const testWithStringify = ( expected: unknown, received: unknown, expand: boolean, ): string => - printDiffOrStringified( - stringify(expected), - stringify(received), + printSnapshotAndReceived( + serialize(expected), + serialize(received), received, expand, ); @@ -583,7 +618,7 @@ describe('printDiffOrStringified', () => { expected: string, received: string, expand: boolean, - ): string => printDiffOrStringified(expected, received, received, expand); + ): string => printSnapshotAndReceived(expected, received, received, expand); describe('backtick', () => { test('single line expected and received', () => { @@ -747,7 +782,7 @@ describe('printDiffOrStringified', () => { test('both are less', () => { const less2 = 'multi\nline'; - const difference = printDiffOrStringified(less2, less, less, true); + const difference = printSnapshotAndReceived(less2, less, less, true); expect(difference).toMatch('- multi'); expect(difference).toMatch('- line'); @@ -756,7 +791,7 @@ describe('printDiffOrStringified', () => { }); test('expected is more', () => { - const difference = printDiffOrStringified(more, less, less, true); + const difference = printSnapshotAndReceived(more, less, less, true); expect(difference).toMatch('- multi line'); expect(difference).toMatch('+ single line'); @@ -764,7 +799,7 @@ describe('printDiffOrStringified', () => { }); test('received is more', () => { - const difference = printDiffOrStringified(less, more, more, true); + const difference = printSnapshotAndReceived(less, more, more, true); expect(difference).toMatch('- single line'); expect(difference).toMatch('+ multi line'); @@ -782,7 +817,7 @@ describe('printDiffOrStringified', () => { test('both are less', () => { const lessQuoted2 = '"0 numbers"'; - const stringified = printDiffOrStringified( + const stringified = printSnapshotAndReceived( lessQuoted2, lessQuoted, less, @@ -795,7 +830,7 @@ describe('printDiffOrStringified', () => { }); test('expected is more', () => { - const stringified = printDiffOrStringified( + const stringified = printSnapshotAndReceived( moreQuoted, lessQuoted, less, @@ -809,7 +844,7 @@ describe('printDiffOrStringified', () => { }); test('received is more', () => { - const stringified = printDiffOrStringified( + const stringified = printSnapshotAndReceived( lessQuoted, moreQuoted, more, diff --git a/packages/jest-snapshot/src/__tests__/utils.test.ts b/packages/jest-snapshot/src/__tests__/utils.test.ts index f3db3a0ac438..f2c12fb2b1d9 100644 --- a/packages/jest-snapshot/src/__tests__/utils.test.ts +++ b/packages/jest-snapshot/src/__tests__/utils.test.ts @@ -191,7 +191,7 @@ test('serialize handles \\r\\n', () => { const data = '
\r\n
'; const serializedData = serialize(data); - expect(serializedData).toBe('\n"
\n
"\n'); + expect(serializedData).toBe('"
\n
"'); }); describe('ExtraLineBreaks', () => { diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index b83d1b1bd93b..33b5c9176429 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -16,8 +16,6 @@ import { RECEIVED_COLOR, matcherErrorMessage, matcherHint, - printExpected, - printReceived, printWithType, stringify, } from 'jest-matcher-utils'; @@ -34,7 +32,10 @@ import { SNAPSHOT_ARG, matcherHintFromConfig, noColor, - printDiffOrStringified, + printExpected, + printPropertiesAndReceived, + printReceived, + printSnapshotAndReceived, } from './printSnapshot'; import {Context, MatchSnapshotConfig} from './types'; import * as utils from './utils'; @@ -254,7 +255,7 @@ const toMatchInlineSnapshot = function( matcherErrorMessage( matcherHint(matcherName, undefined, PROPERTIES_ARG, options), `Inline snapshot must be a string`, - printWithType('Inline snapshot', inlineSnapshot, stringify), + printWithType('Inline snapshot', inlineSnapshot, utils.serialize), ), ); } @@ -301,6 +302,8 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { if (snapshotState == null) { // Because the state is the problem, this is not a matcher error. + // Call generic stringify from jest-matcher-utils package + // because uninitialized snapshot state does not need snapshot serializers. throw new Error( matcherHintFromConfig(config, false) + '\n\n' + @@ -316,6 +319,20 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { : currentTestName || ''; // future BREAKING change: || hint if (typeof properties === 'object') { + if (typeof received !== 'object' || received === null) { + throw new Error( + matcherErrorMessage( + matcherHintFromConfig(config, false), + `${RECEIVED_COLOR( + 'received', + )} value must be an object when the matcher has ${EXPECTED_COLOR( + 'properties', + )}`, + printWithType('Received', received, printReceived), + ), + ); + } + const propertyPass = context.equals(received, properties, [ context.utils.iterableEquality, context.utils.subsetEquality, @@ -331,8 +348,7 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { '\n\n' + printSnapshotName(currentTestName, hint, count) + '\n\n' + - `Expected properties: ${printExpected(properties)}\n` + - `Received value: ${printReceived(received)}`; + printPropertiesAndReceived(properties, received, snapshotState.expand); return { message, @@ -376,7 +392,7 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { '\n\n' + printSnapshotName(currentTestName, hint, count) + '\n\n' + - printDiffOrStringified( + printSnapshotAndReceived( expected, actual, received, @@ -437,7 +453,7 @@ const toThrowErrorMatchingInlineSnapshot = function( matcherErrorMessage( matcherHint(matcherName, undefined, SNAPSHOT_ARG, options), `Inline snapshot must be a string`, - printWithType('Inline snapshot', inlineSnapshot, stringify), + printWithType('Inline snapshot', inlineSnapshot, utils.serialize), ), ); } diff --git a/packages/jest-snapshot/src/printSnapshot.ts b/packages/jest-snapshot/src/printSnapshot.ts index bc7836ed043c..40de416e60a8 100644 --- a/packages/jest-snapshot/src/printSnapshot.ts +++ b/packages/jest-snapshot/src/printSnapshot.ts @@ -28,7 +28,7 @@ import { } from 'jest-matcher-utils'; import prettyFormat = require('pretty-format'); import {MatchSnapshotConfig} from './types'; -import {unstringifyString} from './utils'; +import {deserializeString, minify, serialize} from './utils'; export const noColor = (string: string) => string; @@ -132,9 +132,48 @@ const isLineDiffable = (received: any): boolean => { return true; }; +export const printExpected = (val: unknown) => EXPECTED_COLOR(minify(val)); +export const printReceived = (val: unknown) => RECEIVED_COLOR(minify(val)); + +export const printPropertiesAndReceived = ( + properties: object, + received: object, + expand: boolean, // CLI options: true if `--expand` or false if `--no-expand` +): string => { + const aAnnotation = 'Expected properties'; + const bAnnotation = 'Received value'; + + if (isLineDiffable(properties) && isLineDiffable(received)) { + return diffLinesUnified( + splitLines0(serialize(properties)), + splitLines0(serialize(received)), + { + aAnnotation, + aColor: EXPECTED_COLOR, + bAnnotation, + bColor: RECEIVED_COLOR, + changeLineTrailingSpaceColor: chalk.bgYellow, + commonLineTrailingSpaceColor: chalk.bgYellow, + emptyFirstOrLastLinePlaceholder: '↵', // U+21B5 + expand, + includeChangeCounts: true, + }, + ); + } + + const printLabel = getLabelPrinter(aAnnotation, bAnnotation); + return ( + printLabel(aAnnotation) + + printExpected(properties) + + '\n' + + printLabel(bAnnotation) + + printReceived(received) + ); +}; + const MAX_DIFF_STRING_LENGTH = 20000; -export const printDiffOrStringified = ( +export const printSnapshotAndReceived = ( a: string, // snapshot without extra line breaks b: string, // received serialized but without extra line breaks received: unknown, @@ -193,7 +232,7 @@ export const printDiffOrStringified = ( } // Else either string is multiline, so display as unquoted strings. - a = unstringifyString(a); // hypothetical unserialized expected string + a = deserializeString(a); // hypothetical expected string b = received; // not serialized } // Else expected had custom serialization or was not a string diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts index eb97da5c3edc..af0d059476e1 100644 --- a/packages/jest-snapshot/src/utils.ts +++ b/packages/jest-snapshot/src/utils.ts @@ -136,20 +136,28 @@ export const removeExtraLineBreaks = (string: string): string => ? string.slice(1, -1) : string; -export const serialize = (val: unknown): string => - addExtraLineBreaks(stringify(val)); +const escapeRegex = true; +const printFunctionName = false; -export const stringify = (val: unknown): string => +export const serialize = (val: unknown): string => normalizeNewlines( prettyFormat(val, { - escapeRegex: true, + escapeRegex, plugins: getSerializers(), - printFunctionName: false, + printFunctionName, }), ); +export const minify = (val: unknown): string => + prettyFormat(val, { + escapeRegex, + min: true, + plugins: getSerializers(), + printFunctionName, + }); + // Remove double quote marks and unescape double quotes and backslashes. -export const unstringifyString = (stringified: string): string => +export const deserializeString = (stringified: string): string => stringified.slice(1, -1).replace(/\\("|\\)/g, '$1'); export const escapeBacktickString = (str: string): string =>