Skip to content

Commit

Permalink
fix: adding types to jest matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
hanseltime committed Nov 26, 2024
1 parent d1ccb8a commit 033048a
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 15 deletions.
16 changes: 16 additions & 0 deletions packages/jest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
} from 'fetch-mock';
import './jest-extensions.js';
import type { Jest } from '@jest/environment';
import type { FetchMockMatchers } from './types.js';
export { FetchMockMatchers } from './types.js';

type MockResetOptions = {
includeSticky: boolean;
Expand Down Expand Up @@ -55,3 +57,17 @@ const fetchMockJest = new FetchMockJest({
});

export default fetchMockJest;

/* eslint-disable @typescript-eslint/no-namespace */
/**
* Export types on the expect object
*/
declare global {
namespace jest {
// Type-narrow expect for FetchMock
interface Expect {
(actual: FetchMock): FetchMockMatchers;
}
}
}
/* eslint-enable @typescript-eslint/no-namespace */
38 changes: 23 additions & 15 deletions packages/jest/src/jest-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ import type {
CallHistoryFilter,
UserRouteConfig,
} from 'fetch-mock';
const methodlessExtensions = {
import {
HumanVerbMethodNames,
HumanVerbs,
RawFetchMockMatchers,
} from './types.js';

const methodlessExtensions: Pick<
RawFetchMockMatchers,
HumanVerbMethodNames<'Fetched'>
> = {
toHaveFetched: (
{ fetchMock }: { fetchMock: FetchMock },
filter: CallHistoryFilter,
Expand Down Expand Up @@ -128,25 +137,24 @@ function scopeExpectationNameToMethod(name: string, humanVerb: string): string {
return name.replace('Fetched', humanVerb);
}

[
'Got:get',
'Posted:post',
'Put:put',
'Deleted:delete',
'FetchedHead:head',
'Patched:patch',
].forEach((verbs) => {
const [humanVerb, method] = verbs.split(':');
const expectMethodNameToMethodMap: {
[humanVerb in Exclude<HumanVerbs, 'Fetched'>]: string;
} = {
Got: 'get',
Posted: 'post',
Put: 'put',
Deleted: 'delete',
FetchedHead: 'head',
Patched: 'patch',
};

const extensions: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: (...args: any[]) => SyncExpectationResult;
} = Object.fromEntries(
Object.entries(expectMethodNameToMethodMap).forEach(([humanVerb, method]) => {
const extensions = Object.fromEntries(
Object.entries(methodlessExtensions).map(([name, func]) => [
scopeExpectationNameToMethod(name, humanVerb),
scopeExpectationFunctionToMethod(func, method),
]),
);
) as Omit<RawFetchMockMatchers, HumanVerbMethodNames<'Fetched'>>;

expect.extend(extensions);
});
91 changes: 91 additions & 0 deletions packages/jest/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { CallHistoryFilter, FetchMock, UserRouteConfig } from 'fetch-mock';
import type { SyncExpectationResult } from 'expect';

export type HumanVerbs =
| 'Got'
| 'Posted'
| 'Put'
| 'Deleted'
| 'FetchedHead'
| 'Patched'
| 'Fetched';

/**
* Verify that a particular call for the HTTP method implied in the function name
* has occurred
*/
export type ToHaveFunc = (
filter: CallHistoryFilter,
options: UserRouteConfig,
) => SyncExpectationResult;

/**
* Verify that a particular Nth call for the HTTP method implied in the function name
* has occurred
*/
export type ToHaveNthFunc = (
n: number,
filter: CallHistoryFilter,
options: UserRouteConfig,
) => SyncExpectationResult;

/**
* Verify that a particular call for the HTTP method implied in the function name
* has been made N times
*/
export type ToHaveTimesFunc = (
times: number,
filter: CallHistoryFilter,
options: UserRouteConfig,
) => SyncExpectationResult;

export type FetchMockMatchers = {
toHaveFetched: ToHaveFunc;
toHaveLastFetched: ToHaveFunc;
toHaveFetchedTimes: ToHaveTimesFunc;
toHaveNthFetched: ToHaveNthFunc;
toHaveGot: ToHaveFunc;
toHaveLastGot: ToHaveFunc;
toHaveGotTimes: ToHaveTimesFunc;
toHaveNthGot: ToHaveNthFunc;
toHavePosted: ToHaveFunc;
toHaveLastPosted: ToHaveFunc;
toHavePostedTimes: ToHaveTimesFunc;
toHaveNthPosted: ToHaveNthFunc;
toHavePut: ToHaveFunc;
toHaveLastPut: ToHaveFunc;
toHavePutTimes: ToHaveTimesFunc;
toHaveNthPut: ToHaveNthFunc;
toHaveDeleted: ToHaveFunc;
toHaveLastDeleted: ToHaveFunc;
toHaveDeletedTimes: ToHaveTimesFunc;
toHaveNthDeleted: ToHaveNthFunc;
toHaveFetchedHead: ToHaveFunc;
toHaveLastFetchedHead: ToHaveFunc;
toHaveFetchedHeadTimes: ToHaveTimesFunc;
toHaveNthFetchedHead: ToHaveNthFunc;
toHavePatched: ToHaveFunc;
toHaveLastPatched: ToHaveFunc;
toHavePatchedTimes: ToHaveTimesFunc;
toHaveNthPatched: ToHaveNthFunc;
};

// types for use doing some intermediate type checking in extensions to make sure things don't get out of sync
/**
* This type allows us to take the Matcher type and creat another one
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type RawMatcher<T extends (...args: any[]) => any> = (
input: { fetchMock: FetchMock },
...args: Parameters<T>
) => ReturnType<T>;

export type RawFetchMockMatchers = {
[k in keyof FetchMockMatchers]: RawMatcher<FetchMockMatchers[k]>;
};

export type HumanVerbMethodNames<M extends HumanVerbs> =
| `toHave${M}`
| `toHaveLast${M}`
| `toHave${M}Times`
| `toHaveNth${M}`;

0 comments on commit 033048a

Please sign in to comment.