@mittwald/react-use-promise
Version:
Simple and declarative use of Promises in your React components. Observe their state and refresh them in various advanced ways.
175 lines (174 loc) • 6.2 kB
JavaScript
import { beforeEach, jest } from "@jest/globals";
import { AsyncResource } from "./AsyncResource.js";
import { sleep } from "../lib/testing.js";
let loaderCalls = 0;
let sleepTime;
const loadingTime = 10000;
let loader;
const loaderImpl = async () => {
loaderCalls++;
await sleep(sleepTime());
return `#${loaderCalls} call`;
};
let errorLoader;
const errorLoaderImpl = async () => {
loaderCalls++;
await sleep(sleepTime());
throw new Error("Whoops");
};
beforeEach(() => {
jest.resetAllMocks();
jest.useFakeTimers();
loaderCalls = 0;
sleepTime = jest.fn(() => loadingTime);
loader = jest.fn(loaderImpl);
errorLoader = jest.fn(errorLoaderImpl);
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
describe("calling load()", () => {
test("triggers loader", async () => {
const resource = new AsyncResource(loader);
expect(loader).toHaveBeenCalledTimes(0);
void resource.load();
expect(loader).toHaveBeenCalledTimes(1);
});
test("twice does not trigger loader twice", async () => {
const resource = new AsyncResource(loader);
// load
void resource.load();
expect(loader).toHaveBeenCalledTimes(1);
// after 100ms load again
await jest.advanceTimersByTimeAsync(loadingTime / 2);
void resource.load();
expect(loader).toHaveBeenCalledTimes(1);
// wait for second load
await jest.advanceTimersByTimeAsync(loadingTime);
expect(loader).toHaveBeenCalledTimes(1);
});
test("can be superseded by another load() call if cleared in between", async () => {
const resource = new AsyncResource(loader);
// #1 load for 50ms
sleepTime.mockReturnValue(50);
void resource.load();
// after 10ms clear
await jest.advanceTimersByTimeAsync(10);
void resource.refresh();
// 2# load for 20ms
sleepTime.mockReturnValue(20);
void resource.load();
// wait for second load
await jest.advanceTimersByTimeAsync(20);
if (!resource.value.value.isSet) {
throw new Error("Should not happen");
}
expect(resource.value.value.value).toBe("#2 call");
});
test("will be aborted if cleared during loading", async () => {
const resource = new AsyncResource(loader);
// load
void resource.load();
// while still loading
await jest.advanceTimersByTimeAsync(loadingTime / 2);
resource.refresh();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
expect(resource.value.value.isSet).toBe(false);
});
});
describe(".value", () => {
test("is empty on fresh resources", () => {
const resource = new AsyncResource(loader);
expect(resource.value.value.isSet).toBe(false);
});
test("is set after loader is done", async () => {
const resource = new AsyncResource(loader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
if (!resource.value.value.isSet) {
throw new Error("Should not happen");
}
expect(resource.value.value.value).toBe("#1 call");
});
test("is empty after clear()", async () => {
const resource = new AsyncResource(loader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
expect(resource.value.value.isSet).toBe(false);
});
test("is updated after loading again when cleared", async () => {
const resource = new AsyncResource(loader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
// load again for 1000ms
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
if (!resource.value.value.isSet) {
throw new Error("Should not happen");
}
expect(resource.value.value.value).toBe("#2 call");
});
});
describe(".error", () => {
test("is empty on fresh resources", () => {
const resource = new AsyncResource(errorLoader);
expect(resource.error.value.isSet).toBe(false);
});
test("is set after loader throws error", async () => {
const resource = new AsyncResource(errorLoader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
if (!resource.error.value.isSet) {
throw new Error("Should not happen");
}
expect(resource.error.value.value).toBeInstanceOf(Error);
});
test("is empty after clear()", async () => {
const resource = new AsyncResource(errorLoader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
expect(resource.error.value.isSet).toBe(false);
});
test("is empty when becoming stale", async () => {
const resource = new AsyncResource(errorLoader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
expect(resource.error.value.isSet).toBe(true);
});
test("is updated after loading again when cleared", async () => {
const resource = new AsyncResource(errorLoader);
// load
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
errorLoader.mockImplementation(loaderImpl);
// load again (now without error)
void resource.load();
// wait for load
await jest.advanceTimersByTimeAsync(loadingTime);
if (!resource.value.value.isSet) {
throw new Error("Should not happen");
}
expect(resource.value.value.value).toBe("#2 call");
expect(resource.error.value.isSet).toBe(false);
});
});