-
-
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
Changes from 2 commits
1b0121b
00315ce
0272238
96ab005
9db869c
a3ccd47
9c166b5
539f974
90aa595
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -94,9 +94,9 @@ export interface BaseExpect { | |||||
} | ||||||
|
||||||
export type Expect = { | ||||||
<T = unknown>(actual: T): Matchers<void> & | ||||||
Inverse<Matchers<void>> & | ||||||
PromiseMatchers; | ||||||
<T = unknown>(actual: T): Matchers<void, T> & | ||||||
Inverse<Matchers<void, T>> & | ||||||
PromiseMatchers<T>; | ||||||
} & BaseExpect & | ||||||
AsymmetricMatchers & | ||||||
Inverse<Omit<AsymmetricMatchers, 'any' | 'anything'>>; | ||||||
|
@@ -118,36 +118,43 @@ export interface AsymmetricMatchers { | |||||
stringMatching(sample: string | RegExp): AsymmetricMatcher; | ||||||
} | ||||||
|
||||||
type PromiseMatchers = { | ||||||
type PromiseMatchers<T = unknown> = { | ||||||
/** | ||||||
* 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>>>; | ||||||
rejects: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>, T>>; | ||||||
/** | ||||||
* 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>>>; | ||||||
resolves: Matchers<Promise<void>> & Inverse<Matchers<Promise<void>, T>>; | ||||||
}; | ||||||
|
||||||
export interface Matchers<R extends void | Promise<void>> { | ||||||
type EnsureFunctionLike<T> = T extends (...args: Array<unknown>) => unknown | ||||||
? T | ||||||
: never; | ||||||
|
||||||
export interface Matchers<R extends void | Promise<void>, T = unknown> { | ||||||
/** | ||||||
* 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 commentThe reason will be displayed to describe this comment to others. Learn more. you are a wizard :) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
/** | ||||||
* Ensure that a mock function is called with specific arguments on an Nth call. | ||||||
*/ | ||||||
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: unknown): R; | ||||||
nthReturnedWith<U extends EnsureFunctionLike<T>>( | ||||||
nth: number, | ||||||
expected: ReturnType<U>, | ||||||
): R; | ||||||
/** | ||||||
* Checks that a value is what you expect. It calls `Object.is` to compare values. | ||||||
* Don't use `toBe` with floating-point numbers. | ||||||
|
@@ -262,7 +269,9 @@ export interface Matchers<R extends void | Promise<void>> { | |||||
* 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: unknown): R; | ||||||
toHaveLastReturnedWith<U extends EnsureFunctionLike<T>>( | ||||||
expected: ReturnType<U>, | ||||||
): R; | ||||||
/** | ||||||
* Used to check that an object has a `.length` property | ||||||
* and it is set to a certain numeric value. | ||||||
|
@@ -273,7 +282,10 @@ export interface Matchers<R extends void | Promise<void>> { | |||||
* 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: unknown): R; | ||||||
toHaveNthReturnedWith<U extends EnsureFunctionLike<T>>( | ||||||
nth: number, | ||||||
expected: ReturnType<U>, | ||||||
): 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 | ||||||
|
@@ -303,7 +315,9 @@ export interface Matchers<R extends void | Promise<void>> { | |||||
/** | ||||||
* Use to ensure that a mock function returned a specific value. | ||||||
*/ | ||||||
toHaveReturnedWith(expected: unknown): R; | ||||||
toHaveReturnedWith<U extends EnsureFunctionLike<T>>( | ||||||
expected: ReturnType<U>, | ||||||
): R; | ||||||
/** | ||||||
* Check that a string matches a regular expression. | ||||||
*/ | ||||||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. Maybe change to this syntax? it works and causes less indirection
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||||||
/** | ||||||
* Use to test that objects have the same types as well as structure. | ||||||
*/ | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,21 +247,44 @@ 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')); | ||
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)); | ||
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')); | ||
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)); | ||
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)); | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. It works with |
||
expectType<void>( | ||
expect(jest.fn<() => string>()).toHaveNthReturnedWith(2, 'value'), | ||
); | ||
expectError(expect(jest.fn<() => number>()).toHaveNthReturnedWith(3, 'value')); | ||
expectError(expect(123).toHaveNthReturnedWith(4, 'value')); | ||
expectError(expect(123).toHaveNthReturnedWith(5)); | ||
expectError(expect(jest.fn()).toHaveNthReturnedWith()); | ||
expectError(expect(jest.fn()).toHaveNthReturnedWith(2)); | ||
|
||
// snapshot matchers | ||
|
||
|
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 (;