import type { HttpResponseInit, JsonBodyType } from "msw"; import { HttpResponse, HttpMethods, http, delay as wait, } from "msw"; import type { TContentTypes } from "./contentTypes"; import { contentTypes } from "./contentTypes"; import type { THttpHandlerWithExtendedInfo } from "./httpHandler"; import { ApiClient } from "../../plugins/apiClient"; export type { TContentTypes, TDecoratorArg, TProps, TResponseDataArg, }; type TMock = TBodyInt | FormData | JsonBodyType; type TDecoratorArg = { res: Response; req: Request; mock: TMock; type: TContentTypes; }; type TProps = { description?: string; method?: HttpMethods; mock?: TMock; body?: BodyInit | null; endpoint: string; type?: TContentTypes; delay?: number; tags?: string[]; getDecorator?: (data: TDecoratorArg) => Promise; getResponseData?: (data: TResponseDataArg) => Promise<{ mock: TMock; init: HttpResponseInit; }>; }; type TBodyInt = string | null | undefined; type TResponseDataArg = { req: Request; mock: TMock; type: TContentTypes; }; const defaultInit: HttpResponseInit = { status: 200, statusText: "OK", headers: new Headers(), }; const defaultDecorator: TProps["getDecorator"] = async function(arg) { return arg.res; }; const defaultResponseData: TProps["getResponseData"] = async function(arg) { return { mock: arg.mock, init: defaultInit, }; }; /** * Получает результат ответа MSW * @param props{Object=} * @param props.description{String=} описание запроса * @param props.method{String=} метод запроса * @param props.mock{Object=} данные ответа на запрос * @param props.endpoint{String} точка запроса * @param props.type{String=} тип ответа * @param props.status{Number=} статус ответа * @param props.delay{Number=} дополнительное время ожидания ответа * @param props.headers{Object=} заголовки ответа * @param props.body{*} пример тела запроса * @param props.tags{String[]} теги * @param url{String=} адрес API * @returns {THttpHandlerWithExtendedInfo} * @example * import { getApiMock } from "@delement/ui/utils/msw"; * * const handler = getApiMock({ * endpoint: "/users", * mock: [{ id: 1, name: "Alex" }], * }); */ const getApiMock = ( props: TProps, url: string | undefined = ApiClient.defaults.url ): THttpHandlerWithExtendedInfo => { const { getDecorator = defaultDecorator, getResponseData = defaultResponseData, method = HttpMethods.GET, mock, type = contentTypes.json, endpoint, body, description = "", delay = 0, tags = [ "Endpoints" ], } = props || {}; if (!url?.startsWith("http")) { throw new Error(`[getApiMock] Prop "url" needs to be a valid URL with protocol e.g. "http://"`); } const path = ApiClient.getEndpoint(url, endpoint); const getResponse = (mock: unknown, init: HttpResponseInit): Response => { [ "Access-Control-Allow-Origin", "Access-Control-Allow-Methods", "Access-Control-Allow-Headers", ].forEach((header) => { if (init.headers instanceof Headers) { init.headers.set(header, "*"); } else if (typeof init.headers === "object") { (init.headers as Record)[header] = "*"; } }); switch (true) { case type === contentTypes.json: return HttpResponse.json(mock as JsonBodyType, init); case type === contentTypes.plain: return HttpResponse.text(mock as TBodyInt, init); case type === contentTypes.binary: return HttpResponse.arrayBuffer(mock as ArrayBuffer, init); case type === contentTypes.xml: return HttpResponse.xml(mock as TBodyInt, init); case type === contentTypes.html: return HttpResponse.html(mock as TBodyInt, init); case type === contentTypes.formData: return HttpResponse.formData(mock as FormData, init); default: throw new Error(`[getResponseMock] Can't parse Content-Type as "${type}". Only the listed ones are available: "${Object.keys(contentTypes).join(", ")}"`); } }; async function resolver({ request }: { request: Request; }): Promise { // delay if (delay > 0) { await wait(delay); } // mock and init after user decoration const { mock: userMock, init: userInit, } = await getResponseData({ req: request, mock, type, }); // resp const response = getResponse(userMock, { ...defaultInit, ...userInit, }); // decorator return await getDecorator({ req: request, res: response, mock: userMock, type, }); } const getHTTPHandler = () => { switch (method) { case HttpMethods.POST: return http.post(path, resolver); case HttpMethods.PUT: return http.put(path, resolver); case HttpMethods.PATCH: return http.patch(path, resolver); case HttpMethods.DELETE: return http.delete(path, resolver); case HttpMethods.OPTIONS: return http.options(path, resolver); default: return http.get(path, resolver); } }; // create handler const mswHandler = getHTTPHandler(); Object.assign(mswHandler.info, { mock, type, description, tags, body, }); return mswHandler as unknown as THttpHandlerWithExtendedInfo; }; getApiMock.requestMethods = HttpMethods; getApiMock.contentTypes = contentTypes; export { getApiMock, };