feat(sp6): Inbox page + useInboxLanes hook
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, renderHook, waitFor } from "@testing-library/react";
|
||||
import { useInboxLanes } from "./useInboxLanes";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.unstubAllGlobals();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("useInboxLanes", () => {
|
||||
it("loads lanes on mount", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
rejected: [],
|
||||
candidates: [],
|
||||
unmatched: [],
|
||||
done_today: [],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(() => useInboxLanes());
|
||||
await waitFor(() => expect(result.current.loading).toBe(false));
|
||||
expect(result.current.lanes).toEqual({
|
||||
rejected: [],
|
||||
candidates: [],
|
||||
unmatched: [],
|
||||
done_today: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes an error when fetch fails", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({ ok: false, status: 500, json: async () => ({}) }),
|
||||
);
|
||||
const { result } = renderHook(() => useInboxLanes());
|
||||
await waitFor(() => expect(result.current.error).not.toBeNull());
|
||||
expect(result.current.error?.message).toMatch(/500/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchInboxLanes, type InboxLanes } from "@/lib/inbox-api";
|
||||
|
||||
/** Default poll interval. Tail-integration (T19) will replace this. */
|
||||
const POLL_INTERVAL_MS = 5_000;
|
||||
|
||||
export function useInboxLanes() {
|
||||
const [lanes, setLanes] = useState<InboxLanes>({
|
||||
rejected: [],
|
||||
candidates: [],
|
||||
unmatched: [],
|
||||
done_today: [],
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
const refetch = useCallback(async () => {
|
||||
try {
|
||||
const next = await fetchInboxLanes();
|
||||
setLanes(next);
|
||||
setError(null);
|
||||
} catch (e) {
|
||||
setError(e as Error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void refetch();
|
||||
pollRef.current = setInterval(() => {
|
||||
void refetch();
|
||||
}, POLL_INTERVAL_MS);
|
||||
return () => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
return { lanes, loading, error, refetch };
|
||||
}
|
||||
Reference in New Issue
Block a user