// @vitest-environment jsdom // // A next-page call while that page is still loading joins it instead of sending it again: // on prod every feed page went out 2–3 times with the same cursor. import { InfiniteQueryObserver } from "@tanstack/react-query"; import { describe, expect, it } from "vitest"; import { mayReadAhead, NEXT_PAGE, nextPageOnClick } from "./nextPage"; import { createQueryClient } from "./queryClient"; function probe(sent: (string | null)[], delay = 5) { return new InfiniteQueryObserver(createQueryClient(), { queryKey: ["probe"], initialPageParam: null as string | null, queryFn: async ({ pageParam }) => { sent.push(pageParam); await new Promise((r) => setTimeout(r, delay)); return { next: `c${sent.length}` }; }, getNextPageParam: (last: { next: string }) => last.next, }); } describe("NEXT_PAGE", () => { it("sends each page once when the read-ahead calls twice in a row", async () => { const sent: (string | null)[] = []; const observer = new InfiniteQueryObserver(createQueryClient(), { queryKey: ["probe"], initialPageParam: null as string | null, queryFn: async ({ pageParam }) => { sent.push(pageParam); await new Promise((r) => setTimeout(r, 5)); return { next: `c${sent.length}` }; }, getNextPageParam: (last: { next: string }) => last.next, }); const unsubscribe = observer.subscribe(() => {}); await observer.refetch(); // Two scroll frames before `isFetchingNextPage` reaches the component. void observer.fetchNextPage(NEXT_PAGE); await observer.fetchNextPage(NEXT_PAGE); expect(sent).toEqual([null, "c1"]); expect(observer.getCurrentResult().data?.pages).toHaveLength(2); unsubscribe(); }); // The join takes ANY fetch in flight. During a background refetch (an invalidation, a // poll) it reloads the pages already there and appends none — why a read-ahead waits it out. it("joins a background refetch and appends no page", async () => { const sent: (string | null)[] = []; const observer = probe(sent); const unsubscribe = observer.subscribe(() => {}); await observer.refetch(); void observer.refetch(); await observer.fetchNextPage(NEXT_PAGE); expect(observer.getCurrentResult().data?.pages).toHaveLength(1); unsubscribe(); }); }); describe("a read-ahead", () => { it("waits while the list fetches anything", () => { expect(mayReadAhead(true, false)).toBe(true); expect(mayReadAhead(true, true)).toBe(false); expect(mayReadAhead(false, false)).toBe(false); }); }); describe("a next page asked for by a click", () => { it("loads the page even while a background refetch runs", async () => { const sent: (string | null)[] = []; const observer = probe(sent); const unsubscribe = observer.subscribe(() => {}); await observer.refetch(); void observer.refetch(); await observer.fetchNextPage(nextPageOnClick(false)); expect(observer.getCurrentResult().data?.pages).toHaveLength(2); unsubscribe(); }); it("joins a next page already loading", () => { expect(nextPageOnClick(true)).toBe(NEXT_PAGE); }); });