-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
feat(expect, @jest/expect): infer type of *ReturnedWith
matchers argument
#13278
Conversation
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works with () => unknown
just like before. Infers return value, if there is one. Errors if received
is not a function.
packages/expect/src/types.ts
Outdated
/** | ||
* Ensures the last call to a mock function was provided specific args. | ||
*/ | ||
lastCalledWith(...expected: Array<unknown>): R; | ||
/** | ||
* 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are a wizard :)
I'll add this to my PR as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks (; Glad you liked it. So much easier to explain with an example.
@mrazauskas |
packages/expect/src/types.ts
Outdated
@@ -325,7 +339,7 @@ export interface Matchers<R extends void | Promise<void>> { | |||
/** | |||
* 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That’s good idea. I was hoping that type argument could allow something like: expect(123).toHaveReturnedWith<() => number>(123)
. But it doesn’t.
@@ -16,7 +16,8 @@ import { | |||
} from 'expect'; | |||
import type * as jestMatcherUtils from 'jest-matcher-utils'; | |||
|
|||
type M = Matchers<void>; | |||
type M = Matchers<void, unknown>; | |||
type N = Matchers<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this N
for? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 (;
packages/expect/src/types.ts
Outdated
}; | ||
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately any
s are necessary for toHaveBeenCalled*
matchers. See #13268 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome stuff!
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
*ReturnedWith
matchers. I have added logic to infer types of*ReturnedWith
matchers argument from the type ofreceived
expression.Test plan
Type tests added.