Skip to content

Commit

Permalink
fix(expect, @jest/expect): do not infer argument types of `*CalledWit…
Browse files Browse the repository at this point in the history
…h` and `*ReturnedWith` matchers (#13339)
  • Loading branch information
mrazauskas committed Sep 30, 2022
1 parent e574964 commit be0d4d1
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 81 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@

### Fixes

- `[expect, @jest/expect]` Revert buggy inference of argument types for `*CalledWith` and `*ReturnedWith` matchers introduced in 29.1.0 ([#13339](https://github.com/facebook/jest/pull/13339))

### Chore & Maintenance

### Performance
Expand All @@ -18,7 +20,7 @@

### Features

- `[expect, @jest/expect]` support type inference for function parameters in `CalledWith` assertions ([#13268](https://github.com/facebook/jest/pull/13268))
- `[expect, @jest/expect]` Support type inference for function parameters in `CalledWith` assertions ([#13268](https://github.com/facebook/jest/pull/13268))
- `[expect, @jest/expect]` Infer type of `*ReturnedWith` matchers argument ([#13278](https://github.com/facebook/jest/pull/13278))
- `[@jest/environment, jest-runtime]` Allow `jest.requireActual` and `jest.requireMock` to take a type argument ([#13253](https://github.com/facebook/jest/pull/13253))
- `[@jest/environment]` Allow `jest.mock` and `jest.doMock` to take a type argument ([#13254](https://github.com/facebook/jest/pull/13254))
Expand Down
3 changes: 1 addition & 2 deletions packages/expect/__typetests__/expect.test.ts
Expand Up @@ -16,8 +16,7 @@ import {
} from 'expect';
import type * as jestMatcherUtils from 'jest-matcher-utils';

type M = Matchers<void, unknown>;
type N = Matchers<void>;
type M = Matchers<void>;

expectError(() => {
type E = Matchers;
Expand Down
46 changes: 19 additions & 27 deletions packages/expect/src/types.ts
Expand Up @@ -94,9 +94,9 @@ export interface BaseExpect {
}

export type Expect = {
<T = unknown>(actual: T): Matchers<void, T> &
Inverse<Matchers<void, T>> &
PromiseMatchers<T>;
<T = unknown>(actual: T): Matchers<void> &
Inverse<Matchers<void>> &
PromiseMatchers;
} & BaseExpect &
AsymmetricMatchers &
Inverse<Omit<AsymmetricMatchers, 'any' | 'anything'>>;
Expand All @@ -118,38 +118,36 @@ export interface AsymmetricMatchers {
stringMatching(sample: string | RegExp): AsymmetricMatcher;
}

type PromiseMatchers<T = unknown> = {
type PromiseMatchers = {
/**
* Unwraps the reason of a rejected promise so any other matcher can be chained.
* If the promise is fulfilled the assertion fails.
*/
rejects: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>, T>>;
rejects: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>>>;
/**
* Unwraps the value of a fulfilled promise so any other matcher can be chained.
* If the promise is rejected the assertion fails.
*/
resolves: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>, T>>;
resolves: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>>>;
};

type EnsureFunctionLike<T> = T extends (...args: any) => any ? T : never;

export interface Matchers<R extends void | Promise<void>, T = unknown> {
export interface Matchers<R extends void | Promise<void>> {
/**
* Ensures the last call to a mock function was provided specific args.
*/
lastCalledWith(...expected: Parameters<EnsureFunctionLike<T>>): R;
lastCalledWith(...expected: Array<unknown>): R;
/**
* Ensure that the last call to a mock function has returned a specified value.
*/
lastReturnedWith(expected: ReturnType<EnsureFunctionLike<T>>): R;
lastReturnedWith(expected: unknown): R;
/**
* Ensure that a mock function is called with specific arguments on an Nth call.
*/
nthCalledWith(nth: number, ...expected: Parameters<EnsureFunctionLike<T>>): R;
nthCalledWith(nth: number, ...expected: Array<unknown>): R;
/**
* Ensure that the nth call to a mock function has returned a specified value.
*/
nthReturnedWith(nth: number, expected: ReturnType<EnsureFunctionLike<T>>): R;
nthReturnedWith(nth: number, expected: unknown): R;
/**
* Checks that a value is what you expect. It calls `Object.is` to compare values.
* Don't use `toBe` with floating-point numbers.
Expand All @@ -166,7 +164,7 @@ export interface Matchers<R extends void | Promise<void>, T = unknown> {
/**
* Ensure that a mock function is called with specific arguments.
*/
toBeCalledWith(...expected: Parameters<EnsureFunctionLike<T>>): R;
toBeCalledWith(...expected: Array<unknown>): R;
/**
* Using exact equality with floating point numbers is a bad idea.
* Rounding means that intuitive things fail.
Expand Down Expand Up @@ -249,25 +247,22 @@ export interface Matchers<R extends void | Promise<void>, T = unknown> {
/**
* Ensure that a mock function is called with specific arguments.
*/
toHaveBeenCalledWith(...expected: Parameters<EnsureFunctionLike<T>>): R;
toHaveBeenCalledWith(...expected: Array<unknown>): R;
/**
* Ensure that a mock function is called with specific arguments on an Nth call.
*/
toHaveBeenNthCalledWith(
nth: number,
...expected: Parameters<EnsureFunctionLike<T>>
): R;
toHaveBeenNthCalledWith(nth: number, ...expected: Array<unknown>): R;
/**
* If you have a mock function, you can use `.toHaveBeenLastCalledWith`
* to test what arguments it was last called with.
*/
toHaveBeenLastCalledWith(...expected: Parameters<EnsureFunctionLike<T>>): R;
toHaveBeenLastCalledWith(...expected: Array<unknown>): R;
/**
* Use to test the specific value that a mock function last returned.
* If the last call to the mock function threw an error, then this matcher will fail
* no matter what value you provided as the expected return value.
*/
toHaveLastReturnedWith(expected: ReturnType<EnsureFunctionLike<T>>): R;
toHaveLastReturnedWith(expected: unknown): R;
/**
* Used to check that an object has a `.length` property
* and it is set to a certain numeric value.
Expand All @@ -278,10 +273,7 @@ export interface Matchers<R extends void | Promise<void>, T = unknown> {
* If the nth call to the mock function threw an error, then this matcher will fail
* no matter what value you provided as the expected return value.
*/
toHaveNthReturnedWith(
nth: number,
expected: ReturnType<EnsureFunctionLike<T>>,
): R;
toHaveNthReturnedWith(nth: number, expected: unknown): R;
/**
* Use to check if property at provided reference keyPath exists for an object.
* For checking deeply nested properties in an object you may use dot notation or an array containing
Expand Down Expand Up @@ -311,7 +303,7 @@ export interface Matchers<R extends void | Promise<void>, T = unknown> {
/**
* Use to ensure that a mock function returned a specific value.
*/
toHaveReturnedWith(expected: ReturnType<EnsureFunctionLike<T>>): R;
toHaveReturnedWith(expected: unknown): R;
/**
* Check that a string matches a regular expression.
*/
Expand All @@ -333,7 +325,7 @@ export interface Matchers<R extends void | Promise<void>, T = unknown> {
/**
* Ensure that a mock function has returned a specified value at least once.
*/
toReturnWith(expected: ReturnType<EnsureFunctionLike<T>>): R;
toReturnWith(expected: unknown): R;
/**
* Use to test that objects have the same types as well as structure.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-expect/src/types.ts
Expand Up @@ -29,7 +29,7 @@ type Inverse<Matchers> = {
not: Matchers;
};

type JestMatchers<R extends void | Promise<void>, T> = Matchers<R, T> &
type JestMatchers<R extends void | Promise<void>, T> = Matchers<R> &
SnapshotMatchers<R, T>;

type PromiseMatchers<T = unknown> = {
Expand Down
122 changes: 72 additions & 50 deletions packages/jest-types/__typetests__/expect.test.ts
Expand Up @@ -214,55 +214,65 @@ expectError(expect(jest.fn()).toHaveBeenCalledTimes());
expectType<void>(expect(jest.fn()).toBeCalledWith());
expectType<void>(expect(jest.fn()).toBeCalledWith('value'));
expectType<void>(expect(jest.fn()).toBeCalledWith('value', 123));
expectType<void>(
expect(jest.fn<(a: string, b: number) => void>()).toBeCalledWith(
expect.stringContaining('value'),
123,
),
);

expectType<void>(expect(jest.fn()).toHaveBeenCalledWith());
expectType<void>(expect(jest.fn()).toHaveBeenCalledWith(123));
expectType<void>(expect(jest.fn()).toHaveBeenCalledWith(123, 'value'));

/**
* type inference for "CalledWith" matchers parameters
*/
expectError(expect(jest.fn<(a: string) => void>()).toHaveBeenCalledWith(123));
expectError(
expect(jest.fn<(a: string) => void>()).toHaveBeenNthCalledWith(1, 123),
);
expectError(
expect(jest.fn<(a: string) => void>()).toHaveBeenLastCalledWith(123),
);
expectType<void>(
expect(
jest.fn<(a: string, b: number, c?: boolean) => void>(),
).toHaveBeenCalledWith('value', 123),
);
expectType<void>(
expect(
jest.fn<(a: string, b: number, c?: boolean) => void>(),
).toHaveBeenCalledWith('value', 123, true),
);
expectError(
expect(
jest.fn<(a: string, b: number, c?: boolean) => void>(),
).toHaveBeenCalledWith(123, 'value'),
);
expectError(
expect(
jest.fn<(a: string, b: number, c?: boolean) => void>(),
).toHaveBeenCalledWith('value', 123, 'not a boolean'),
expect(jest.fn<(a: string, b: number) => void>()).toHaveBeenCalledWith(
expect.stringContaining('value'),
123,
),
);

expectType<void>(expect(jest.fn()).lastCalledWith());
expectType<void>(expect(jest.fn()).lastCalledWith('value'));
expectType<void>(expect(jest.fn()).lastCalledWith('value', 123));
expectType<void>(
expect(jest.fn<(a: string, b: number) => void>()).lastCalledWith(
expect.stringContaining('value'),
123,
),
);

expectType<void>(expect(jest.fn()).toHaveBeenLastCalledWith());
expectType<void>(expect(jest.fn()).toHaveBeenLastCalledWith(123));
expectType<void>(expect(jest.fn()).toHaveBeenLastCalledWith(123, 'value'));
expectType<void>(
expect(jest.fn<(a: string, b: number) => void>()).lastCalledWith(
expect.stringContaining('value'),
123,
),
);

expectType<void>(expect(jest.fn()).nthCalledWith(2));
expectType<void>(expect(jest.fn()).nthCalledWith(1, 'value'));
expectType<void>(expect(jest.fn()).nthCalledWith(1, 'value', 123));
expectType<void>(
expect(jest.fn<(a: string, b: number) => void>()).nthCalledWith(
1,
expect.stringContaining('value'),
123,
),
);
expectError(expect(jest.fn()).nthCalledWith());

expectType<void>(expect(jest.fn()).toHaveBeenNthCalledWith(2));
expectType<void>(expect(jest.fn()).toHaveBeenNthCalledWith(1, 'value'));
expectType<void>(expect(jest.fn()).toHaveBeenNthCalledWith(1, 'value', 123));
expectType<void>(
expect(jest.fn<(a: string, b: number) => void>()).toHaveBeenNthCalledWith(
1,
expect.stringContaining('value'),
123,
),
);
expectError(expect(jest.fn()).toHaveBeenNthCalledWith());

expectType<void>(expect(jest.fn()).toReturn());
Expand All @@ -278,43 +288,55 @@ expectError(expect(jest.fn()).toHaveReturnedTimes(true));
expectError(expect(jest.fn()).toHaveReturnedTimes());

expectType<void>(expect(jest.fn()).toReturnWith('value'));
expectType<void>(expect(jest.fn<() => string>()).toReturnWith('value'));
expectError(expect(jest.fn<() => number>()).toReturnWith('value'));
expectError(expect(123).toReturnWith('value'));
expectType<void>(
expect(jest.fn<() => string>()).toReturnWith(
expect.stringContaining('value'),
),
);
expectError(expect(jest.fn()).toReturnWith());

expectType<void>(expect(jest.fn()).toHaveReturnedWith(123));
expectType<void>(expect(jest.fn<() => number>()).toHaveReturnedWith(123));
expectError(expect(jest.fn<() => string>()).toHaveReturnedWith(123));
expectError(expect(123).toHaveReturnedWith(123));
expectType<void>(
expect(jest.fn<() => string>()).toHaveReturnedWith(
expect.stringContaining('value'),
),
);
expectError(expect(jest.fn()).toHaveReturnedWith());

expectType<void>(expect(jest.fn()).lastReturnedWith('value'));
expectType<void>(expect(jest.fn<() => string>()).lastReturnedWith('value'));
expectError(expect(jest.fn<() => number>()).lastReturnedWith('value'));
expectError(expect(123).lastReturnedWith('value'));
expectType<void>(
expect(jest.fn<() => string>()).lastReturnedWith(
expect.stringContaining('value'),
),
);
expectError(expect(jest.fn()).lastReturnedWith());

expectType<void>(expect(jest.fn()).toHaveLastReturnedWith(123));
expectType<void>(expect(jest.fn<() => number>()).toHaveLastReturnedWith(123));
expectError(expect(jest.fn<() => string>()).toHaveLastReturnedWith(123));
expectError(expect(123).toHaveLastReturnedWith(123));
expectType<void>(
expect(jest.fn<() => string>()).toHaveLastReturnedWith(
expect.stringContaining('value'),
),
);
expectError(expect(jest.fn()).toHaveLastReturnedWith());

expectType<void>(expect(jest.fn()).nthReturnedWith(1, 'value'));
expectType<void>(expect(jest.fn<() => string>()).nthReturnedWith(2, 'value'));
expectError(expect(jest.fn<() => number>()).nthReturnedWith(3, 'value'));
expectError(expect(123).nthReturnedWith(4, 'value'));
expectError(expect(123).nthReturnedWith(5));
expectType<void>(
expect(jest.fn<() => string>()).nthReturnedWith(
2,
expect.stringContaining('value'),
),
);
expectError(expect(123).nthReturnedWith(3));
expectError(expect(jest.fn()).nthReturnedWith());

expectType<void>(expect(jest.fn()).toHaveNthReturnedWith(1, 'value'));
expectType<void>(expect(jest.fn()).nthReturnedWith(1, 'value'));
expectType<void>(
expect(jest.fn<() => string>()).toHaveNthReturnedWith(2, 'value'),
expect(jest.fn<() => string>()).nthReturnedWith(
2,
expect.stringContaining('value'),
),
);
expectError(expect(jest.fn<() => number>()).toHaveNthReturnedWith(3, 'value'));
expectError(expect(123).toHaveNthReturnedWith(4, 'value'));
expectError(expect(123).toHaveNthReturnedWith(5));
expectError(expect(123).toHaveNthReturnedWith(3));
expectError(expect(jest.fn()).toHaveNthReturnedWith());

// snapshot matchers
Expand Down

0 comments on commit be0d4d1

Please sign in to comment.