import EventEmitter from "events"; import { createHttpMockingServer, sleep } from "@hyper-fetch/testing"; import type { RequestProgressEventType } from "managers"; import { getRequestManagerEvents } from "managers"; import { Client } from "client"; import type { RequestInstance } from "request"; const { resetMocks, startServer, stopServer, mockRequest } = createHttpMockingServer(); describe("RequestManager [ Base ]", () => { const abortKey = "abort-key"; const queryKey = "query-key"; const cacheKey = "cache-key"; const requestId = "some-id"; let client = new Client({ url: "shared-base-url" }); let events = getRequestManagerEvents(new EventEmitter()); beforeAll(() => { startServer(); }); beforeEach(() => { resetMocks(); vi.resetAllMocks(); client = new Client({ url: "shared-base-url" }); events = getRequestManagerEvents(new EventEmitter()); }); afterAll(() => { stopServer(); }); describe("When app manager is initialized", () => { it("should allow to add abort controller", async () => { expect(client.requestManager.getAbortController(abortKey, requestId)).toBeUndefined(); client.requestManager.addAbortController(abortKey, requestId); expect(client.requestManager.getAbortController(abortKey, requestId)).toBeDefined(); }); it("should allow to remove abort controller", async () => { client.requestManager.addAbortController(abortKey, requestId); client.requestManager.removeAbortController(abortKey, requestId); expect(client.requestManager.getAbortController(abortKey, requestId)).toBeUndefined(); }); it("should not throw when removing non-existing controller", async () => { expect(() => client.requestManager.removeAbortController(abortKey, requestId)).not.toThrow(); }); }); describe("When request is being aborted", () => { it("should remove it from dispatcher's queue storage", async () => { const spy = vi.fn(); const request = client.createRequest()({ endpoint: "shared-base-endpoint" }); mockRequest(request); client.onResponse((response) => { spy(); return response; }); client.fetchDispatcher.add(request); await sleep(5); client.requestManager.abortAll(); await sleep(1); const queue = client.fetchDispatcher.getQueue(request.queryKey); expect(queue.requests).toHaveLength(0); expect(spy).toHaveBeenCalledTimes(1); }); }); describe("When events get emitted", () => { it("should propagate the loading events", async () => { const spy = vi.fn(); const spyQueue = vi.fn(); const spyById = vi.fn(); events.onLoadingByCache(cacheKey, spy); events.onLoadingByQueue(queryKey, spyQueue); events.onLoadingById(requestId, spyById); const data = { request: client.createRequest()({ endpoint: "shared-base-endpoint", cacheKey, queryKey }), requestId, loading: false, isRetry: false, isOffline: false, }; events.emitLoading(data); expect(spy).toHaveBeenCalledTimes(1); expect(spyQueue).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should propagate the request start events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onRequestStartByQueue(queryKey, spy); events.onRequestStartById(requestId, spyById); const request = client.createRequest()({ endpoint: "shared-base-endpoint", cacheKey, queryKey }); events.emitRequestStart({ request, requestId }); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should propagate the response start events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onResponseStartByQueue(queryKey, spy); events.onResponseStartById(requestId, spyById); const request = client.createRequest()({ endpoint: "shared-base-endpoint", cacheKey, queryKey }); events.emitResponseStart({ request, requestId }); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should propagate the upload events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onUploadProgressByQueue(queryKey, spy); events.onUploadProgressById(requestId, spyById); const request = client.createRequest()({ endpoint: "shared-base-endpoint", cacheKey, queryKey }); const details: RequestProgressEventType = { requestId, request, progress: 0, timeLeft: 0, sizeLeft: 0, total: 0, loaded: 0, startTimestamp: 0, }; events.emitUploadProgress(details); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should propagate the download events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onDownloadProgressByQueue(queryKey, spy); events.onDownloadProgressById(requestId, spyById); const request = client.createRequest()({ endpoint: "shared-base-endpoint", cacheKey, queryKey }); const details: RequestProgressEventType = { requestId, request, progress: 0, timeLeft: 0, sizeLeft: 0, total: 0, loaded: 0, startTimestamp: 0, }; events.emitDownloadProgress(details); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); }); describe("When events get emitted", () => { it("should propagate the response events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onResponseByCache(cacheKey, spy); events.onResponseById(requestId, spyById); events.emitResponse({ request: client.createRequest()({ endpoint: "shared-base-endpoint", queryKey, cacheKey }), requestId, response: { data: null, error: null, status: 200, success: true, extra: null, requestTimestamp: +new Date(), responseTimestamp: +new Date(), }, details: { retries: 0, isCanceled: false, isOffline: false, willRetry: false, requestTimestamp: +new Date(), responseTimestamp: +new Date(), triggerTimestamp: +new Date(), addedTimestamp: +new Date(), }, }); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should propagate the remove events", async () => { const spy = vi.fn(); const spyById = vi.fn(); events.onRemoveByQueue(queryKey, spy); events.onRemoveById(requestId, spyById); const request = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey, cacheKey }); events.emitRemove({ requestId, request, resolved: true, }); expect(spy).toHaveBeenCalledTimes(1); expect(spyById).toHaveBeenCalledTimes(1); }); it("should not throw on not existing controller", async () => { client.requestManager.addAbortController("test2", "test2"); const emptyAbortController = client.requestManager.getAbortController("test", "test"); const emptyAbortController2 = client.requestManager.getAbortController("test2", "test"); expect(emptyAbortController).not.toBeDefined(); expect(emptyAbortController2).not.toBeDefined(); expect(() => client.requestManager.useAbortController("test", "test")).not.toThrow(); expect(() => client.requestManager.useAbortController("test2", "test")).not.toThrow(); }); }); });