From b9c2e5ed786d3d98ceec7408354c069f06cc73a1 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 6 Dec 2020 17:14:50 +0100 Subject: [PATCH 1/8] docs: Example implementation for async inline snapshot matchers --- docs/ExpectAPI.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 2ee488f23e8b..bb04cd0db5cf 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -255,6 +255,41 @@ it('stores only 10 characters', () => { }); ``` +#### async + +If your custom inline snapshot matcher is async i.e. uses `async`-`await` you might encounter an error like "Multiple inline snapshots for the same call are not supported". Jest needs additional context information to find where the custom inline snapshot matcher was used to update the snapshots properly. + +```js +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...rest) { + // The error (and its stacktrace) must be created before any `await` + this.error = new Error(); + + // The implementation of `observe`d oesn't matter. + // It only matters that the custom snapshot matcher is async. + const observation = await observe(async () => { + await fn(); + }); + + return toMatchInlineSnapshot.call(this, recording, ...rest); + }, +}); + +it('observes something', async () => { + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(); + /* + The snapshot will be added inline like + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(`"async action"`); + */ +}); +``` + ### `expect.anything()` `expect.anything()` matches anything but `null` or `undefined`. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument: From 1867543615bd2d52d4e772693bb186f004db48a7 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 6 Dec 2020 18:33:55 +0100 Subject: [PATCH 2/8] Add e2e test for documented implementation --- .../customInlineSnapshotMatchers.test.ts.snap | 93 +++++++++++++++++++ .../customInlineSnapshotMatchers.test.ts | 26 ++++++ .../__tests__/asynchronous.test.js | 30 ++++++ .../package.json | 5 + 4 files changed, 154 insertions(+) create mode 100644 e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap create mode 100644 e2e/__tests__/customInlineSnapshotMatchers.test.ts create mode 100644 e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js create mode 100644 e2e/custom-inline-snapshot-matchers/package.json diff --git a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap new file mode 100644 index 000000000000..004a378ed4cf --- /dev/null +++ b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`works with custom inline snapshot matchers 1`] = ` +FAIL __tests__/asynchronous.test.js + ✕ new async, inline snapshots + ✕ mismatching async, inline snapshots + + ● new async, inline snapshots + + expect(received).toMatchInlineSnapshot() + + Snapshot name: \`new async, inline snapshots 1\` + + New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. + + This is likely because this test is run in a continuous integration (CI) environment in which snapshots are not written by default. + + Received: "result #1" + + 17 | + 18 | test('new async, inline snapshots', async () => { + > 19 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + | ^ + 20 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + 21 | }); + 22 | + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:19:41) + + ● new async, inline snapshots + + expect(received).toMatchInlineSnapshot() + + Snapshot name: \`new async, inline snapshots 2\` + + New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. + + This is likely because this test is run in a continuous integration (CI) environment in which snapshots are not written by default. + + Received: "result #2" + + 18 | test('new async, inline snapshots', async () => { + 19 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + > 20 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + | ^ + 21 | }); + 22 | + 23 | test('mismatching async, inline snapshots', async () => { + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:20:41) + + ● mismatching async, inline snapshots + + expect(received).toMatchInlineSnapshot(snapshot) + + Snapshot name: \`mismatching async, inline snapshots 1\` + + Snapshot: "result #?" + Received: "result #1" + + 22 | + 23 | test('mismatching async, inline snapshots', async () => { + > 24 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + | ^ + 25 | \`"result #?"\`, + 26 | ); + 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:24:41) + + ● mismatching async, inline snapshots + + expect(received).toMatchInlineSnapshot(snapshot) + + Snapshot name: \`mismatching async, inline snapshots 2\` + + Snapshot: "result #?" + Received: "result #1" + + 25 | \`"result #?"\`, + 26 | ); + > 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + | ^ + 28 | \`"result #?"\`, + 29 | ); + 30 | }); + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:27:41) + + › 4 snapshots failed. +Snapshot Summary + › 4 snapshots failed from 1 test suite. Inspect your code changes or re-run jest with \`-u\` to update them. +`; diff --git a/e2e/__tests__/customInlineSnapshotMatchers.test.ts b/e2e/__tests__/customInlineSnapshotMatchers.test.ts new file mode 100644 index 000000000000..9b4178666920 --- /dev/null +++ b/e2e/__tests__/customInlineSnapshotMatchers.test.ts @@ -0,0 +1,26 @@ +/** + * 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. + */ + +import {wrap} from 'jest-snapshot-serializer-raw'; +import {extractSummary} from '../Utils'; +import runJest from '../runJest'; + +test('works with custom inline snapshot matchers', () => { + const {stderr} = runJest('custom-inline-snapshot-matchers', [ + '--ci', + 'asynchronous.test.js', + ]); + + let {rest} = extractSummary(stderr); + + rest = rest + .split('\n') + .filter(line => line.indexOf('at Error (native)') < 0) + .join('\n'); + + expect(wrap(rest)).toMatchSnapshot(); +}); diff --git a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js new file mode 100644 index 000000000000..a195e3f49780 --- /dev/null +++ b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js @@ -0,0 +1,30 @@ +/** + * 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. + */ +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...args) { + this.error = new Error(); + const result = await fn(); + + return toMatchInlineSnapshot.call(this, result, ...args); + }, +}); + +test('new async, inline snapshots', async () => { + await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); +}); + +test('mismatching async, inline snapshots', async () => { + await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + `"result #?"`, + ); + await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + `"result #?"`, + ); +}); diff --git a/e2e/custom-inline-snapshot-matchers/package.json b/e2e/custom-inline-snapshot-matchers/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/custom-inline-snapshot-matchers/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} From dd477661d190a6388e9ddafe3a29204d97f347e7 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 6 Dec 2020 18:37:25 +0100 Subject: [PATCH 3/8] Fix typo --- docs/ExpectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index bb04cd0db5cf..d1ce4d6dbf84 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -267,7 +267,7 @@ expect.extend({ // The error (and its stacktrace) must be created before any `await` this.error = new Error(); - // The implementation of `observe`d oesn't matter. + // The implementation of `observe` doesn't matter. // It only matters that the custom snapshot matcher is async. const observation = await observe(async () => { await fn(); From f69e88127ef1625f8841e81c5cff7411489764ea Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 6 Dec 2020 18:39:30 +0100 Subject: [PATCH 4/8] Explain example --- .../__tests__/asynchronous.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js index a195e3f49780..1d148d769950 100644 --- a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js +++ b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js @@ -9,6 +9,9 @@ const {toMatchInlineSnapshot} = require('jest-snapshot'); expect.extend({ async toMatchObservationInlineSnapshot(fn, ...args) { this.error = new Error(); + // This specific behavior can be implemented without a custom matcher. + // In a real example one might want to observe some global value that `fn()` is affecting. + // The difference between before and after `fn()` might then be persisted as a snapshot. const result = await fn(); return toMatchInlineSnapshot.call(this, result, ...args); From a1ad983a29f184d8280ae4ddd89e792f934853e2 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 6 Dec 2020 18:59:37 +0100 Subject: [PATCH 5/8] Actual snapshot --- .../customInlineSnapshotMatchers.test.ts.snap | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap index 004a378ed4cf..90b2bceecbdb 100644 --- a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap +++ b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap @@ -17,15 +17,15 @@ FAIL __tests__/asynchronous.test.js Received: "result #1" - 17 | - 18 | test('new async, inline snapshots', async () => { - > 19 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + 20 | + 21 | test('new async, inline snapshots', async () => { + > 22 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); | ^ - 20 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); - 21 | }); - 22 | + 23 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + 24 | }); + 25 | - at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:19:41) + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:22:41) ● new async, inline snapshots @@ -39,15 +39,15 @@ FAIL __tests__/asynchronous.test.js Received: "result #2" - 18 | test('new async, inline snapshots', async () => { - 19 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); - > 20 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + 21 | test('new async, inline snapshots', async () => { + 22 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + > 23 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); | ^ - 21 | }); - 22 | - 23 | test('mismatching async, inline snapshots', async () => { + 24 | }); + 25 | + 26 | test('mismatching async, inline snapshots', async () => { - at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:20:41) + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:23:41) ● mismatching async, inline snapshots @@ -58,15 +58,15 @@ FAIL __tests__/asynchronous.test.js Snapshot: "result #?" Received: "result #1" - 22 | - 23 | test('mismatching async, inline snapshots', async () => { - > 24 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + 25 | + 26 | test('mismatching async, inline snapshots', async () => { + > 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( | ^ - 25 | \`"result #?"\`, - 26 | ); - 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + 28 | \`"result #?"\`, + 29 | ); + 30 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( - at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:24:41) + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:27:41) ● mismatching async, inline snapshots @@ -77,15 +77,15 @@ FAIL __tests__/asynchronous.test.js Snapshot: "result #?" Received: "result #1" - 25 | \`"result #?"\`, - 26 | ); - > 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( - | ^ 28 | \`"result #?"\`, 29 | ); - 30 | }); + > 30 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + | ^ + 31 | \`"result #?"\`, + 32 | ); + 33 | }); - at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:27:41) + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:30:41) › 4 snapshots failed. Snapshot Summary From de035f6e2a84a222f0089b68a105df3e70accebf Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 7 Dec 2020 15:08:28 +0100 Subject: [PATCH 6/8] Fix typo in result number --- .../__snapshots__/customInlineSnapshotMatchers.test.ts.snap | 6 +++--- .../__tests__/asynchronous.test.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap index 90b2bceecbdb..eebb3e9ff410 100644 --- a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap +++ b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap @@ -64,7 +64,7 @@ FAIL __tests__/asynchronous.test.js | ^ 28 | \`"result #?"\`, 29 | ); - 30 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + 30 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot( at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:27:41) @@ -75,11 +75,11 @@ FAIL __tests__/asynchronous.test.js Snapshot name: \`mismatching async, inline snapshots 2\` Snapshot: "result #?" - Received: "result #1" + Received: "result #2" 28 | \`"result #?"\`, 29 | ); - > 30 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + > 30 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot( | ^ 31 | \`"result #?"\`, 32 | ); diff --git a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js index 1d148d769950..afb94f944e42 100644 --- a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js +++ b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js @@ -27,7 +27,7 @@ test('mismatching async, inline snapshots', async () => { await expect(async () => 'result #1').toMatchObservationInlineSnapshot( `"result #?"`, ); - await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + await expect(async () => 'result #2').toMatchObservationInlineSnapshot( `"result #?"`, ); }); From 22fbbd771297d2bccb23e36d6294e491c633b6d0 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 7 Dec 2020 15:10:07 +0100 Subject: [PATCH 7/8] Explain ci flag --- e2e/__tests__/customInlineSnapshotMatchers.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/__tests__/customInlineSnapshotMatchers.test.ts b/e2e/__tests__/customInlineSnapshotMatchers.test.ts index 9b4178666920..a644c74ab270 100644 --- a/e2e/__tests__/customInlineSnapshotMatchers.test.ts +++ b/e2e/__tests__/customInlineSnapshotMatchers.test.ts @@ -11,6 +11,7 @@ import runJest from '../runJest'; test('works with custom inline snapshot matchers', () => { const {stderr} = runJest('custom-inline-snapshot-matchers', [ + // Prevent adding new snapshots or rather changing the test. '--ci', 'asynchronous.test.js', ]); From ce36eacd68651d333f413afa48febaae7c3321f7 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 7 Dec 2020 15:17:31 +0100 Subject: [PATCH 8/8] Backport docs to jest 25 --- .../versioned_docs/version-25.x/ExpectAPI.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/website/versioned_docs/version-25.x/ExpectAPI.md b/website/versioned_docs/version-25.x/ExpectAPI.md index c3e8ee6a6284..d20578d74b6f 100644 --- a/website/versioned_docs/version-25.x/ExpectAPI.md +++ b/website/versioned_docs/version-25.x/ExpectAPI.md @@ -256,6 +256,41 @@ it('stores only 10 characters', () => { }); ``` +#### async + +If your custom inline snapshot matcher is async i.e. uses `async`-`await` you might encounter an error like "Multiple inline snapshots for the same call are not supported". Jest needs additional context information to find where the custom inline snapshot matcher was used to update the snapshots properly. + +```js +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...rest) { + // The error (and its stacktrace) must be created before any `await` + this.error = new Error(); + + // The implementation of `observe` doesn't matter. + // It only matters that the custom snapshot matcher is async. + const observation = await observe(async () => { + await fn(); + }); + + return toMatchInlineSnapshot.call(this, recording, ...rest); + }, +}); + +it('observes something', async () => { + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(); + /* + The snapshot will be added inline like + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(`"async action"`); + */ +}); +``` + ### `expect.anything()` `expect.anything()` matches anything but `null` or `undefined`. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument: