import { act, renderHook, waitFor } from "@testing-library/react"; import { http, HttpResponse } from "msw"; import { describe, expect, it, vi } from "vitest"; import { useBrokerageAccounts } from "@/entities/brokerage-account"; import { configureAuthClient } from "@/shared/api"; import { createQueryWrapper } from "@/test/query"; import { resource } from "@/test/resource"; import { server } from "@/test/server"; import { useScreenSync } from "../index"; const TRANSPORT_ATTEMPTS = 3; const TRANSPORT_RECOVERY_MS = 3000; function auth() { configureAuthClient({ accessToken: () => "synthetic", clear: vi.fn(), refresh: () => Promise.resolve(true), }); } describe("read-only screen synchronization", () => { it("retries the resource with GET without requesting source observation", async () => { auth(); let gets = 0; const syncModes: (null | string)[] = []; server.use( http.get("*/api/v1/brokerage-accounts", ({ request }) => { gets += 1; syncModes.push(new URL(request.url).searchParams.get("sync")); // The transport retries a transient read twice (1 s, 2 s) on its own. return gets <= TRANSPORT_ATTEMPTS ? HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 }) : HttpResponse.json(resource([])); }), ); vi.useFakeTimers(); const { result } = renderHook( () => { const accounts = useBrokerageAccounts(); return { accounts, sync: useScreenSync({ screen: "accounts" }, [accounts]), }; }, { wrapper: createQueryWrapper().wrapper }, ); try { // Exhaust the transport back-off so the query settles in error. await vi.waitFor(async () => { await act(() => vi.advanceTimersByTimeAsync(TRANSPORT_RECOVERY_MS)); expect(result.current.sync.transportError).toBe(true); }); expect(gets).toBe(TRANSPORT_ATTEMPTS); await act(async () => result.current.sync.retry()); await vi.waitFor(async () => { await act(() => vi.advanceTimersByTimeAsync(0)); expect(result.current.accounts.data).toEqual([]); }); } finally { vi.useRealTimers(); } expect(gets).toBe(TRANSPORT_ATTEMPTS + 1); expect(syncModes).toEqual(Array.from({ length: gets }, () => null)); }); it("refreshes the currently open resources together", async () => { auth(); let gets = 0; server.use( http.get("*/api/v1/brokerage-accounts", () => { gets += 1; return HttpResponse.json(resource([])); }), ); const { result } = renderHook( () => { const accounts = useBrokerageAccounts(); return useScreenSync({ screen: "accounts" }, [accounts]); }, { wrapper: createQueryWrapper().wrapper }, ); await waitFor(() => expect(gets).toBe(1)); await act(async () => result.current.refresh()); await waitFor(() => expect(gets).toBe(2)); }); it("does not manually refetch disabled resources", async () => { vi.useFakeTimers(); const refetch = vi.fn(() => Promise.resolve()); const resource = { isEnabled: false, isError: true, isFetching: false, queryKey: [], refetch, }; try { const { result, unmount } = renderHook(() => useScreenSync({ screen: "accounts" }, [resource]), ); await act(async () => { await result.current.refresh(); await result.current.retry(); }); await act(() => vi.advanceTimersByTime(1_800_000)); expect(refetch).not.toHaveBeenCalled(); unmount(); } finally { vi.useRealTimers(); } }); });