- Renamed usePolledIntervalCheck to usePolledData for clarity - Updated hook to properly manage interval cleanup on unmount - Added comprehensive test suite covering normal operation, error handling, and cleanup - Updated documentation to reflect new hook name - Updated Tasks.md to track progress Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
184 lines
4.5 KiB
TypeScript
184 lines
4.5 KiB
TypeScript
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();
|
|
});
|
|
});
|