Skip to content

Commit 033048a

Browse files
committed
fix: adding types to jest matchers
1 parent d1ccb8a commit 033048a

File tree

3 files changed

+130
-15
lines changed

3 files changed

+130
-15
lines changed

packages/jest/src/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
} from 'fetch-mock';
66
import './jest-extensions.js';
77
import type { Jest } from '@jest/environment';
8+
import type { FetchMockMatchers } from './types.js';
9+
export { FetchMockMatchers } from './types.js';
810

911
type MockResetOptions = {
1012
includeSticky: boolean;
@@ -55,3 +57,17 @@ const fetchMockJest = new FetchMockJest({
5557
});
5658

5759
export default fetchMockJest;
60+
61+
/* eslint-disable @typescript-eslint/no-namespace */
62+
/**
63+
* Export types on the expect object
64+
*/
65+
declare global {
66+
namespace jest {
67+
// Type-narrow expect for FetchMock
68+
interface Expect {
69+
(actual: FetchMock): FetchMockMatchers;
70+
}
71+
}
72+
}
73+
/* eslint-enable @typescript-eslint/no-namespace */

packages/jest/src/jest-extensions.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ import type {
66
CallHistoryFilter,
77
UserRouteConfig,
88
} from 'fetch-mock';
9-
const methodlessExtensions = {
9+
import {
10+
HumanVerbMethodNames,
11+
HumanVerbs,
12+
RawFetchMockMatchers,
13+
} from './types.js';
14+
15+
const methodlessExtensions: Pick<
16+
RawFetchMockMatchers,
17+
HumanVerbMethodNames<'Fetched'>
18+
> = {
1019
toHaveFetched: (
1120
{ fetchMock }: { fetchMock: FetchMock },
1221
filter: CallHistoryFilter,
@@ -128,25 +137,24 @@ function scopeExpectationNameToMethod(name: string, humanVerb: string): string {
128137
return name.replace('Fetched', humanVerb);
129138
}
130139

131-
[
132-
'Got:get',
133-
'Posted:post',
134-
'Put:put',
135-
'Deleted:delete',
136-
'FetchedHead:head',
137-
'Patched:patch',
138-
].forEach((verbs) => {
139-
const [humanVerb, method] = verbs.split(':');
140+
const expectMethodNameToMethodMap: {
141+
[humanVerb in Exclude<HumanVerbs, 'Fetched'>]: string;
142+
} = {
143+
Got: 'get',
144+
Posted: 'post',
145+
Put: 'put',
146+
Deleted: 'delete',
147+
FetchedHead: 'head',
148+
Patched: 'patch',
149+
};
140150

141-
const extensions: {
142-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143-
[key: string]: (...args: any[]) => SyncExpectationResult;
144-
} = Object.fromEntries(
151+
Object.entries(expectMethodNameToMethodMap).forEach(([humanVerb, method]) => {
152+
const extensions = Object.fromEntries(
145153
Object.entries(methodlessExtensions).map(([name, func]) => [
146154
scopeExpectationNameToMethod(name, humanVerb),
147155
scopeExpectationFunctionToMethod(func, method),
148156
]),
149-
);
157+
) as Omit<RawFetchMockMatchers, HumanVerbMethodNames<'Fetched'>>;
150158

151159
expect.extend(extensions);
152160
});

packages/jest/src/types.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { CallHistoryFilter, FetchMock, UserRouteConfig } from 'fetch-mock';
2+
import type { SyncExpectationResult } from 'expect';
3+
4+
export type HumanVerbs =
5+
| 'Got'
6+
| 'Posted'
7+
| 'Put'
8+
| 'Deleted'
9+
| 'FetchedHead'
10+
| 'Patched'
11+
| 'Fetched';
12+
13+
/**
14+
* Verify that a particular call for the HTTP method implied in the function name
15+
* has occurred
16+
*/
17+
export type ToHaveFunc = (
18+
filter: CallHistoryFilter,
19+
options: UserRouteConfig,
20+
) => SyncExpectationResult;
21+
22+
/**
23+
* Verify that a particular Nth call for the HTTP method implied in the function name
24+
* has occurred
25+
*/
26+
export type ToHaveNthFunc = (
27+
n: number,
28+
filter: CallHistoryFilter,
29+
options: UserRouteConfig,
30+
) => SyncExpectationResult;
31+
32+
/**
33+
* Verify that a particular call for the HTTP method implied in the function name
34+
* has been made N times
35+
*/
36+
export type ToHaveTimesFunc = (
37+
times: number,
38+
filter: CallHistoryFilter,
39+
options: UserRouteConfig,
40+
) => SyncExpectationResult;
41+
42+
export type FetchMockMatchers = {
43+
toHaveFetched: ToHaveFunc;
44+
toHaveLastFetched: ToHaveFunc;
45+
toHaveFetchedTimes: ToHaveTimesFunc;
46+
toHaveNthFetched: ToHaveNthFunc;
47+
toHaveGot: ToHaveFunc;
48+
toHaveLastGot: ToHaveFunc;
49+
toHaveGotTimes: ToHaveTimesFunc;
50+
toHaveNthGot: ToHaveNthFunc;
51+
toHavePosted: ToHaveFunc;
52+
toHaveLastPosted: ToHaveFunc;
53+
toHavePostedTimes: ToHaveTimesFunc;
54+
toHaveNthPosted: ToHaveNthFunc;
55+
toHavePut: ToHaveFunc;
56+
toHaveLastPut: ToHaveFunc;
57+
toHavePutTimes: ToHaveTimesFunc;
58+
toHaveNthPut: ToHaveNthFunc;
59+
toHaveDeleted: ToHaveFunc;
60+
toHaveLastDeleted: ToHaveFunc;
61+
toHaveDeletedTimes: ToHaveTimesFunc;
62+
toHaveNthDeleted: ToHaveNthFunc;
63+
toHaveFetchedHead: ToHaveFunc;
64+
toHaveLastFetchedHead: ToHaveFunc;
65+
toHaveFetchedHeadTimes: ToHaveTimesFunc;
66+
toHaveNthFetchedHead: ToHaveNthFunc;
67+
toHavePatched: ToHaveFunc;
68+
toHaveLastPatched: ToHaveFunc;
69+
toHavePatchedTimes: ToHaveTimesFunc;
70+
toHaveNthPatched: ToHaveNthFunc;
71+
};
72+
73+
// types for use doing some intermediate type checking in extensions to make sure things don't get out of sync
74+
/**
75+
* This type allows us to take the Matcher type and creat another one
76+
*/
77+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
78+
type RawMatcher<T extends (...args: any[]) => any> = (
79+
input: { fetchMock: FetchMock },
80+
...args: Parameters<T>
81+
) => ReturnType<T>;
82+
83+
export type RawFetchMockMatchers = {
84+
[k in keyof FetchMockMatchers]: RawMatcher<FetchMockMatchers[k]>;
85+
};
86+
87+
export type HumanVerbMethodNames<M extends HumanVerbs> =
88+
| `toHave${M}`
89+
| `toHaveLast${M}`
90+
| `toHave${M}Times`
91+
| `toHaveNth${M}`;

0 commit comments

Comments
 (0)