From e7aaf9c7a0926782b40c3ff58f81a65426678b7f Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 10 Sep 2022 12:49:47 +0300 Subject: [PATCH 1/5] fix missing exports --- packages/expect/src/index.ts | 1 + packages/jest-snapshot/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index 3172ec5de0f5..202c02d5c185 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -56,6 +56,7 @@ export type { AsymmetricMatchers, BaseExpect, Expect, + ExpectationResult, MatcherContext, MatcherFunction, MatcherFunctionWithContext, diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index adf570b82476..5aff73e5916d 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -42,7 +42,7 @@ export { } from './SnapshotResolver'; export type {SnapshotResolver} from './SnapshotResolver'; export {default as SnapshotState} from './State'; -export type {SnapshotMatchers} from './types'; +export type {Context, SnapshotMatchers} from './types'; const DID_NOT_THROW = 'Received function did not throw'; // same as toThrow const NOT_SNAPSHOT_MATCHERS = `Snapshot matchers cannot be used with ${BOLD_WEIGHT( From 9d3ab0906057f2d1614dd4daedb90ac9ae6f8d49 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 10 Sep 2022 12:51:03 +0300 Subject: [PATCH 2/5] fix types of matchers --- ...pshot.test.ts => SnapshotResolver.test.ts} | 0 .../__typetests__/matchers.test.ts | 147 ++++++++++++ packages/jest-snapshot/src/index.ts | 221 +++++++++--------- 3 files changed, 258 insertions(+), 110 deletions(-) rename packages/jest-snapshot/__typetests__/{jest-snapshot.test.ts => SnapshotResolver.test.ts} (100%) create mode 100644 packages/jest-snapshot/__typetests__/matchers.test.ts diff --git a/packages/jest-snapshot/__typetests__/jest-snapshot.test.ts b/packages/jest-snapshot/__typetests__/SnapshotResolver.test.ts similarity index 100% rename from packages/jest-snapshot/__typetests__/jest-snapshot.test.ts rename to packages/jest-snapshot/__typetests__/SnapshotResolver.test.ts diff --git a/packages/jest-snapshot/__typetests__/matchers.test.ts b/packages/jest-snapshot/__typetests__/matchers.test.ts new file mode 100644 index 000000000000..6a3fdf264256 --- /dev/null +++ b/packages/jest-snapshot/__typetests__/matchers.test.ts @@ -0,0 +1,147 @@ +/** + * 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 {expectError, expectType} from 'tsd-lite'; +import type {ExpectationResult} from 'expect'; +import { + type Context, + type SnapshotState, + toMatchInlineSnapshot, + toMatchSnapshot, + toThrowErrorMatchingInlineSnapshot, + toThrowErrorMatchingSnapshot, +} from 'jest-snapshot'; + +// Context + +expectType(({} as Context).snapshotState); + +// toMatchSnapshot + +expectType( + toMatchSnapshot.call({} as Context, {received: 'value'}), +); + +expectType( + toMatchSnapshot.call({} as Context, {received: 'value'}, 'someHint'), +); + +expectType( + toMatchSnapshot.call({} as Context, {received: 'value'}, {property: 'match'}), +); + +expectType( + toMatchSnapshot.call( + {} as Context, + {received: 'value'}, + {property: 'match'}, + 'someHint', + ), +); + +expectError(toMatchSnapshot({received: 'value'})); + +// toMatchInlineSnapshot + +expectType( + toMatchInlineSnapshot.call({} as Context, {received: 'value'}), +); + +expectType( + toMatchInlineSnapshot.call( + {} as Context, + {received: 'value'}, + 'inlineSnapshot', + ), +); + +expectType( + toMatchInlineSnapshot.call( + {} as Context, + {received: 'value'}, + {property: 'match'}, + ), +); + +expectType( + toMatchInlineSnapshot.call( + {} as Context, + {received: 'value'}, + {property: 'match'}, + 'inlineSnapshot', + ), +); + +expectError(toMatchInlineSnapshot({received: 'value'})); + +// toThrowErrorMatchingSnapshot + +expectType( + toThrowErrorMatchingSnapshot.call({} as Context, new Error('received')), +); + +expectType( + toThrowErrorMatchingSnapshot.call( + {} as Context, + new Error('received'), + 'someHint', + ), +); + +expectType( + toThrowErrorMatchingSnapshot.call( + {} as Context, + new Error('received'), + 'someHint', + true, // fromPromise + ), +); + +expectType( + toThrowErrorMatchingSnapshot.call( + {} as Context, + new Error('received'), + undefined, + false, // fromPromise + ), +); + +expectError(toThrowErrorMatchingSnapshot({received: 'value'})); + +// toThrowErrorMatchingInlineSnapshot + +expectType( + toThrowErrorMatchingInlineSnapshot.call({} as Context, new Error('received')), +); + +expectType( + toThrowErrorMatchingInlineSnapshot.call( + {} as Context, + new Error('received'), + 'inlineSnapshot', + ), +); + +expectType( + toThrowErrorMatchingInlineSnapshot.call( + {} as Context, + new Error('received'), + 'inlineSnapshot', + true, // fromPromise + ), +); + +expectType( + toThrowErrorMatchingInlineSnapshot.call( + {} as Context, + new Error('received'), + undefined, + false, // fromPromise + ), +); + +expectError(toThrowErrorMatchingInlineSnapshot({received: 'value'})); diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 5aff73e5916d..8e56ec912661 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -153,11 +153,10 @@ export const cleanup = ( }; }; -export const toMatchSnapshot: MatcherFunctionWithContext = function ( - received: unknown, - propertiesOrHint?: object | string, - hint?: string, -) { +export const toMatchSnapshot: MatcherFunctionWithContext< + Context, + [propertiesOrHint?: object | string, hint?: string] +> = function (received, propertiesOrHint, hint) { const matcherName = 'toMatchSnapshot'; let properties; @@ -211,70 +210,68 @@ export const toMatchSnapshot: MatcherFunctionWithContext = function ( }); }; -export const toMatchInlineSnapshot: MatcherFunctionWithContext = - function ( - received: unknown, - propertiesOrSnapshot?: object | string, - inlineSnapshot?: string, - ) { - const matcherName = 'toMatchInlineSnapshot'; - let properties; - - const length = arguments.length; - if (length === 2 && typeof propertiesOrSnapshot === 'string') { - inlineSnapshot = propertiesOrSnapshot; - } else if (length >= 2) { - const options: MatcherHintOptions = { - isNot: this.isNot, - promise: this.promise, - }; - if (length === 3) { - options.secondArgument = SNAPSHOT_ARG; - options.secondArgumentColor = noColor; - } +export const toMatchInlineSnapshot: MatcherFunctionWithContext< + Context, + [propertiesOrSnapshot?: object | string, inlineSnapshot?: string] +> = function (received, propertiesOrSnapshot, inlineSnapshot) { + const matcherName = 'toMatchInlineSnapshot'; + let properties; - if ( - typeof propertiesOrSnapshot !== 'object' || - propertiesOrSnapshot === null - ) { - throw new Error( - matcherErrorMessage( - matcherHint(matcherName, undefined, PROPERTIES_ARG, options), - `Expected ${EXPECTED_COLOR('properties')} must be an object`, - printWithType( - 'Expected properties', - propertiesOrSnapshot, - printExpected, - ), - ), - ); - } + const length = arguments.length; + if (length === 2 && typeof propertiesOrSnapshot === 'string') { + inlineSnapshot = propertiesOrSnapshot; + } else if (length >= 2) { + const options: MatcherHintOptions = { + isNot: this.isNot, + promise: this.promise, + }; + if (length === 3) { + options.secondArgument = SNAPSHOT_ARG; + options.secondArgumentColor = noColor; + } - if (length === 3 && typeof inlineSnapshot !== 'string') { - throw new Error( - matcherErrorMessage( - matcherHint(matcherName, undefined, PROPERTIES_ARG, options), - 'Inline snapshot must be a string', - printWithType('Inline snapshot', inlineSnapshot, serialize), + if ( + typeof propertiesOrSnapshot !== 'object' || + propertiesOrSnapshot === null + ) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, PROPERTIES_ARG, options), + `Expected ${EXPECTED_COLOR('properties')} must be an object`, + printWithType( + 'Expected properties', + propertiesOrSnapshot, + printExpected, ), - ); - } + ), + ); + } - properties = propertiesOrSnapshot; + if (length === 3 && typeof inlineSnapshot !== 'string') { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, PROPERTIES_ARG, options), + 'Inline snapshot must be a string', + printWithType('Inline snapshot', inlineSnapshot, serialize), + ), + ); } - return _toMatchSnapshot({ - context: this, - inlineSnapshot: - inlineSnapshot !== undefined - ? stripAddedIndentation(inlineSnapshot) - : undefined, - isInline: true, - matcherName, - properties, - received, - }); - }; + properties = propertiesOrSnapshot; + } + + return _toMatchSnapshot({ + context: this, + inlineSnapshot: + inlineSnapshot !== undefined + ? stripAddedIndentation(inlineSnapshot) + : undefined, + isInline: true, + matcherName, + properties, + received, + }); +}; const _toMatchSnapshot = (config: MatchSnapshotConfig) => { const {context, hint, inlineSnapshot, isInline, matcherName, properties} = @@ -409,59 +406,63 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => { }; }; -export const toThrowErrorMatchingSnapshot: MatcherFunctionWithContext = - function (received: unknown, hint?: string, fromPromise?: boolean) { - const matcherName = 'toThrowErrorMatchingSnapshot'; +export const toThrowErrorMatchingSnapshot: MatcherFunctionWithContext< + Context, + [hint?: string, fromPromise?: boolean] +> = function (received, hint, fromPromise) { + const matcherName = 'toThrowErrorMatchingSnapshot'; - // Future breaking change: Snapshot hint must be a string - // if (hint !== undefined && typeof hint !== string) {} - - return _toThrowErrorMatchingSnapshot( - { - context: this, - hint, - isInline: false, - matcherName, - received, - }, - fromPromise, - ); - }; + // Future breaking change: Snapshot hint must be a string + // if (hint !== undefined && typeof hint !== string) {} -export const toThrowErrorMatchingInlineSnapshot: MatcherFunctionWithContext = - function (received: unknown, inlineSnapshot?: string, fromPromise?: boolean) { - const matcherName = 'toThrowErrorMatchingInlineSnapshot'; + return _toThrowErrorMatchingSnapshot( + { + context: this, + hint, + isInline: false, + matcherName, + received, + }, + fromPromise, + ); +}; - if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { - const options: MatcherHintOptions = { - expectedColor: noColor, - isNot: this.isNot, - promise: this.promise, - }; +export const toThrowErrorMatchingInlineSnapshot: MatcherFunctionWithContext< + Context, + [inlineSnapshot?: string, fromPromise?: boolean] +> = function (received, inlineSnapshot, fromPromise) { + const matcherName = 'toThrowErrorMatchingInlineSnapshot'; - throw new Error( - matcherErrorMessage( - matcherHint(matcherName, undefined, SNAPSHOT_ARG, options), - 'Inline snapshot must be a string', - printWithType('Inline snapshot', inlineSnapshot, serialize), - ), - ); - } + if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { + const options: MatcherHintOptions = { + expectedColor: noColor, + isNot: this.isNot, + promise: this.promise, + }; - return _toThrowErrorMatchingSnapshot( - { - context: this, - inlineSnapshot: - inlineSnapshot !== undefined - ? stripAddedIndentation(inlineSnapshot) - : undefined, - isInline: true, - matcherName, - received, - }, - fromPromise, + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, SNAPSHOT_ARG, options), + 'Inline snapshot must be a string', + printWithType('Inline snapshot', inlineSnapshot, serialize), + ), ); - }; + } + + return _toThrowErrorMatchingSnapshot( + { + context: this, + inlineSnapshot: + inlineSnapshot !== undefined + ? stripAddedIndentation(inlineSnapshot) + : undefined, + isInline: true, + matcherName, + received, + }, + fromPromise, + ); +}; const _toThrowErrorMatchingSnapshot = ( config: MatchSnapshotConfig, From 030692fdde6cff57409eedc8ca984c1716f086c7 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 10 Sep 2022 14:08:29 +0300 Subject: [PATCH 3/5] fix type errors in tests --- .../src/__tests__/matcher.test.ts | 20 +++---- .../src/__tests__/throwMatcher.test.ts | 60 ++++++++++--------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/jest-snapshot/src/__tests__/matcher.test.ts b/packages/jest-snapshot/src/__tests__/matcher.test.ts index f9cb3aa27247..58320e021b20 100644 --- a/packages/jest-snapshot/src/__tests__/matcher.test.ts +++ b/packages/jest-snapshot/src/__tests__/matcher.test.ts @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {toMatchSnapshot} from '../'; +import {type Context, toMatchSnapshot} from '../'; -it('matcher returns matcher name, expected and actual values', () => { - const actual = 'a'; - const expected = 'b'; - const matcher = toMatchSnapshot.bind({ +test('returns matcher name, expected and actual values', () => { + const mockedContext = { snapshotState: { - match: (_testName: string, _received: unknown) => ({actual, expected}), + match: () => ({actual: 'a', expected: 'b'}), }, - }); + } as unknown as Context; - const matcherResult = matcher({a: 1}); + const matcherResult = toMatchSnapshot.call(mockedContext, { + a: 1, + }); expect(matcherResult).toEqual( expect.objectContaining({ - actual, - expected, + actual: 'a', + expected: 'b', name: 'toMatchSnapshot', }), ); diff --git a/packages/jest-snapshot/src/__tests__/throwMatcher.test.ts b/packages/jest-snapshot/src/__tests__/throwMatcher.test.ts index d17dfac04138..94a201d87757 100644 --- a/packages/jest-snapshot/src/__tests__/throwMatcher.test.ts +++ b/packages/jest-snapshot/src/__tests__/throwMatcher.test.ts @@ -5,23 +5,24 @@ * LICENSE file in the root directory of this source tree. */ -import {toThrowErrorMatchingSnapshot} from '..'; +import {type Context, toThrowErrorMatchingSnapshot} from '../'; -let matchFn: jest.Mock; +const mockedMatch = jest.fn(() => ({ + actual: 'coconut', + expected: 'coconut', +})); -beforeEach(() => { - matchFn = jest.fn(() => ({ - actual: 'coconut', - expected: 'coconut', - })); +const mockedContext = { + snapshotState: {match: mockedMatch}, +} as unknown as Context; + +afterEach(() => { + jest.clearAllMocks(); }); it('throw matcher can take func', () => { - const throwMatcher = toThrowErrorMatchingSnapshot.bind({ - snapshotState: {match: matchFn}, - }); - - throwMatcher( + toThrowErrorMatchingSnapshot.call( + mockedContext, () => { throw new Error('coconut'); }, @@ -29,35 +30,40 @@ it('throw matcher can take func', () => { false, ); - expect(matchFn).toHaveBeenCalledWith( + expect(mockedMatch).toBeCalledTimes(1); + expect(mockedMatch).toHaveBeenCalledWith( expect.objectContaining({received: 'coconut', testName: ''}), ); }); describe('throw matcher from promise', () => { - let throwMatcher: typeof toThrowErrorMatchingSnapshot; - - beforeEach(() => { - throwMatcher = toThrowErrorMatchingSnapshot.bind({ - snapshotState: {match: matchFn}, - }); - }); - it('can take error', () => { - throwMatcher(new Error('coconut'), 'testName', true); + toThrowErrorMatchingSnapshot.call( + mockedContext, + new Error('coco'), + 'testName', + true, + ); - expect(matchFn).toHaveBeenCalledWith( - expect.objectContaining({received: 'coconut', testName: ''}), + expect(mockedMatch).toBeCalledTimes(1); + expect(mockedMatch).toHaveBeenCalledWith( + expect.objectContaining({received: 'coco', testName: ''}), ); }); it('can take custom error', () => { class CustomError extends Error {} - throwMatcher(new CustomError('coconut'), 'testName', true); + toThrowErrorMatchingSnapshot.call( + mockedContext, + new CustomError('nut'), + 'testName', + true, + ); - expect(matchFn).toHaveBeenCalledWith( - expect.objectContaining({received: 'coconut', testName: ''}), + expect(mockedMatch).toBeCalledTimes(1); + expect(mockedMatch).toHaveBeenCalledWith( + expect.objectContaining({received: 'nut', testName: ''}), ); }); }); From 1219457c00a188b01e2fcd87f1cd8e377c542484 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 10 Sep 2022 14:17:50 +0300 Subject: [PATCH 4/5] add changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7cc0c566a5..975c3f3f4c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,14 @@ ### Features - `[@jest/environment, jest-runtime]` Allow passing a generic type argument to `jest.createMockFromModule()` method ([#13202](https://github.com/facebook/jest/pull/13202)) +- `[expect]` Expose `ExpectationResult` type ([#13240])(https://github.com/facebook/jest/pull/13240)) +- `[jest-snapshot]` Expose `Context` type ([#13240])(https://github.com/facebook/jest/pull/13240)) - `[@jest/globals]` Add `jest.Mock` type helper ([#13235](https://github.com/facebook/jest/pull/13235)) ### Fixes - `[jest-core]` Capture execError during `TestScheduler.scheduleTests` and dispatch to reporters ([#13203](https://github.com/facebook/jest/pull/13203)) +- `[jest-snapshot]` Fix typings of snapshot matchers ([#13240])(https://github.com/facebook/jest/pull/13240)) ### Chore & Maintenance From 5a399ba052ff6393f81354c781d4a628baf7555b Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sat, 10 Sep 2022 14:26:48 +0300 Subject: [PATCH 5/5] fix TS 4.3 error --- packages/jest-snapshot/__typetests__/matchers.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-snapshot/__typetests__/matchers.test.ts b/packages/jest-snapshot/__typetests__/matchers.test.ts index 6a3fdf264256..660789233b8e 100644 --- a/packages/jest-snapshot/__typetests__/matchers.test.ts +++ b/packages/jest-snapshot/__typetests__/matchers.test.ts @@ -8,8 +8,8 @@ import {expectError, expectType} from 'tsd-lite'; import type {ExpectationResult} from 'expect'; import { - type Context, - type SnapshotState, + Context, + SnapshotState, toMatchInlineSnapshot, toMatchSnapshot, toThrowErrorMatchingInlineSnapshot,