/** * @file useSpin.test.ts * @description Tests the Refresh-button hook: the reload runs with no * arguments, a click while it is in flight is dropped, and the spin lasts at * least the floor even when the reload answers at once — also under StrictMode's * mount → unmount → mount, which once left it spinning for good. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi, afterEach } from "vitest"; import { StrictMode } from "react"; import { act, renderHook } from "@testing-library/react"; import { useSpin } from "../useSpin"; afterEach(() => vi.useRealTimers()); describe("useSpin", () => { it("drops a click while the reload is in flight, and spins for the floor after it answers", async () => { vi.useFakeTimers(); let answer!: () => void; const load = vi.fn(() => new Promise((r) => (answer = r))); const { result } = renderHook(() => useSpin(load, 600)); act(() => { void result.current[1](); }); expect(result.current[0]).toBe(true); expect(load).toHaveBeenCalledWith(); act(() => { void result.current[1](); }); expect(load).toHaveBeenCalledTimes(1); // Answered at once: still spinning until the floor, but a new click reloads. await act(async () => answer()); expect(result.current[0]).toBe(true); act(() => { void result.current[1](); }); expect(load).toHaveBeenCalledTimes(2); await act(async () => { answer(); await vi.advanceTimersByTimeAsync(600); }); expect(result.current[0]).toBe(false); }); it("stops spinning under StrictMode's double mount", async () => { vi.useFakeTimers(); const { result } = renderHook(() => useSpin(() => undefined, 600), { wrapper: StrictMode }); await act(async () => { void result.current[1](); await vi.advanceTimersByTimeAsync(600); }); expect(result.current[0]).toBe(false); }); });