import { afterEach, describe, expect, test, vi } from "vitest"; import { act, fireEvent, render, renderHook, screen, } from "@testing-library/react"; import { type ReactNode } from "react"; import { MemoryRouter, Route, Routes, useNavigate } from "react-router"; import { useFeedCache } from "../../../hooks/feed/useFeedCache.ts"; afterEach(() => vi.useRealTimers()); describe("useFeedCache", () => { // A MemoryRouter's first entry is a POP, as after Back. const wrapper = () => ({ children }: { children: ReactNode }) => ( {children} ); test("restores a saved feed on a back/forward (POP) visit", () => { const first = renderHook(() => useFeedCache<{ n: number }>("k", (x) => x), { wrapper: wrapper(), }); act(() => first.result.current.saveState({ items: [{ n: 1 }], page: 2, hasMore: true, }, 300) ); const second = renderHook( () => useFeedCache<{ n: number }>("k", (x) => ({ n: x.n * 10 })), { wrapper: wrapper() }, ); expect(second.result.current.cached).toMatchObject({ items: [{ n: 10 }], page: 2, hasMore: true, scrollY: 300, }); }); test("an expired entry is ignored", () => { vi.useFakeTimers(); const first = renderHook(() => useFeedCache("k", (x) => x), { wrapper: wrapper(), }); act(() => first.result.current.saveState({ items: [], page: 1, hasMore: false }, 0) ); vi.advanceTimersByTime(11 * 60 * 1000); const second = renderHook(() => useFeedCache("k", (x) => x), { wrapper: wrapper(), }); expect(second.result.current.cached).toBeNull(); }); test("a fresh (PUSH) visit ignores the cache", () => { sessionStorage.setItem( "k", JSON.stringify({ items: [1], page: 1, hasMore: false, scrollY: 0, savedAt: Date.now(), }), ); function Probe() { const { cached } = useFeedCache("k", (x) => x); return

{cached ? "cached" : "fresh"}

; } function Home() { const navigate = useNavigate(); return ( ); } render( } /> } /> , ); fireEvent.click(screen.getByText("go")); expect(screen.getByText("fresh")).toBeInTheDocument(); }); test("corrupt storage reads as no cache; a full quota doesn't throw", () => { sessionStorage.setItem("k", "{"); const { result } = renderHook(() => useFeedCache("k", (x) => x), { wrapper: wrapper(), }); expect(result.current.cached).toBeNull(); vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => { throw new Error("QuotaExceededError"); }); expect(() => result.current.saveState({ items: [], page: 1, hasMore: false }, 0) ).not.toThrow(); }); });