fix(ui): a read-ahead waits out a background refetch, and a revealed row is mounted before it is focused
NEXT_PAGE joins ANY fetch in flight: a read-ahead during an invalidation or a poll joined that refetch and appended no page, and nothing asked again until the reader moved the range (the channel page during a backfill). The read-ahead now waits while the list fetches anything (mayReadAhead, re-run on isFetching); a Load older click cancels a poll instead (nextPageOnClick). A page turn to a revealed row restarted the progressive mount at 30 rows; Channels focuses that row in the next commit, and at index 40 of a 50-row page it was not there, so focus fell to <body>. The mount now reaches through the revealed row.
This commit is contained in:
@@ -5,9 +5,22 @@
|
||||
import { InfiniteQueryObserver } from "@tanstack/react-query";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { NEXT_PAGE } from "./nextPage";
|
||||
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)[] = [];
|
||||
@@ -32,4 +45,46 @@ describe("NEXT_PAGE", () => {
|
||||
expect(observer.getCurrentResult().data?.pages).toHaveLength(2);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
// E31 S5: 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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user