@hyper-fetch/core
Version:
Cache, Queue and Persist your requests no matter if you are online or offline!
463 lines (376 loc) • 16.9 kB
text/typescript
import { waitFor } from "@testing-library/dom";
import { createHttpMockingServer, sleep } from "@hyper-fetch/testing";
import { Dispatcher } from "dispatcher";
import { createDispatcher, createAdapter } from "../../utils";
import { Client } from "client";
import { Plugin } from "plugin";
const { resetMocks, startServer, stopServer, mockRequest } = createHttpMockingServer();
describe("Dispatcher [ Queue ]", () => {
const adapterSpy = vi.fn();
let adapter = createAdapter({ callback: adapterSpy });
let client = new Client({ url: "shared-base-url" }).setAdapter(adapter);
let dispatcher = createDispatcher(client);
beforeAll(() => {
startServer();
});
beforeEach(() => {
vi.resetAllMocks();
resetMocks();
client.clear();
adapter = createAdapter({ callback: adapterSpy });
client = new Client({ url: "shared-base-url" }).setAdapter(adapter);
dispatcher = createDispatcher(client);
});
afterAll(() => {
stopServer();
});
describe("When using dispatcher add method", () => {
it("should add request to the dispatcher storage and trigger it", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
const loadingSpy = vi.fn();
client.requestManager.events.onLoadingByQueue(request.queryKey, loadingSpy);
const requestId = dispatcher.add(request);
await waitFor(() => {
expect(requestId).toBeString();
// expect(adapterSpy).toHaveBeenCalledTimes(1);
expect(loadingSpy).toHaveBeenCalledTimes(1);
expect(dispatcher.getIsActiveQueue(request.queryKey)).toBeTrue();
expect(dispatcher.getQueueRequestCount(request.queryKey)).toBe(1);
});
});
it("should add running request and delete it once data is fetched", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request, { delay: 1 });
dispatcher.add(request);
expect(dispatcher.getAllRunningRequests()).toHaveLength(1);
await waitFor(() => {
expect(dispatcher.getAllRunningRequests()).toHaveLength(0);
expect(dispatcher.getQueue(request.queryKey).requests).toHaveLength(0);
});
});
it("should deduplicate requests and return ongoing requestId", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", deduplicate: true });
mockRequest(request);
const spy = vi.spyOn(dispatcher, "performRequest");
const requestId = dispatcher.add(request);
const deduplicatedId = dispatcher.add(request);
expect(requestId).toBe(deduplicatedId);
expect(spy).toHaveBeenCalledTimes(1);
expect(dispatcher.getAllRunningRequests()).toHaveLength(1);
});
it("should queue the queued request", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: true });
mockRequest(request);
const spy = vi.spyOn(dispatcher, "flushQueue");
dispatcher.add(request);
dispatcher.add(request);
expect(spy).toHaveBeenCalledTimes(2);
expect(dispatcher.getAllRunningRequests()).toHaveLength(1);
});
it("should send all concurrent requests", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: false });
mockRequest(request);
const spy = vi.spyOn(dispatcher, "performRequest");
dispatcher.add(request);
dispatcher.add(request);
expect(spy).toHaveBeenCalledTimes(2);
expect(dispatcher.getAllRunningRequests()).toHaveLength(2);
});
it("should send all concurrent requests without any duplicates", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: false });
mockRequest(request, { delay: 40 });
const spy1 = vi.spyOn(dispatcher, "performRequest");
const spy2 = vi.spyOn(client.requestManager.events, "emitResponse");
setTimeout(() => {
dispatcher.add(request);
}, 0);
setTimeout(() => {
dispatcher.add(request);
}, 10);
setTimeout(() => {
dispatcher.add(request);
}, 20);
await sleep(100);
expect(spy1).toHaveBeenCalledTimes(3);
expect(spy2).toHaveBeenCalledTimes(3);
});
});
describe("When using dispatcher performRequest method", () => {
it("should trigger fetch adapter", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
const storageElement = dispatcher.createStorageItem(request);
dispatcher.performRequest(storageElement);
expect(spy).toHaveBeenCalledTimes(1);
});
it("should not trigger fetch adapter when app is offline", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
client.appManager.setOnline(false);
const spy = vi.spyOn(client.adapter, "fetch");
const storageElement = dispatcher.createStorageItem(request);
await dispatcher.performRequest(storageElement);
expect(spy).toHaveBeenCalledTimes(0);
});
it("should trigger all requests when going back from offline", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
client.appManager.setOnline(false);
dispatcher.add(request.setQueryKey("test1"));
dispatcher.add(request.setQueryKey("test2"));
dispatcher.add(request.setQueryKey("test3"));
await sleep(5);
expect(spy).toHaveBeenCalledTimes(0);
client.appManager.setOnline(true);
await sleep(5);
expect(spy).toHaveBeenCalledTimes(3);
});
it("should not trigger one storage element two times at the same time", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
const storageElement = dispatcher.createStorageItem(request);
dispatcher.performRequest(storageElement);
dispatcher.performRequest(storageElement);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe("When retrying requests", () => {
it("should retry failed request", async () => {
const spy = vi.fn();
const spyDelete = vi.fn();
const plugin = new Plugin({ name: "delete" }).onDispatcherItemDeleted(spyDelete);
const customClient = new Client({
url: "shared-base-url",
fetchDispatcher: () => new Dispatcher(),
}).addPlugin(plugin);
const request = customClient.createRequest()({ endpoint: "shared-base-endpoint", retry: 1, retryTime: 0 });
mockRequest(request, { status: 400, delay: 0 });
customClient.onRequest((cmd) => {
spy();
return cmd;
});
customClient.fetchDispatcher.add(request);
await waitFor(() => {
expect(spyDelete).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(2);
});
});
it("should retry multiple times", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", retry: 2, retryTime: 0 });
mockRequest(request, { status: 400, delay: 0 });
const spy = vi.spyOn(client.adapter, "fetch");
dispatcher.add(request);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(3);
});
});
it("should not retry failed request when request 'retry' option is disabled", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", retry: 0 });
mockRequest(request, { status: 400, delay: 0 });
const spy = vi.spyOn(client.adapter, "fetch");
dispatcher.add(request);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
it("should not retry failed request in offline mode", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", retry: 0 });
mockRequest(request, { status: 400, delay: 5 });
const spy = vi.spyOn(client.adapter, "fetch");
dispatcher.add(request);
await sleep(5);
client.appManager.setOnline(false);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(1);
});
});
});
describe("When flushing requests", () => {
it("should flush all queues request", async () => {
const firstRequest = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "1" });
const secondRequest = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "2" });
mockRequest(firstRequest);
mockRequest(secondRequest);
const spy = vi.spyOn(client.adapter, "fetch");
client.appManager.setOnline(false);
dispatcher.add(firstRequest);
dispatcher.add(firstRequest);
dispatcher.add(secondRequest);
dispatcher.add(secondRequest);
await sleep(5);
expect(spy).toHaveBeenCalledTimes(0);
client.appManager.setOnline(true);
dispatcher.flush();
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(4);
});
});
it("should not trigger flush methods when queue is empty", async () => {
const spy = vi.spyOn(dispatcher, "performRequest");
dispatcher.flushQueue("fake-queue");
expect(spy).not.toHaveBeenCalled();
});
it("should not trigger flushQueue when queue is processing", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: true });
mockRequest(request, { delay: 1 });
const spy = vi.spyOn(dispatcher, "performRequest");
dispatcher.add(request);
dispatcher.add(request);
dispatcher.flushQueue(request.queryKey);
dispatcher.flushQueue(request.queryKey);
dispatcher.flushQueue(request.queryKey);
expect(spy).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(2);
});
});
it("should not trigger flushQueue when having ongoing request", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: true });
mockRequest(request, { delay: 1 });
const spy = vi.spyOn(dispatcher, "performRequest");
const jsonRequest = dispatcher.createStorageItem(request);
dispatcher.addQueueItem(request.queryKey, jsonRequest);
dispatcher.addRunningRequest(request.queryKey, jsonRequest.requestId, request);
dispatcher.flushQueue(request.queryKey);
expect(spy).toHaveBeenCalledTimes(0);
});
it("should not trigger flushQueue on stopped requests", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queued: true });
mockRequest(request, { delay: 1 });
const spy = vi.spyOn(client.adapter, "fetch");
const jsonRequest = dispatcher.createStorageItem(request);
dispatcher.addQueueItem(request.queryKey, jsonRequest);
dispatcher.stopRequest(request.queryKey, jsonRequest.requestId);
dispatcher.flushQueue(request.queryKey);
expect(spy).toHaveBeenCalledTimes(0);
});
it("should not duplicate ongoing requests using flushQueue", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request, { delay: 30 });
const spy = vi.spyOn(dispatcher, "performRequest");
dispatcher.add(request);
dispatcher.add(request);
await sleep(5);
dispatcher.add(request);
await sleep(5);
expect(spy).toHaveBeenCalledTimes(3);
});
it("should not throw on missing items", async () => {
dispatcher.getQueue = () => null as any;
dispatcher.getQueuesKeys = () => ["test1"];
await expect(dispatcher.flush()).resolves.not.toThrow();
});
it("should not throw on missing requests", async () => {
expect(() => dispatcher.stopRequest("test1", "test2")).not.toThrow();
});
});
describe("When starting and stopping queue", () => {
it("should stop queue from being send", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "1" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
dispatcher.stop(request.queryKey);
dispatcher.add(request);
dispatcher.add(request);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(0);
expect(dispatcher.getIsActiveQueue(request.queryKey)).toBeFalse();
});
});
it("should stop queue and cancel ongoing requests", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "1" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
const firstSpy = vi.fn();
const secondSpy = vi.fn();
const firstRequestId = dispatcher.add(request);
const secondRequestId = dispatcher.add(request);
client.requestManager.events.onAbortById(firstRequestId, firstSpy);
client.requestManager.events.onAbortById(secondRequestId, secondSpy);
await sleep(1);
dispatcher.stop(request.queryKey);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(2);
expect(firstSpy).toHaveBeenCalledTimes(1);
expect(secondSpy).toHaveBeenCalledTimes(1);
expect(dispatcher.getIsActiveQueue(request.queryKey)).toBeFalse();
});
});
it("should start previously stopped queue", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "1" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
dispatcher.stop(request.queryKey);
dispatcher.add(request);
dispatcher.add(request);
dispatcher.start(request.queryKey);
expect(dispatcher.getIsActiveQueue(request.queryKey)).toBeTrue();
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(2);
});
});
it("should pause queue and finish ongoing requests", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", queryKey: "1" });
mockRequest(request);
const spy = vi.spyOn(client.adapter, "fetch");
const firstSpy = vi.fn();
const secondSpy = vi.fn();
const firstRequestId = dispatcher.add(request);
const secondRequestId = dispatcher.add(request);
client.requestManager.events.onAbortById(firstRequestId, firstSpy);
client.requestManager.events.onAbortById(secondRequestId, secondSpy);
await sleep(1);
dispatcher.pause(request.queryKey);
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(2);
expect(firstSpy).toHaveBeenCalledTimes(0);
expect(secondSpy).toHaveBeenCalledTimes(0);
expect(dispatcher.getIsActiveQueue(request.queryKey)).toBeFalse();
});
});
it("should not remove requests from queue storage on stop", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
dispatcher.add(request);
await sleep(2);
dispatcher.stop(request.queryKey);
expect(dispatcher.getQueue(request.queryKey).requests).toHaveLength(1);
});
it("should not remove request from queue storage on stopRequest", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
const requestId = dispatcher.add(request);
await sleep(2);
dispatcher.stopRequest(request.queryKey, requestId);
expect(dispatcher.getQueue(request.queryKey).requests).toHaveLength(1);
});
});
describe("When request is canceled", () => {
it("should remove request from queue", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint" });
mockRequest(request);
dispatcher.add(request);
await sleep(1);
client.requestManager.abortAll();
await waitFor(() => {
expect(dispatcher.getQueue(request.queryKey).requests).toHaveLength(0);
});
});
});
describe("When request is not offline", () => {
it("should remove request from queue", async () => {
const request = client.createRequest()({ endpoint: "shared-base-endpoint", offline: false });
mockRequest(request, { status: 400 });
dispatcher.add(request);
await sleep(1);
client.appManager.setOnline(false);
await waitFor(() => {
expect(dispatcher.getQueue(request.queryKey).requests).toHaveLength(0);
});
});
});
});