Files
fusion/packages/dashboard/app/hooks/__tests__/usePrChecksStream.test.ts
Fusion (runfusion.ai) 76c5142f4f feat(FN-4760): complete Step 3 — add PR checks polling hook
Fusion-Task-Id: FN-4760
Fusion-Task-Lineage: 71db4a53-ee59-49f0-a1f7-232954b86660
2026-05-17 08:49:51 -07:00

121 lines
4.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { usePrChecksStream } from "../usePrChecksStream";
import * as api from "../../api";
vi.mock("../../api", () => ({
fetchPrChecks: vi.fn(),
}));
const mockFetchPrChecks = vi.mocked(api.fetchPrChecks);
async function flush() {
await act(async () => {
await Promise.resolve();
});
}
describe("usePrChecksStream", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
Object.defineProperty(document, "hidden", { configurable: true, value: false });
});
afterEach(() => {
vi.useRealTimers();
});
it("polls at the default interval", async () => {
mockFetchPrChecks.mockResolvedValue({ checks: [], rollup: "unknown", lastCheckedAt: "2026-01-01T00:00:00Z" });
renderHook(() => usePrChecksStream({ taskId: "KB-1", prNumber: 1, enabled: true }));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
await act(async () => {
vi.advanceTimersByTime(15_000);
});
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(2);
});
it("backs off after 3 identical payloads", async () => {
mockFetchPrChecks.mockResolvedValue({
checks: [{ name: "ci", required: true, state: "pending" }],
rollup: "pending",
lastCheckedAt: "2026-01-01T00:00:00Z",
});
renderHook(() => usePrChecksStream({ taskId: "KB-1", prNumber: 1, enabled: true }));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
await act(async () => vi.advanceTimersByTime(15_000));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(2);
await act(async () => vi.advanceTimersByTime(15_000));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(3);
await act(async () => vi.advanceTimersByTime(15_000));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(4);
await act(async () => vi.advanceTimersByTime(59_000));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(4);
await act(async () => vi.advanceTimersByTime(1_000));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(5);
});
it("pauses when hidden and resumes on visibilitychange", async () => {
mockFetchPrChecks.mockResolvedValue({ checks: [], rollup: "unknown", lastCheckedAt: "2026-01-01T00:00:00Z" });
renderHook(() => usePrChecksStream({ taskId: "KB-1", prNumber: 1, enabled: true }));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
Object.defineProperty(document, "hidden", { configurable: true, value: true });
act(() => {
document.dispatchEvent(new Event("visibilitychange"));
vi.advanceTimersByTime(60_000);
});
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
Object.defineProperty(document, "hidden", { configurable: true, value: false });
act(() => {
document.dispatchEvent(new Event("visibilitychange"));
});
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(2);
});
it("refresh triggers off-cycle fetch", async () => {
mockFetchPrChecks.mockResolvedValue({ checks: [], rollup: "unknown", lastCheckedAt: "2026-01-01T00:00:00Z" });
const { result } = renderHook(() => usePrChecksStream({ taskId: "KB-1", prNumber: 1, enabled: true }));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
await act(async () => {
await result.current.refresh();
});
expect(mockFetchPrChecks).toHaveBeenCalledTimes(2);
});
it("stops polling after unmount", async () => {
mockFetchPrChecks.mockResolvedValue({ checks: [], rollup: "unknown", lastCheckedAt: "2026-01-01T00:00:00Z" });
const { unmount } = renderHook(() => usePrChecksStream({ taskId: "KB-1", prNumber: 1, enabled: true }));
await flush();
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
unmount();
await act(async () => vi.advanceTimersByTime(60_000));
expect(mockFetchPrChecks).toHaveBeenCalledTimes(1);
});
});