@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.
194 lines (193 loc) • 6.96 kB
JavaScript
import { vitest, beforeEach, expect, test, afterEach, describe, } from "vitest";
import { AsyncResource } from "./AsyncResource.js";
import { sleep } from "../lib/testing.js";
import { getAsyncResource } from "./getAsyncResource.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(() => {
vitest.resetAllMocks();
vitest.useFakeTimers();
loaderCalls = 0;
sleepTime = vitest.fn(() => loadingTime);
loader = vitest.fn(loaderImpl);
errorLoader = vitest.fn(errorLoaderImpl);
});
afterEach(() => {
vitest.runOnlyPendingTimers();
vitest.useRealTimers();
});
describe("calling load()", () => {
test("triggers loader", async () => {
const resource = new AsyncResource(loader);
expect(loader).toHaveBeenCalledTimes(0);
resource.load();
expect(loader).toHaveBeenCalledTimes(1);
});
test("resource uses 'fresh' loader when function execution scope changes", async () => {
const loaded = [];
const scopedFn = async (scopedInputVar) => {
const resource = getAsyncResource(async () => {
await sleep(sleepTime());
return scopedInputVar;
}, []);
resource.refresh();
resource.load();
await vitest.advanceTimersByTimeAsync(loadingTime);
if (resource.value.value.isSet) {
loaded.push(resource.value.value.value);
}
};
await scopedFn("foo");
await scopedFn("bar");
expect(loaded).toEqual(["foo", "bar"]);
});
test("twice does not trigger loader twice", async () => {
const resource = new AsyncResource(loader);
// load
resource.load();
expect(loader).toHaveBeenCalledTimes(1);
// after 100ms load again
await vitest.advanceTimersByTimeAsync(loadingTime / 2);
resource.load();
expect(loader).toHaveBeenCalledTimes(1);
// wait for second load
await vitest.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);
resource.load();
// after 10ms clear
await vitest.advanceTimersByTimeAsync(10);
void resource.refresh();
// 2# load for 20ms
sleepTime.mockReturnValue(20);
resource.load();
// wait for second load
await vitest.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
resource.load();
// while still loading
await vitest.advanceTimersByTimeAsync(loadingTime / 2);
resource.refresh();
// wait for load
await vitest.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
resource.load();
// wait for load
await vitest.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
resource.load();
// wait for load
await vitest.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
resource.load();
// wait for load
await vitest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
// load again for 1000ms
resource.load();
// wait for load
await vitest.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
resource.load();
// wait for load
await vitest.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
resource.load();
// wait for load
await vitest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
expect(resource.error.value.isSet).toBe(false);
});
test("is empty when becoming stale", async () => {
const resource = new AsyncResource(errorLoader);
// load
resource.load();
// wait for load
await vitest.advanceTimersByTimeAsync(loadingTime);
expect(resource.error.value.isSet).toBe(true);
});
test("is updated after loading again when cleared", async () => {
const resource = new AsyncResource(errorLoader);
// load
resource.load();
// wait for load
await vitest.advanceTimersByTimeAsync(loadingTime);
resource.refresh();
errorLoader.mockImplementation(loaderImpl);
// load again (now without error)
resource.load();
// wait for load
await vitest.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);
});
});