Files
BanGUI/frontend/src/hooks/__tests__/usePolledData.test.ts
Lukas 3bd2a71367 Refactor usePolledData hook and add comprehensive tests
- 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>
2026-04-30 20:24:47 +02:00

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();
});
});