97 lines
3.6 KiB
TypeScript
97 lines
3.6 KiB
TypeScript
import { describe, expect, test, vi } from "vitest";
|
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
import { useFetchState } from "../../../hooks/shared/useFetchState.ts";
|
|
import { deferred } from "../../fetch.ts";
|
|
|
|
describe("useFetchState", () => {
|
|
test("loads once enabled, and only once per key", async () => {
|
|
const load = vi.fn(() => Promise.resolve(1));
|
|
const { result, rerender } = renderHook(
|
|
({ enabled }) => useFetchState("a", load, { enabled }),
|
|
{ initialProps: { enabled: false } },
|
|
);
|
|
expect(load).not.toHaveBeenCalled();
|
|
expect(result.current.state).toEqual({ status: "loading" });
|
|
rerender({ enabled: true });
|
|
await waitFor(() =>
|
|
expect(result.current.state).toEqual({ status: "loaded", data: 1 })
|
|
);
|
|
rerender({ enabled: false });
|
|
rerender({ enabled: true });
|
|
expect(load).toHaveBeenCalledTimes(1);
|
|
expect(result.current.state).toEqual({ status: "loaded", data: 1 });
|
|
});
|
|
|
|
test("turning it off doesn't stop a load under way", async () => {
|
|
const d = deferred<number>();
|
|
const { result, rerender } = renderHook(
|
|
({ enabled }) => useFetchState("a", () => d.promise, { enabled }),
|
|
{ initialProps: { enabled: true } },
|
|
);
|
|
rerender({ enabled: false });
|
|
act(() => d.resolve(2));
|
|
await waitFor(() =>
|
|
expect(result.current.state).toEqual({ status: "loaded", data: 2 })
|
|
);
|
|
});
|
|
|
|
test("a new key starts over and aborts the last key's load", async () => {
|
|
const signals: AbortSignal[] = [];
|
|
const load = vi.fn((signal: AbortSignal) => {
|
|
signals.push(signal);
|
|
return signals.length === 1
|
|
? new Promise<string>(() => {})
|
|
: Promise.resolve("b");
|
|
});
|
|
const { result, rerender } = renderHook(
|
|
({ key }) => useFetchState(key, load),
|
|
{ initialProps: { key: "a" } },
|
|
);
|
|
rerender({ key: "b" });
|
|
expect(signals[0].aborted).toBe(true);
|
|
await waitFor(() =>
|
|
expect(result.current.state).toEqual({ status: "loaded", data: "b" })
|
|
);
|
|
});
|
|
|
|
test("an undefined key loads nothing", () => {
|
|
const load = vi.fn(() => Promise.resolve(1));
|
|
renderHook(() => useFetchState(undefined, load));
|
|
expect(load).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("a failed load shows its message", async () => {
|
|
const { result } = renderHook(() =>
|
|
useFetchState("a", () => Promise.reject(new Error("Nope")))
|
|
);
|
|
await waitFor(() =>
|
|
expect(result.current.state).toEqual({ status: "error", error: "Nope" })
|
|
);
|
|
});
|
|
|
|
test("setData edits the loaded data, and nothing before", async () => {
|
|
const d = deferred<number[]>();
|
|
const { result } = renderHook(() => useFetchState("a", () => d.promise));
|
|
act(() => result.current.setData((xs) => [...xs, 9]));
|
|
expect(result.current.state).toEqual({ status: "loading" });
|
|
act(() => d.resolve([1]));
|
|
await waitFor(() => expect(result.current.state.status).toBe("loaded"));
|
|
act(() => result.current.setData((xs) => [...xs, 2]));
|
|
expect(result.current.state).toEqual({ status: "loaded", data: [1, 2] });
|
|
});
|
|
|
|
test("an initial value starts loaded and loads nothing, for its key only", async () => {
|
|
const load = vi.fn(() => Promise.resolve(2));
|
|
const { result, rerender } = renderHook(
|
|
({ key, initial }) => useFetchState(key, load, { initial }),
|
|
{ initialProps: { key: "a", initial: 1 as number | undefined } },
|
|
);
|
|
expect(result.current.state).toEqual({ status: "loaded", data: 1 });
|
|
expect(load).not.toHaveBeenCalled();
|
|
rerender({ key: "b", initial: undefined });
|
|
await waitFor(() =>
|
|
expect(result.current.state).toEqual({ status: "loaded", data: 2 })
|
|
);
|
|
});
|
|
});
|