static-cms/packages/core/test/mockFetch.ts
Daniel Lautzenheiser 799c7e6936
feat: v4.0.0 (#1016)
Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
Co-authored-by: Mathieu COSYNS <64072917+Mathieu-COSYNS@users.noreply.github.com>
2024-01-03 15:14:09 -05:00

193 lines
4.8 KiB
TypeScript

import { isNotNullish } from '@staticcms/core/lib/util/null.util';
export const createMockRequest = <T>(
status: number,
data: {
json?: T;
text?: string;
},
options: {
contentType?: string;
headers?: Record<string, string>;
} = {},
): Response => {
const { contentType = 'application/json', headers = {} } = options;
const finalHeaders = (function () {
const data: Record<string, string> = 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<unknown> | MockResponseFunc<unknown>;
limit?: number;
used?: number;
}
export type ReplyFunc = <T>(response: MockResponse<T> | MockResponseFunc<T>) => void;
export type RepeatFunc = (limit: number) => { reply: ReplyFunc };
export interface MockFetch {
baseUrl: string;
mocks: Record<string, Partial<Record<FetchMethod, RequestData[]>>>;
when: (
method: FetchMethod,
url: string,
) => {
query: (query: string | true | QueryCheckFunc) => { reply: ReplyFunc; repeat: RepeatFunc };
repeat: RepeatFunc;
reply: ReplyFunc;
};
reset: () => void;
}
export interface MockResponse<T> {
status: number;
json?: T;
text?: string;
contentType?: string;
headers?: Record<string, string>;
}
export type MockResponseFunc<T> = (url: string) => MockResponse<T> | Promise<MockResponse<T>>;
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) =>
<T>(response: MockResponse<T> | MockResponseFunc<T>) => {
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;