From 7ece746b29edfe385fedb24ebe1d2fd9c9ec87e3 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti <pedrottimark@gmail.com> Date: Mon, 4 Nov 2019 14:13:10 -0500 Subject: [PATCH] jest-snapshot: Improve report when the matcher has properties (#9104) * jest-snapshot: Improve report when the matcher has properties * Fix context in new matcher error test * Update CHANGELOG.md * Add test for received null when the matcher has properties --- CHANGELOG.md | 1 + e2e/__tests__/toMatchSnapshot.test.ts | 4 +- packages/jest-snapshot/src/State.ts | 3 +- .../__snapshots__/printSnapshot.test.ts.snap | 123 ++++++++++++------ .../src/__tests__/printSnapshot.test.ts | 87 +++++++++---- .../jest-snapshot/src/__tests__/utils.test.ts | 2 +- packages/jest-snapshot/src/index.ts | 32 +++-- packages/jest-snapshot/src/printSnapshot.ts | 45 ++++++- packages/jest-snapshot/src/utils.ts | 20 ++- 9 files changed, 232 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 975de612d35c..5865b17d2e3c 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 <b>Matcher error</>: Expected <g>properties</> must be an object Expected properties has type: function -Expected properties has value: <g>[Function properties]</> +Expected properties has value: <g>[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`] = ` +<d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</> + +<b>Matcher error</>: <r>received</> value must be an object when the matcher has <g>properties</> + +Received has type: string +Received has value: <r>"string"</> +`; + +exports[`matcher error toMatchSnapshot received value must be an object (null) 1`] = ` +<d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</> + +<b>Matcher error</>: <r>received</> value must be an object when the matcher has <g>properties</> + +Received has value: <r>null</> +`; + exports[`matcher error toThrowErrorMatchingInlineSnapshot Inline snapshot must be a string 1`] = ` <d>expect(</><r>received</><d>).</>toThrowErrorMatchingInlineSnapshot<d>(</>snapshot<d>)</> @@ -110,8 +127,15 @@ exports[`pass false toMatchInlineSnapshot with properties equals false with snap Snapshot name: \`with properties 1\` -Expected properties: <g>{"id": "abcdef"}</> -Received value: <r>{"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"}</> +<g>- Expected properties - 1</> +<r>+ Received value + 3</> + +<d> Object {</> +<g>- "id": "abcdef",</> +<r>+ "id": "abcdefg",</> +<r>+ "text": "Increase code coverage",</> +<r>+ "type": "ADD_ITEM",</> +<d> }</> `; 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: <g>{"id": "abcdef"}</> -Received value: <r>{"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"}</> +<g>- Expected properties - 1</> +<r>+ Received value + 3</> + +<d> Object {</> +<g>- "id": "abcdef",</> +<r>+ "id": "abcdefg",</> +<r>+ "text": "Increase code coverage",</> +<r>+ "type": "ADD_ITEM",</> +<d> }</> `; 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: <r>"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`] = ` +<d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</> + +Snapshot name: \`with properties 1\` + +Expected properties: <g>{"name": "Error"}</> +Received value: <r>[RangeError: Invalid array length]</> +`; + +exports[`pass false toMatchSnapshot with properties equals false isLineDiffable true 1`] = ` <d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</> Snapshot name: \`with properties 1\` -Expected properties: <g>{"id": "abcdef"}</> -Received value: <r>{"id": "abcdefg", "text": "Increase code coverage", "type": "ADD_ITEM"}</> +<g>- Expected properties - 1</> +<r>+ Received value + 3</> + +<d> Object {</> +<g>- "id": "abcdef",</> +<r>+ "id": "abcdefg",</> +<r>+ "text": "Increase code coverage",</> +<r>+ "type": "ADD_ITEM",</> +<d> }</> `; exports[`pass false toMatchSnapshot with properties equals true 1`] = ` @@ -199,17 +246,17 @@ Snapshot: <g>"inline snapshot"</> Received: <r>"received"</> `; -exports[`printDiffOrStringified backtick single line expected and received 1`] = ` +exports[`printSnapshotAndReceived backtick single line expected and received 1`] = ` Snapshot: <g>"var foo = \`backtick\`;"</> Received: <r>"var foo = <i>tag</i>\`backtick\`;"</> `; -exports[`printDiffOrStringified empty string expected and received single line 1`] = ` +exports[`printSnapshotAndReceived empty string expected and received single line 1`] = ` Snapshot: <g>""</> Received: <r>"single line string"</> `; -exports[`printDiffOrStringified empty string received and expected multi line 1`] = ` +exports[`printSnapshotAndReceived empty string received and expected multi line 1`] = ` <g>- Snapshot - 3</> <r>+ Received + 0</> @@ -218,7 +265,7 @@ exports[`printDiffOrStringified empty string received and expected multi line 1` <g>- string</> `; -exports[`printDiffOrStringified escape backslash in multi line string 1`] = ` +exports[`printSnapshotAndReceived escape backslash in multi line string 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 2</> @@ -227,22 +274,22 @@ exports[`printDiffOrStringified escape backslash in multi line string 1`] = ` <r>+ <i>B</i>ack \\ slash</> `; -exports[`printDiffOrStringified escape backslash in single line string 1`] = ` +exports[`printSnapshotAndReceived escape backslash in single line string 1`] = ` Snapshot: <g>"<i>f</i>orward / slash and back \\\\ slash"</> Received: <r>"<i>F</i>orward / slash and back \\\\ slash"</> `; -exports[`printDiffOrStringified escape double quote marks in string 1`] = ` +exports[`printSnapshotAndReceived escape double quote marks in string 1`] = ` Snapshot: <g>"What does \\"<i>oo</i>bleck\\" mean?"</> Received: <r>"What does \\"<i>ew</i>bleck\\" mean?"</> `; -exports[`printDiffOrStringified escape regexp 1`] = ` +exports[`printSnapshotAndReceived escape regexp 1`] = ` Snapshot: <g>/\\\\\\\\\\("\\)/g</> Received: <r>/\\\\\\\\\\("\\)/</> `; -exports[`printDiffOrStringified expand false 1`] = ` +exports[`printSnapshotAndReceived expand false 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 3</> @@ -259,7 +306,7 @@ exports[`printDiffOrStringified expand false 1`] = ` <d> ↵</> `; -exports[`printDiffOrStringified expand true 1`] = ` +exports[`printSnapshotAndReceived expand true 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 3</> @@ -286,7 +333,7 @@ exports[`printDiffOrStringified expand true 1`] = ` <d> ↵</> `; -exports[`printDiffOrStringified fallback to line diff 1`] = ` +exports[`printSnapshotAndReceived fallback to line diff 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 8</> @@ -306,7 +353,7 @@ exports[`printDiffOrStringified fallback to line diff 1`] = ` <r>+ ================================================================================</> `; -exports[`printDiffOrStringified has no common after clean up chaff array 1`] = ` +exports[`printSnapshotAndReceived has no common after clean up chaff array 1`] = ` <g>- Snapshot - 2</> <r>+ Received + 2</> @@ -318,44 +365,44 @@ exports[`printDiffOrStringified has no common after clean up chaff array 1`] = ` <d> ]</> `; -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: <g>"delete"</> Received: <r>"insert"</> `; -exports[`printDiffOrStringified isLineDiffable false asymmetric matcher 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false asymmetric matcher 1`] = ` Snapshot: <g>null</> Received: <r>Object {</> <r> "asymmetricMatch": [Function],</> <r>}</> `; -exports[`printDiffOrStringified isLineDiffable false boolean 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false boolean 1`] = ` Snapshot: <g>true</> Received: <r>false</> `; -exports[`printDiffOrStringified isLineDiffable false date 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false date 1`] = ` Snapshot: <g>2019-09-19T00:00:00.000Z</> Received: <r>2019-09-20T00:00:00.000Z</> `; -exports[`printDiffOrStringified isLineDiffable false error 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false error 1`] = ` Snapshot: <g>[Error: Cannot spread fragment "NameAndAppearances" within itself.]</> Received: <r>[Error: Cannot spread fragment "NameAndAppearancesAndFriends" within itself.]</> `; -exports[`printDiffOrStringified isLineDiffable false function 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false function 1`] = ` Snapshot: <g>undefined</> Received: <r>[Function]</> `; -exports[`printDiffOrStringified isLineDiffable false number 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable false number 1`] = ` Snapshot: <g>-0</> Received: <r>NaN</> `; -exports[`printDiffOrStringified isLineDiffable true array 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true array 1`] = ` <g>- Snapshot - 0</> <r>+ Received + 2</> @@ -373,7 +420,7 @@ exports[`printDiffOrStringified isLineDiffable true array 1`] = ` <d> ]</> `; -exports[`printDiffOrStringified isLineDiffable true object 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true object 1`] = ` <g>- Snapshot - 2</> <r>+ Received + 3</> @@ -389,7 +436,7 @@ exports[`printDiffOrStringified isLineDiffable true object 1`] = ` <d> }</> `; -exports[`printDiffOrStringified isLineDiffable true single line expected and multi line received 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true single line expected and multi line received 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 3</> @@ -399,7 +446,7 @@ exports[`printDiffOrStringified isLineDiffable true single line expected and mul <r>+ ]</> `; -exports[`printDiffOrStringified isLineDiffable true single line expected and received 1`] = ` +exports[`printSnapshotAndReceived isLineDiffable true single line expected and received 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 1</> @@ -407,7 +454,7 @@ exports[`printDiffOrStringified isLineDiffable true single line expected and rec <r>+ 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`] = ` <g>- Snapshot - 1</> <r>+ Received + 1</> @@ -416,7 +463,7 @@ exports[`printDiffOrStringified multi line small change in one line and other is <d> Must be one of: 'Home'</> `; -exports[`printDiffOrStringified multi line small changes 1`] = ` +exports[`printSnapshotAndReceived multi line small changes 1`] = ` <g>- Snapshot - 7</> <r>+ Received + 7</> @@ -437,12 +484,12 @@ exports[`printDiffOrStringified multi line small changes 1`] = ` <r>+ at Object.doesNotThrow (__tests__/assertionError.test.js:7<i>0</i>:10)</> `; -exports[`printDiffOrStringified single line large changes 1`] = ` +exports[`printSnapshotAndReceived single line large changes 1`] = ` Snapshot: <g>"<i>A</i>rray length<i> must be a finite positive integer</i>"</> Received: <r>"<i>Invalid a</i>rray 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`] = ` <g>- Snapshot - 1</> <r>+ Received + 2</> @@ -451,7 +498,7 @@ exports[`printDiffOrStringified without serialize backtick single line expected <r>+ tick\`;</> `; -exports[`printDiffOrStringified without serialize backtick single line expected and received 1`] = ` +exports[`printSnapshotAndReceived without serialize backtick single line expected and received 1`] = ` <g>- Snapshot - 1</> <r>+ Received + 1</> @@ -459,7 +506,7 @@ exports[`printDiffOrStringified without serialize backtick single line expected <r>+ var foo = \`back<i>\${x}</i>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`] = ` <g>- Snapshot - 2</> <r>+ Received + 2</> @@ -469,7 +516,7 @@ exports[`printDiffOrStringified without serialize has no common after clean up c <r>+ 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`] = ` <g>- Snapshot - 1</> <r>+ Received + 1</> @@ -477,7 +524,7 @@ exports[`printDiffOrStringified without serialize has no common after clean up c <r>+ insert</> `; -exports[`printDiffOrStringified without serialize prettier/pull/5590 1`] = ` +exports[`printSnapshotAndReceived without serialize prettier/pull/5590 1`] = ` <g>- Snapshot - 1</> <r>+ 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 = '<div>\r\n</div>'; const serializedData = serialize(data); - expect(serializedData).toBe('\n"<div>\n</div>"\n'); + expect(serializedData).toBe('"<div>\n</div>"'); }); 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 =>