feat(web): every paged list loads as you scroll; Liked becomes tabs (#5416)
release / go (push) Successful in 1m53s
release / govulncheck (push) Successful in 49s
release / web (push) Successful in 1m50s
release / android (push) Successful in 6m44s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 21m41s
release / go (push) Successful in 1m53s
release / govulncheck (push) Successful in 49s
release / web (push) Successful in 1m50s
release / android (push) Successful in 6m44s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 21m41s
Preference 172: no "Load more" buttons. Eight remained, on Liked (three sections), the three Search overflow pages, Genres and Years. - ListContinuation: the one bottom-of-list block. It loads the next page ahead of the reader, announces "Loading more…" through aria-live, shows an optional end line, and on a failed page shows the error with Try again, dropping the sentinel so a dead endpoint isn't re-hit on every scroll. Used on all ten paged lists, including the four that already autoloaded. - A failed next page no longer replaces the list. TanStack sets isError for it, so the page-level error branches now apply only to a failed first load. Genres and Years did the same with their own loader; a later page's failure now keeps the grid. - Liked: Artists | Albums | Tracks tabs (operator's choice). Stacked, a long Artists list loading as you scroll would bury the other two. It opens on the first tab that has likes. - TabStrip: the in-page tab strip, now shared by Liked, Playback errors and Requests. - test-utils/intersectionObserver: a stand-in so tests can scroll to the end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// jsdom doesn't ship IntersectionObserver, so a list that loads as you scroll
|
||||
// (InfiniteScrollSentinel, ListContinuation) never fires in a test. This
|
||||
// installs a stand-in that records each observer, so a test can play
|
||||
// "the reader scrolled to the bottom" with scrollToEnd().
|
||||
type Observer = { cb: IntersectionObserverCallback; self: IntersectionObserver; connected: boolean };
|
||||
|
||||
export function installIntersectionObserverMock() {
|
||||
const observers: Observer[] = [];
|
||||
|
||||
class MockIntersectionObserver {
|
||||
observe = vi.fn();
|
||||
unobserve = vi.fn();
|
||||
disconnect = vi.fn(() => {
|
||||
const o = observers.find((x) => x.self === (this as unknown as IntersectionObserver));
|
||||
if (o) o.connected = false;
|
||||
});
|
||||
takeRecords = vi.fn(() => []);
|
||||
constructor(cb: IntersectionObserverCallback) {
|
||||
observers.push({ cb, self: this as unknown as IntersectionObserver, connected: true });
|
||||
}
|
||||
}
|
||||
|
||||
vi.stubGlobal('IntersectionObserver', MockIntersectionObserver);
|
||||
|
||||
return {
|
||||
/** Reports every connected observer's target as on screen. */
|
||||
scrollToEnd() {
|
||||
for (const o of observers) {
|
||||
if (!o.connected) continue;
|
||||
o.cb([{ isIntersecting: true } as IntersectionObserverEntry], o.self);
|
||||
}
|
||||
},
|
||||
/** How many observers are currently watching. */
|
||||
connectedCount() {
|
||||
return observers.filter((o) => o.connected).length;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export function mockInfiniteQuery<T>(opts: {
|
||||
error?: unknown;
|
||||
hasNextPage?: boolean;
|
||||
isFetchingNextPage?: boolean;
|
||||
isFetchNextPageError?: boolean;
|
||||
fetchNextPage?: () => void;
|
||||
refetch?: () => void;
|
||||
} = {}) {
|
||||
@@ -24,6 +25,7 @@ export function mockInfiniteQuery<T>(opts: {
|
||||
error: opts.error,
|
||||
hasNextPage: opts.hasNextPage ?? false,
|
||||
isFetchingNextPage: opts.isFetchingNextPage ?? false,
|
||||
isFetchNextPageError: opts.isFetchNextPageError ?? false,
|
||||
fetchNextPage: opts.fetchNextPage ?? (() => {}),
|
||||
refetch: opts.refetch ?? (() => {})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user