Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(jest-snapshot): fix typings of snapshot matchers #13240

Merged
merged 5 commits into from Sep 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -3,11 +3,14 @@
### Features

- `[@jest/environment, jest-runtime]` Allow passing a generic type argument to `jest.createMockFromModule<T>()` 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))
Comment on lines +6 to +7
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used those in type tests, but in a way they can be useful to defined matcher functions (ExpectationResult) or to implement snapshot matchers (Context).

- `[@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

Expand Down
1 change: 1 addition & 0 deletions packages/expect/src/index.ts
Expand Up @@ -56,6 +56,7 @@ export type {
AsymmetricMatchers,
BaseExpect,
Expect,
ExpectationResult,
MatcherContext,
MatcherFunction,
MatcherFunctionWithContext,
Expand Down
147 changes: 147 additions & 0 deletions 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 {
Context,
SnapshotState,
toMatchInlineSnapshot,
toMatchSnapshot,
toThrowErrorMatchingInlineSnapshot,
toThrowErrorMatchingSnapshot,
} from 'jest-snapshot';

// Context

expectType<SnapshotState>(({} as Context).snapshotState);

// toMatchSnapshot

expectType<ExpectationResult>(
toMatchSnapshot.call({} as Context, {received: 'value'}),
);

expectType<ExpectationResult>(
toMatchSnapshot.call({} as Context, {received: 'value'}, 'someHint'),
);

expectType<ExpectationResult>(
toMatchSnapshot.call({} as Context, {received: 'value'}, {property: 'match'}),
);

expectType<ExpectationResult>(
toMatchSnapshot.call(
{} as Context,
{received: 'value'},
{property: 'match'},
'someHint',
),
);

expectError(toMatchSnapshot({received: 'value'}));

// toMatchInlineSnapshot

expectType<ExpectationResult>(
toMatchInlineSnapshot.call({} as Context, {received: 'value'}),
);

expectType<ExpectationResult>(
toMatchInlineSnapshot.call(
{} as Context,
{received: 'value'},
'inlineSnapshot',
),
);

expectType<ExpectationResult>(
toMatchInlineSnapshot.call(
{} as Context,
{received: 'value'},
{property: 'match'},
),
);

expectType<ExpectationResult>(
toMatchInlineSnapshot.call(
{} as Context,
{received: 'value'},
{property: 'match'},
'inlineSnapshot',
),
);

expectError(toMatchInlineSnapshot({received: 'value'}));

// toThrowErrorMatchingSnapshot

expectType<ExpectationResult>(
toThrowErrorMatchingSnapshot.call({} as Context, new Error('received')),
);

expectType<ExpectationResult>(
toThrowErrorMatchingSnapshot.call(
{} as Context,
new Error('received'),
'someHint',
),
);

expectType<ExpectationResult>(
toThrowErrorMatchingSnapshot.call(
{} as Context,
new Error('received'),
'someHint',
true, // fromPromise
),
);

expectType<ExpectationResult>(
toThrowErrorMatchingSnapshot.call(
{} as Context,
new Error('received'),
undefined,
false, // fromPromise
),
);

expectError(toThrowErrorMatchingSnapshot({received: 'value'}));

// toThrowErrorMatchingInlineSnapshot

expectType<ExpectationResult>(
toThrowErrorMatchingInlineSnapshot.call({} as Context, new Error('received')),
);

expectType<ExpectationResult>(
toThrowErrorMatchingInlineSnapshot.call(
{} as Context,
new Error('received'),
'inlineSnapshot',
),
);

expectType<ExpectationResult>(
toThrowErrorMatchingInlineSnapshot.call(
{} as Context,
new Error('received'),
'inlineSnapshot',
true, // fromPromise
),
);

expectType<ExpectationResult>(
toThrowErrorMatchingInlineSnapshot.call(
{} as Context,
new Error('received'),
undefined,
false, // fromPromise
),
);

expectError(toThrowErrorMatchingInlineSnapshot({received: 'value'}));
20 changes: 10 additions & 10 deletions packages/jest-snapshot/src/__tests__/matcher.test.ts
Expand Up @@ -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',
}),
);
Expand Down
60 changes: 33 additions & 27 deletions packages/jest-snapshot/src/__tests__/throwMatcher.test.ts
Expand Up @@ -5,59 +5,65 @@
* 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');
},
undefined,
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},
});
});
Comment on lines -40 to -44
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.bind was reworked into .call, which proofed to be friendlier with types. Also mocks are cleared in afterEach calling jest.clearAllMocks().


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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure that mock was cleared.

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: ''}),
);
});
});