Skip to content

Commit 8a8e894

Browse files
authored
Merge pull request #892 from Yihao-G/main
fix: incorrect Jest matcher extension TypeScript type
2 parents b63230f + 9d47c33 commit 8a8e894

File tree

6 files changed

+143
-67
lines changed

6 files changed

+143
-67
lines changed

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"eslint": "9.14.0",
5757
"eslint-config-prettier": "^9.1.0",
5858
"eslint-plugin-prettier": "^5.2.1",
59+
"expect-type": "^1.1.0",
5960
"globals": "^15.9.0",
6061
"husky": "^9.0.11",
6162
"jest": "^29.7.0",

packages/jest/src/__tests__/extensions.spec.js packages/jest/src/__tests__/extensions.spec.ts

+79-22
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { describe, it, beforeAll, afterAll, expect } from '@jest/globals';
1+
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
22

3-
import fetchMockModule from '../index';
4-
const fetchMock = fetchMockModule.default;
3+
import fetchMock from '../index';
4+
import { expectTypeOf } from 'expect-type';
55

66
const humanVerbToMethods = [
7-
'Fetched',
8-
'Got:get',
9-
'Posted:post',
10-
'Put:put',
11-
'Deleted:delete',
12-
'FetchedHead:head',
13-
'Patched:patch',
14-
];
7+
{ humanVerb: 'Fetched', method: 'get' },
8+
{ humanVerb: 'Got', method: 'get' },
9+
{ humanVerb: 'Posted', method: 'post' },
10+
{ humanVerb: 'Put', method: 'put' },
11+
{ humanVerb: 'Deleted', method: 'delete' },
12+
{ humanVerb: 'FetchedHead', method: 'head' },
13+
{ humanVerb: 'Patched', method: 'patch' },
14+
] as const;
1515

1616
// initialize a mock here so fetch is patched across all tests
1717
fetchMock.mockGlobal();
@@ -20,34 +20,37 @@ describe.each([
2020
['patched fetch input', fetch],
2121
['fetchMock input', fetchMock],
2222
])('expect extensions %s', (_str, expectInput) => {
23-
humanVerbToMethods.forEach((verbs) => {
24-
const [humanVerb, method] = verbs.split(':');
23+
humanVerbToMethods.forEach(({ humanVerb, method }) => {
2524
describe(`${humanVerb} expectations`, () => {
2625
describe('when no calls', () => {
2726
beforeAll(() => {
2827
fetchMock.mockGlobal().route('*', 200, 'my-route');
2928
});
3029
afterAll(() => fetchMock.mockReset());
3130
it(`toHave${humanVerb} should be falsy`, () => {
31+
expect(expectInput).not[`toHave${humanVerb}`]();
3232
expect(expectInput).not[`toHave${humanVerb}`](
3333
'http://example.com/path',
3434
);
3535
});
3636

3737
it(`toHaveLast${humanVerb} should be falsy`, () => {
38+
expect(expectInput).not[`toHaveLast${humanVerb}`]();
3839
expect(expectInput).not[`toHaveLast${humanVerb}`](
3940
'http://example.com/path',
4041
);
4142
});
4243

4344
it(`toHaveNth${humanVerb} should be falsy`, () => {
45+
expect(expectInput).not[`toHaveNth${humanVerb}`](1);
4446
expect(expectInput).not[`toHaveNth${humanVerb}`](
4547
1,
4648
'http://example.com/path',
4749
);
4850
});
4951

5052
it(`toHave${humanVerb}Times should be falsy`, () => {
53+
expect(expectInput).not[`toHave${humanVerb}Times`](1);
5154
expect(expectInput).not[`toHave${humanVerb}Times`](
5255
1,
5356
'http://example.com/path',
@@ -58,20 +61,24 @@ describe.each([
5861
beforeAll(() => {
5962
fetchMock.mockGlobal().route('*', 200);
6063
fetch('http://example.com/path2', {
61-
method: method || 'get',
64+
method,
6265
headers: {
6366
test: 'header',
6467
},
6568
});
6669
fetch('http://example.com/path', {
67-
method: method || 'get',
70+
method,
6871
headers: {
6972
test: 'header',
7073
},
7174
});
7275
});
7376
afterAll(() => fetchMock.mockReset());
7477

78+
it('matches without any matcher supplied', () => {
79+
expect(expectInput)[`toHave${humanVerb}`]();
80+
});
81+
7582
it('matches with just url', () => {
7683
expect(expectInput)[`toHave${humanVerb}`]('http://example.com/path');
7784
});
@@ -111,19 +118,30 @@ describe.each([
111118
},
112119
);
113120
});
121+
122+
it('should not be any', () => {
123+
expectTypeOf(expect(expectInput)[`toHave${humanVerb}`]).not.toBeAny();
124+
expectTypeOf(
125+
expect(expectInput).not[`toHave${humanVerb}`],
126+
).not.toBeAny();
127+
});
114128
});
115129
describe(`toHaveLast${humanVerb}`, () => {
116130
beforeAll(() => {
117131
fetchMock.mockGlobal().route('*', 200);
118132
fetch('http://example.com/path', {
119-
method: method || 'get',
133+
method,
120134
headers: {
121135
test: 'header',
122136
},
123137
});
124138
});
125139
afterAll(() => fetchMock.mockReset());
126140

141+
it('matches without any matcher supplied', () => {
142+
expect(expectInput)[`toHaveLast${humanVerb}`]();
143+
});
144+
127145
it('matches with just url', () => {
128146
expect(expectInput)[`toHaveLast${humanVerb}`](
129147
'http://example.com/path',
@@ -168,26 +186,39 @@ describe.each([
168186
},
169187
);
170188
});
189+
190+
it('should not be any', () => {
191+
expectTypeOf(
192+
expect(expectInput)[`toHaveLast${humanVerb}`],
193+
).not.toBeAny();
194+
expectTypeOf(
195+
expect(expectInput).not[`toHaveLast${humanVerb}`],
196+
).not.toBeAny();
197+
});
171198
});
172199

173200
describe(`toHaveNth${humanVerb}`, () => {
174201
beforeAll(() => {
175202
fetchMock.mockGlobal().route('*', 200);
176203
fetch('http://example1.com/path', {
177-
method: method || 'get',
204+
method,
178205
headers: {
179206
test: 'header',
180207
},
181208
});
182209
fetch('http://example2.com/path', {
183-
method: method || 'get',
210+
method,
184211
headers: {
185212
test: 'header',
186213
},
187214
});
188215
});
189216
afterAll(() => fetchMock.mockReset());
190217

218+
it('matches without any matcher supplied', () => {
219+
expect(expectInput)[`toHaveNth${humanVerb}`](2);
220+
});
221+
191222
it('matches with just url', () => {
192223
expect(expectInput)[`toHaveNth${humanVerb}`](
193224
2,
@@ -244,26 +275,39 @@ describe.each([
244275
'http://example2.com/path',
245276
);
246277
});
278+
279+
it('should not be any', () => {
280+
expectTypeOf(
281+
expect(expectInput)[`toHaveNth${humanVerb}`],
282+
).not.toBeAny();
283+
expectTypeOf(
284+
expect(expectInput).not[`toHaveNth${humanVerb}`],
285+
).not.toBeAny();
286+
});
247287
});
248288

249289
describe(`toHave${humanVerb}Times`, () => {
250290
beforeAll(() => {
251291
fetchMock.mockGlobal().route('*', 200);
252292
fetch('http://example.com/path', {
253-
method: method || 'get',
293+
method,
254294
headers: {
255295
test: 'header',
256296
},
257297
});
258298
fetch('http://example.com/path', {
259-
method: method || 'get',
299+
method,
260300
headers: {
261301
test: 'header',
262302
},
263303
});
264304
});
265305
afterAll(() => fetchMock.mockReset());
266306

307+
it('matches without any matcher supplied', () => {
308+
expect(expectInput)[`toHave${humanVerb}Times`](2);
309+
});
310+
267311
it('matches with just url', () => {
268312
expect(expectInput)[`toHave${humanVerb}Times`](
269313
2,
@@ -327,6 +371,15 @@ describe.each([
327371
'http://example.com/path',
328372
);
329373
});
374+
375+
it('should not be any', () => {
376+
expectTypeOf(
377+
expect(expectInput)[`toHave${humanVerb}Times`],
378+
).not.toBeAny();
379+
expectTypeOf(
380+
expect(expectInput).not[`toHave${humanVerb}Times`],
381+
).not.toBeAny();
382+
});
330383
});
331384
});
332385
});
@@ -359,12 +412,16 @@ describe.each([
359412
it("doesn't match if too few calls", () => {
360413
expect(expectInput).not.toBeDone('route2');
361414
});
415+
416+
it('should not be any', () => {
417+
expectTypeOf(expect(expectInput).toBeDone).not.toBeAny();
418+
expectTypeOf(expect(expectInput).not.toBeDone).not.toBeAny();
419+
});
362420
});
363421
});
364422

365423
describe('expect extensions: bad inputs', () => {
366-
humanVerbToMethods.forEach((verbs) => {
367-
const [humanVerb] = verbs.split(':');
424+
humanVerbToMethods.forEach(({ humanVerb }) => {
368425
it(`${humanVerb} - throws an error if we the input is not patched with fetchMock`, () => {
369426
expect(() => {
370427
// This simulates a "fetch" implementation that doesn't have fetchMock

packages/jest/src/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,8 @@ declare global {
7676
}
7777
}
7878
/* eslint-enable @typescript-eslint/no-namespace */
79+
80+
declare module '@jest/expect' {
81+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
82+
interface Matchers<R> extends FetchMockMatchers<R> {}
83+
}

packages/jest/src/jest-extensions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Object.entries(expectMethodNameToMethodMap).forEach(([humanVerb, method]) => {
172172
scopeExpectationNameToMethod(name, humanVerb),
173173
scopeExpectationFunctionToMethod(func, method),
174174
]),
175-
) as Omit<RawFetchMockMatchers, HumanVerbMethodNames<'Fetched'>>;
175+
) as Omit<RawFetchMockMatchers, HumanVerbMethodNames<'Fetched'> | 'toBeDone'>;
176176

177177
expect.extend(extensions);
178178
});

0 commit comments

Comments
 (0)