feat(expect, @jest/expect): infer type of *ReturnedWith matchers argument#13278
feat(expect, @jest/expect): infer type of *ReturnedWith matchers argument#13278SimenB merged 9 commits intojestjs:mainfrom mrazauskas:feat-typed-ReturnedWith
*ReturnedWith matchers argument#13278Conversation
|
@royhadad What you think about this approach? I create this PR just to illustrate the idea of restricting type of the expect(123).toHaveLastReturnedWith(123);Only function can be passed as |
| expectError(expect(jest.fn()).nthReturnedWith()); | ||
| expectError(expect(jest.fn()).nthReturnedWith(2)); | ||
|
|
||
| expectType<void>(expect(jest.fn()).toHaveNthReturnedWith(1, 'value')); |
There was a problem hiding this comment.
It works with () => unknown just like before. Infers return value, if there is one. Errors if received is not a function.
| * Ensure that the last call to a mock function has returned a specified value. | ||
| */ | ||
| lastReturnedWith(expected: unknown): R; | ||
| lastReturnedWith<U extends EnsureFunctionLike<T>>(expected: ReturnType<U>): R; |
There was a problem hiding this comment.
you are a wizard :)
I'll add this to my PR as well
There was a problem hiding this comment.
Thanks (; Glad you liked it. So much easier to explain with an example.
@mrazauskas |
| * Ensure that a mock function has returned a specified value at least once. | ||
| */ | ||
| toReturnWith(expected: unknown): R; | ||
| toReturnWith<U extends EnsureFunctionLike<T>>(expected: ReturnType<U>): R; |
There was a problem hiding this comment.
Maybe change to this syntax? it works and causes less indirection
| toReturnWith<U extends EnsureFunctionLike<T>>(expected: ReturnType<U>): R; | |
| toReturnWith(expected: ReturnType<EnsureFunctionLike<T>>): R; |
There was a problem hiding this comment.
Thanks. That’s good idea. I was hoping that type argument could allow something like: expect(123).toHaveReturnedWith<() => number>(123). But it doesn’t.
|
|
||
| type M = Matchers<void>; | ||
| type M = Matchers<void, unknown>; | ||
| type N = Matchers<void>; |
There was a problem hiding this comment.
Just three test – takes two type args, but allows passing juts one and (below) errors if type args are missing.
Some time ago I added second arg without default. That was a breaking change and a user raised an issue. This test was added back in these days, but became unnecessary then @jest/expect package was introduced. Now it is needed again.
One day this will be wrapped with nice test("required type arguments", () => { /.... Good enough for now (;
| }; | ||
|
|
||
| export interface Matchers<R extends void | Promise<void>> { | ||
| type EnsureFunctionLike<T> = T extends (...args: any) => any ? T : never; |
There was a problem hiding this comment.
Unfortunately anys are necessary for toHaveBeenCalled* matchers. See #13268 (comment)
|
Thanks! I was wondering if <T>expect(actual: T).toBe(expected: T)Simple thing. Does it make sense? |
Seems about right. For weird edge cases the user can always pass /packages/expect/src/types.ts toBe(expected: unknown): R;
toContain(expected: unknown): R;
toContainEqual(expected: unknown): R;
toEqual(expected: unknown): R;
toMatchObject(
expected: Record<string, unknown> | Array<Record<string, unknown>>,
): R;
toStrictEqual(expected: unknown): R; |
|
Just wanted to say that some of these are allowed to take asymmetric type matchers and that can be complicated. Quick look at the docs, there is also an example with class Cat {}
function getCat(fn: (c: Cat) => void) {
return fn(new Cat());
}
{
const mock = jest.fn<(c: Cat) => void>();
getCat(mock);
expect(mock).toBeCalledWith(expect.any(Cat));
}
function randomCall(fn: (n: number) => void) {
return fn(Math.floor(Math.random() * 6 + 1));
}
{
const mock = jest.fn<(n: number) => void>();
randomCall(mock);
expect(mock).toBeCalledWith(expect.any(Number));
}First example works, but second one errors: "Argument of type 'AsymmetricMatcher_2' is not assignable to parameter of type 'number'." Ups.. That’s a regression. |
|
Same issue with expect(jest.fn<() => {one: string; two: string}>()).toHaveReturnedWith({
one: expect.stringContaining('a'),
two: expect.stringContaining('x'),
});I was trying to see if it is possible to fix |
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Similar to #13268, but
*ReturnedWithmatchers. I have added logic to infer types of*ReturnedWithmatchers argument from the type ofreceivedexpression.Test plan
Type tests added.