From d85472365eb45d6073625965c390ba3445a18935 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 7 Feb 2022 10:47:15 +1300 Subject: [PATCH] feat: create `prefer-snapshot-hint` rule (#1012) * feat: create `prefer-snapshot-hint` rule * feat(prefer-snapshot-hint): check nested scope for multiple snapshot matchers * fix: update import * test: update number --- README.md | 1 + docs/rules/prefer-snapshot-hint.md | 188 +++++ .../__snapshots__/rules.test.ts.snap | 1 + src/__tests__/rules.test.ts | 2 +- .../__tests__/prefer-snapshot-hint.test.ts | 726 ++++++++++++++++++ src/rules/prefer-snapshot-hint.ts | 103 +++ 6 files changed, 1020 insertions(+), 1 deletion(-) create mode 100644 docs/rules/prefer-snapshot-hint.md create mode 100644 src/rules/__tests__/prefer-snapshot-hint.test.ts create mode 100644 src/rules/prefer-snapshot-hint.ts diff --git a/README.md b/README.md index 9974a2bd6..44abff693 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ installations requiring long-term consistency. | [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | ![fixable][] | | [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | [prefer-lowercase-title](docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | ![fixable][] | +| [prefer-snapshot-hint](docs/rules/prefer-snapshot-hint.md) | Prefer including a hint with external snapshots | | | | [prefer-spy-on](docs/rules/prefer-spy-on.md) | Suggest using `jest.spyOn()` | | ![fixable][] | | [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | ![suggest][] | | [prefer-to-be](docs/rules/prefer-to-be.md) | Suggest using `toBe()` for primitive literals | ![style][] | ![fixable][] | diff --git a/docs/rules/prefer-snapshot-hint.md b/docs/rules/prefer-snapshot-hint.md new file mode 100644 index 000000000..4c4dbc515 --- /dev/null +++ b/docs/rules/prefer-snapshot-hint.md @@ -0,0 +1,188 @@ +# Prefer including a hint with external snapshots (`prefer-snapshot-hint`) + +When working with external snapshot matchers it's considered best practice to +provide a hint (as the last argument to the matcher) describing the expected +snapshot content that will be included in the snapshots name by Jest. + +This makes it easier for reviewers to verify the snapshots during review, and +for anyone to know whether an outdated snapshot is the correct behavior before +updating. + +## Rule details + +This rule looks for any use of an external snapshot matcher (e.g. +`toMatchSnapshot` and `toThrowErrorMatchingSnapshot`) and checks if they include +a snapshot hint. + +## Options + +### `'always'` + +Require a hint to _always_ be provided when using external snapshot matchers. + +Examples of **incorrect** code for the `'always'` option: + +```js +const snapshotOutput = ({ stdout, stderr }) => { + expect(stdout).toMatchSnapshot(); + expect(stderr).toMatchSnapshot(); +}; + +describe('cli', () => { + describe('--version flag', () => { + it('prints the version', async () => { + snapshotOutput(await runCli(['--version'])); + }); + }); + + describe('--config flag', () => { + it('reads the config', async () => { + const { stdout, parsedConfig } = await runCli([ + '--config', + 'jest.config.js', + ]); + + expect(stdout).toMatchSnapshot(); + expect(parsedConfig).toMatchSnapshot(); + }); + + it('prints nothing to stderr', async () => { + const { stderr } = await runCli(['--config', 'jest.config.js']); + + expect(stderr).toMatchSnapshot(); + }); + + describe('when the file does not exist', () => { + it('throws an error', async () => { + await expect( + runCli(['--config', 'does-not-exist.js']), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + }); +}); +``` + +Examples of **correct** code for the `'always'` option: + +```js +const snapshotOutput = ({ stdout, stderr }, hints) => { + expect(stdout).toMatchSnapshot({}, `stdout: ${hints.stdout}`); + expect(stderr).toMatchSnapshot({}, `stderr: ${hints.stderr}`); +}; + +describe('cli', () => { + describe('--version flag', () => { + it('prints the version', async () => { + snapshotOutput(await runCli(['--version']), { + stdout: 'version string', + stderr: 'empty', + }); + }); + }); + + describe('--config flag', () => { + it('reads the config', async () => { + const { stdout } = await runCli(['--config', 'jest.config.js']); + + expect(stdout).toMatchSnapshot({}, 'stdout: config settings'); + }); + + it('prints nothing to stderr', async () => { + const { stderr } = await runCli(['--config', 'jest.config.js']); + + expect(stderr).toMatchInlineSnapshot(); + }); + + describe('when the file does not exist', () => { + it('throws an error', async () => { + await expect( + runCli(['--config', 'does-not-exist.js']), + ).rejects.toThrowErrorMatchingSnapshot('stderr: config error'); + }); + }); + }); +}); +``` + +### `'multi'` (default) + +Require a hint to be provided when there are multiple external snapshot matchers +within the scope (meaning it includes nested calls). + +Examples of **incorrect** code for the `'multi'` option: + +```js +const snapshotOutput = ({ stdout, stderr }) => { + expect(stdout).toMatchSnapshot(); + expect(stderr).toMatchSnapshot(); +}; + +describe('cli', () => { + describe('--version flag', () => { + it('prints the version', async () => { + snapshotOutput(await runCli(['--version'])); + }); + }); + + describe('--config flag', () => { + it('reads the config', async () => { + const { stdout, parsedConfig } = await runCli([ + '--config', + 'jest.config.js', + ]); + + expect(stdout).toMatchSnapshot(); + expect(parsedConfig).toMatchSnapshot(); + }); + + it('prints nothing to stderr', async () => { + const { stderr } = await runCli(['--config', 'jest.config.js']); + + expect(stderr).toMatchSnapshot(); + }); + }); +}); +``` + +Examples of **correct** code for the `'multi'` option: + +```js +const snapshotOutput = ({ stdout, stderr }, hints) => { + expect(stdout).toMatchSnapshot({}, `stdout: ${hints.stdout}`); + expect(stderr).toMatchSnapshot({}, `stderr: ${hints.stderr}`); +}; + +describe('cli', () => { + describe('--version flag', () => { + it('prints the version', async () => { + snapshotOutput(await runCli(['--version']), { + stdout: 'version string', + stderr: 'empty', + }); + }); + }); + + describe('--config flag', () => { + it('reads the config', async () => { + const { stdout } = await runCli(['--config', 'jest.config.js']); + + expect(stdout).toMatchSnapshot(); + }); + + it('prints nothing to stderr', async () => { + const { stderr } = await runCli(['--config', 'jest.config.js']); + + expect(stderr).toMatchInlineSnapshot(); + }); + + describe('when the file does not exist', () => { + it('throws an error', async () => { + await expect( + runCli(['--config', 'does-not-exist.js']), + ).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + }); +}); +``` diff --git a/src/__tests__/__snapshots__/rules.test.ts.snap b/src/__tests__/__snapshots__/rules.test.ts.snap index 7b70d2fe2..830570ce8 100644 --- a/src/__tests__/__snapshots__/rules.test.ts.snap +++ b/src/__tests__/__snapshots__/rules.test.ts.snap @@ -41,6 +41,7 @@ Object { "jest/prefer-expect-resolves": "error", "jest/prefer-hooks-on-top": "error", "jest/prefer-lowercase-title": "error", + "jest/prefer-snapshot-hint": "error", "jest/prefer-spy-on": "error", "jest/prefer-strict-equal": "error", "jest/prefer-to-be": "error", diff --git a/src/__tests__/rules.test.ts b/src/__tests__/rules.test.ts index 6703e7a13..88f10b1a1 100644 --- a/src/__tests__/rules.test.ts +++ b/src/__tests__/rules.test.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import { resolve } from 'path'; import plugin from '../'; -const numberOfRules = 46; +const numberOfRules = 47; const ruleNames = Object.keys(plugin.rules); const deprecatedRules = Object.entries(plugin.rules) .filter(([, rule]) => rule.meta.deprecated) diff --git a/src/rules/__tests__/prefer-snapshot-hint.test.ts b/src/rules/__tests__/prefer-snapshot-hint.test.ts new file mode 100644 index 000000000..cd11661e5 --- /dev/null +++ b/src/rules/__tests__/prefer-snapshot-hint.test.ts @@ -0,0 +1,726 @@ +import { TSESLint } from '@typescript-eslint/utils'; +import dedent from 'dedent'; +import rule from '../prefer-snapshot-hint'; +import { espreeParser } from './test-utils'; + +const ruleTester = new TSESLint.RuleTester({ + parser: espreeParser, + parserOptions: { + ecmaVersion: 2015, + }, +}); + +ruleTester.run('prefer-snapshot-hint (always)', rule, { + valid: [ + { + code: 'expect(something).toStrictEqual(somethingElse);', + options: ['always'], + }, + { + code: "a().toEqual('b')", + options: ['always'], + }, + { + code: 'expect(a);', + options: ['always'], + }, + { + code: 'expect(1).toMatchSnapshot({}, "my snapshot");', + options: ['always'], + }, + { + code: 'expect(1).toThrowErrorMatchingSnapshot("my snapshot");', + options: ['always'], + }, + { + code: 'expect(1).toMatchInlineSnapshot();', + options: ['always'], + }, + { + code: 'expect(1).toThrowErrorMatchingInlineSnapshot();', + options: ['always'], + }, + ], + invalid: [ + { + code: 'expect(1).toMatchSnapshot();', + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 11, + line: 1, + }, + ], + }, + { + code: 'expect(1).toMatchSnapshot({});', + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 11, + line: 1, + }, + ], + }, + { + code: 'expect(1).toThrowErrorMatchingSnapshot();', + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 11, + line: 1, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + }); + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toThrowErrorMatchingSnapshot("my error"); + }); + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + ], + }, + { + code: dedent` + const expectSnapshot = value => { + expect(value).toMatchSnapshot(); + }; + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 17, + line: 2, + }, + ], + }, + { + code: dedent` + const expectSnapshot = value => { + expect(value).toThrowErrorMatchingSnapshot(); + }; + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 17, + line: 2, + }, + ], + }, + { + code: dedent` + it('is true', () => { + { expect(1).toMatchSnapshot(); } + }); + `, + options: ['always'], + errors: [ + { + messageId: 'missingHint', + column: 15, + line: 2, + }, + ], + }, + ], +}); + +ruleTester.run('prefer-snapshot-hint (multi)', rule, { + valid: [ + { + code: 'expect(something).toStrictEqual(somethingElse);', + options: ['multi'], + }, + { + code: "a().toEqual('b')", + options: ['multi'], + }, + { + code: 'expect(a);', + options: ['multi'], + }, + { + code: 'expect(1).toMatchSnapshot({}, "my snapshot");', + options: ['multi'], + }, + { + code: 'expect(1).toThrowErrorMatchingSnapshot("my snapshot");', + options: ['multi'], + }, + { + code: 'expect(1).toMatchSnapshot({});', + options: ['multi'], + }, + { + code: 'expect(1).toThrowErrorMatchingSnapshot();', + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(undefined, 'my first snapshot'); + }); + `, + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toMatchSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(2).toThrowErrorMatchingSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(1).toMatchSnapshot(); + }); + + it('is false', () => { + expect(1).toStrictEqual(1); + expect(1).toStrictEqual(2); + expect(2).toThrowErrorMatchingSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchInlineSnapshot(); + }); + + it('is false', () => { + expect(1).toMatchInlineSnapshot(); + expect(1).toMatchInlineSnapshot(); + expect(1).toThrowErrorMatchingInlineSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + }; + + expect(value).toBe(1); + }; + + it('my test', () => { + expect(1).toMatchSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(value).toBe(1); + }; + + expect(value).toBe(1); + expect(anotherValue).toMatchSnapshot(); + }; + + it('my test', () => { + expect(1).toMatchSnapshot(); + }); + `, + options: ['multi'], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + }; + + expect(value).toBe(1); + }; + + expect(1).toMatchSnapshot(); + `, + options: ['multi'], + }, + ], + invalid: [ + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toThrowErrorMatchingSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toThrowErrorMatchingSnapshot(); + expect(2).toMatchSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot({}); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot({}); + { + expect(2).toMatchSnapshot({}); + } + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 15, + line: 4, + }, + ], + }, + { + code: dedent` + it('is true', () => { + { expect(1).toMatchSnapshot(); } + { expect(2).toMatchSnapshot(); } + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 15, + line: 2, + }, + { + messageId: 'missingHint', + column: 15, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot(); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot({}); + expect(2).toMatchSnapshot(undefined, 'my second snapshot'); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(1).toMatchSnapshot({}, 'my first snapshot'); + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + { + messageId: 'missingHint', + column: 13, + line: 4, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot({}, 'my second snapshot'); + expect(2).toMatchSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 4, + }, + ], + }, + { + code: dedent` + it('is true', () => { + expect(2).toMatchSnapshot(undefined); + expect(2).toMatchSnapshot(); + expect(1).toMatchSnapshot(null, 'my third snapshot'); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 13, + line: 2, + }, + { + messageId: 'missingHint', + column: 13, + line: 3, + }, + ], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + expect(value).toMatchSnapshot(); + + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + }; + + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toThrowErrorMatchingSnapshot(snapshotHint); + }; + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 17, + line: 2, + }, + { + messageId: 'missingHint', + column: 26, + line: 5, + }, + { + messageId: 'missingHint', + column: 21, + line: 9, + }, + ], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + expect(value).toMatchSnapshot(); + + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toMatchSnapshot(null, snapshotHint); + }; + }; + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 17, + line: 2, + }, + { + messageId: 'missingHint', + column: 26, + line: 5, + }, + { + messageId: 'missingHint', + column: 23, + line: 8, + }, + ], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + expect(value + 1).toMatchSnapshot(null); + expect(value + 2).toMatchSnapshot(null, snapshotHint); + }; + + expect(value).toThrowErrorMatchingSnapshot(); + }; + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 26, + line: 3, + }, + { + messageId: 'missingHint', + column: 23, + line: 6, + }, + { + messageId: 'missingHint', + column: 17, + line: 10, + }, + ], + }, + { + code: dedent` + const myReusableTestBody = (value, snapshotHint) => { + const innerFn = anotherValue => { + expect(anotherValue).toMatchSnapshot(); + + expect(value).toBe(1); + }; + + expect(value).toMatchSnapshot(); + }; + + it('my test', () => { + expect(1).toMatchSnapshot(); + }); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 26, + line: 3, + }, + { + messageId: 'missingHint', + column: 17, + line: 8, + }, + ], + }, + { + code: dedent` + const myReusableTestBody = value => { + expect(value).toMatchSnapshot(); + }; + + expect(1).toMatchSnapshot(); + expect(1).toThrowErrorMatchingSnapshot(); + `, + options: ['multi'], + errors: [ + { + messageId: 'missingHint', + column: 11, + line: 5, + }, + { + messageId: 'missingHint', + column: 11, + line: 6, + }, + ], + }, + ], +}); diff --git a/src/rules/prefer-snapshot-hint.ts b/src/rules/prefer-snapshot-hint.ts new file mode 100644 index 000000000..98f82d3a2 --- /dev/null +++ b/src/rules/prefer-snapshot-hint.ts @@ -0,0 +1,103 @@ +import { + ParsedExpectMatcher, + createRule, + isExpectCall, + parseExpectCall, +} from './utils'; + +const snapshotMatchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot']; + +const isSnapshotMatcher = (matcher: ParsedExpectMatcher) => { + return snapshotMatchers.includes(matcher.name); +}; + +const isSnapshotMatcherWithoutHint = (matcher: ParsedExpectMatcher) => { + const expectedNumberOfArgumentsWithHint = + 1 + Number(matcher.name === 'toMatchSnapshot'); + + return matcher.arguments?.length !== expectedNumberOfArgumentsWithHint; +}; + +const messages = { + missingHint: 'You should provide a hint for this snapshot', +}; + +export default createRule<[('always' | 'multi')?], keyof typeof messages>({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Prefer including a hint with external snapshots', + recommended: false, + }, + messages, + type: 'suggestion', + schema: [ + { + type: 'string', + enum: ['always', 'multi'], + }, + ], + }, + defaultOptions: ['multi'], + create(context, [mode]) { + const snapshotMatchers: ParsedExpectMatcher[] = []; + let expressionDepth = 0; + + const reportSnapshotMatchersWithoutHints = () => { + for (const snapshotMatcher of snapshotMatchers) { + if (isSnapshotMatcherWithoutHint(snapshotMatcher)) { + context.report({ + messageId: 'missingHint', + node: snapshotMatcher.node.property, + }); + } + } + }; + + const enterExpression = () => { + expressionDepth++; + }; + + const exitExpression = () => { + expressionDepth--; + + if (mode === 'always') { + reportSnapshotMatchersWithoutHints(); + snapshotMatchers.length = 0; + } + + if (mode === 'multi' && expressionDepth === 0) { + if (snapshotMatchers.length > 1) { + reportSnapshotMatchersWithoutHints(); + } + + snapshotMatchers.length = 0; + } + }; + + return { + 'Program:exit'() { + enterExpression(); + exitExpression(); + }, + FunctionExpression: enterExpression, + 'FunctionExpression:exit': exitExpression, + ArrowFunctionExpression: enterExpression, + 'ArrowFunctionExpression:exit': exitExpression, + CallExpression(node) { + if (!isExpectCall(node)) { + return; + } + + const { matcher } = parseExpectCall(node); + + if (!matcher || !isSnapshotMatcher(matcher)) { + return; + } + + snapshotMatchers.push(matcher); + }, + }; + }, +});