import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { renderHook, act } from "@testing-library/react"; import { usePolledData } from "../usePolledData"; // Mock usePageVisibility to always return true (page visible) vi.mock("../usePageVisibility", () => ({ usePageVisibility: () => true, })); describe("usePolledData", () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.runOnlyPendingTimers(); vi.useRealTimers(); }); it("stops polling when pollInterval is undefined", async () => { const fetcher = vi.fn().mockResolvedValue({ value: "test" }); const selector = vi.fn((response: { value: string }) => response.value); renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", }) ); // Initial fetch should happen in useFetchData await act(async () => { vi.runAllTimersAsync(); }); const callCountAfterInitial = fetcher.mock.calls.length; // Reset timer and advance to ensure no more polls vi.clearAllTimers(); fetcher.mockClear(); await act(async () => { vi.advanceTimersByTime(10000); }); // Should not poll since pollInterval is undefined expect(fetcher).not.toHaveBeenCalled(); }); it("implements drift correction: next poll is scheduled based on elapsed time", async () => { const fetcher = vi.fn().mockResolvedValue({ value: "test" }); const selector = vi.fn((response: { value: string }) => response.value); renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", pollInterval: 5000, }) ); // Wait for initial setup await act(async () => { vi.advanceTimersByTime(100); }); const initialCalls = fetcher.mock.calls.length; // Clear for clean test fetcher.mockClear(); // First poll completes after ~0ms, next poll should be in ~5000ms // Advance 4000ms await act(async () => { vi.advanceTimersByTime(4000); }); expect(fetcher).not.toHaveBeenCalled(); // Advance to 5500ms total, poll should have happened await act(async () => { vi.advanceTimersByTime(1500); }); expect(fetcher).toHaveBeenCalled(); }); it("cleans up timers on unmount", async () => { const fetcher = vi.fn().mockResolvedValue({ value: "test" }); const selector = vi.fn((response: { value: string }) => response.value); const { unmount } = renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", pollInterval: 5000, }) ); await act(async () => { vi.advanceTimersByTime(1000); }); const callCount = fetcher.mock.calls.length; // Unmount unmount(); // Advance time and verify no new fetches await act(async () => { vi.advanceTimersByTime(10000); }); // Should not increase beyond initial calls expect(fetcher.mock.calls.length).toBe(callCount); }); it("calls refresh callback to trigger immediate fetch", async () => { const fetcher = vi.fn().mockResolvedValue({ value: "test" }); const selector = vi.fn((response: { value: string }) => response.value); const { result } = renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", pollInterval: 5000, }) ); await act(async () => { vi.advanceTimersByTime(100); }); const initialCalls = fetcher.mock.calls.length; fetcher.mockClear(); // Call refresh await act(async () => { result.current.refresh(); vi.advanceTimersByTime(100); }); expect(fetcher).toHaveBeenCalled(); }); it("returns initial data if provided", () => { const fetcher = vi.fn().mockResolvedValue({ value: "updated" }); const selector = vi.fn(); const { result } = renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", initialData: "initial", pollInterval: 5000, }) ); expect(result.current.data).toBe("initial"); expect(result.current.loading).toBe(true); }); it("returns null when data is undefined and no initialData", () => { const fetcher = vi.fn().mockResolvedValue({ value: "test" }); const selector = vi.fn(); const { result } = renderHook(() => usePolledData({ fetcher, selector, errorMessage: "Failed to load", pollInterval: 5000, }) ); expect(result.current.data).toBeNull(); }); });