import { isNotNullish } from '@staticcms/core/lib/util/null.util'; export const createMockRequest = ( status: number, data: { json?: T; text?: string; }, options: { contentType?: string; headers?: Record; } = {}, ): Response => { const { contentType = 'application/json', headers = {} } = options; const finalHeaders = (function () { const data: Record = headers; return { get: (key: string) => { return data[key]; }, set: (key: string, value: string) => { data[key] = value; }, }; })(); finalHeaders.set('Content-Type', contentType); return { status, ok: status < 400, headers: finalHeaders, json: () => Promise.resolve(data.json), text: () => Promise.resolve(data.text), } as Response; }; export type FetchMethod = 'GET' | 'POST' | 'PUT' | 'HEAD'; export type QueryCheckFunc = (query: URLSearchParams) => boolean; export interface RequestData { query: string | true | QueryCheckFunc; response: MockResponse | MockResponseFunc; limit?: number; used?: number; } export type ReplyFunc = (response: MockResponse | MockResponseFunc) => void; export type RepeatFunc = (limit: number) => { reply: ReplyFunc }; export interface MockFetch { baseUrl: string; mocks: Record>>; when: ( method: FetchMethod, url: string, ) => { query: (query: string | true | QueryCheckFunc) => { reply: ReplyFunc; repeat: RepeatFunc }; repeat: RepeatFunc; reply: ReplyFunc; }; reset: () => void; } export interface MockResponse { status: number; json?: T; text?: string; contentType?: string; headers?: Record; } export type MockResponseFunc = (url: string) => MockResponse | Promise>; const mockFetch = (baseUrl: string): MockFetch => { const mockedFetch: MockFetch = { baseUrl, mocks: {}, // eslint-disable-next-line object-shorthand when: function (this: MockFetch, method: FetchMethod, url: string) { const reply = (query: string | true | QueryCheckFunc = '') => (limit?: number) => (response: MockResponse | MockResponseFunc) => { const fullUrl = `${baseUrl}${url}`; if (!(fullUrl in this.mocks)) { this.mocks[fullUrl] = {}; } if (!(method in this.mocks[fullUrl])) { this.mocks[fullUrl][method] = []; } this.mocks[fullUrl][method]?.push({ query, response, limit, }); }; const repeat = (query: string | true | QueryCheckFunc = '') => (limit: number) => { return { reply: reply(query)(limit), }; }; return { query: (query: string | true | QueryCheckFunc) => { return { repeat: repeat(query), reply: reply(query)(), }; }, repeat: repeat(), reply: reply()(), }; }, reset(this: MockFetch) { this.mocks = {}; }, }; global.fetch = jest .fn() .mockImplementation(async (fullUrl: string, { method = 'GET' }: { method: FetchMethod }) => { const [url, ...rest] = fullUrl.split('?'); const query = rest.length > 0 ? rest[0] : ''; const mockResponses = [...(mockedFetch.mocks[url]?.[method] ?? [])]; if (!mockResponses) { return Promise.resolve(undefined); } for (let i = 0; i < mockResponses.length; i++) { const mockResponse = mockResponses[i]; const limit = mockResponse.limit; const used = mockResponse.used ?? 0; if (isNotNullish(limit)) { if (used >= limit) { continue; } } if (isNotNullish(mockResponse.query) && mockResponse.query !== true) { if (typeof mockResponse.query === 'string') { if (mockResponse.query !== query) { continue; } } else if (!mockResponse.query(new URLSearchParams(query))) { continue; } } let responseData = mockResponse.response; if (typeof responseData === 'function') { responseData = await responseData(fullUrl); } const response = createMockRequest( responseData.status, { json: responseData.json, text: responseData.text, }, { contentType: responseData.contentType, headers: responseData.headers, }, ); mockedFetch.mocks[url][method]![i] = { ...mockResponse, used: used + 1, }; return Promise.resolve(response); } return Promise.resolve(undefined); }); return mockedFetch; }; export default mockFetch;