feat(web): infinite scroll on /library/artists and /library/albums
Replaces the explicit 'Load more' button on both library list pages with an IntersectionObserver-based sentinel that triggers fetchNextPage automatically as the operator scrolls near the bottom (300px rootMargin). New InfiniteScrollSentinel component: - Stays disabled while a fetch is in flight (prevents repeat firing on tall pages or fast scrolls). - Tears down its observer when hasNextPage flips false. - Defensive against environments without IntersectionObserver (jsdom); tests provide a synchronous mock. Also fixes a pre-existing test setup gap on the albums page test: AlbumCard renders LikeButton which calls useQueryClient(), and the page.test.ts wasn't wrapped in a QueryClientProvider — added the same useQueryClient mock the AlbumCard.test.ts already uses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import AlbumCard from '$lib/components/AlbumCard.svelte';
|
||||
import AlphabeticalGrid from '$lib/components/AlphabeticalGrid.svelte';
|
||||
import ApiErrorBanner from '$lib/components/ApiErrorBanner.svelte';
|
||||
import InfiniteScrollSentinel from '$lib/components/InfiniteScrollSentinel.svelte';
|
||||
import { useDelayed } from '$lib/utils/useDelayed.svelte';
|
||||
import type { AlbumRef } from '$lib/api/types';
|
||||
|
||||
@@ -40,14 +41,13 @@
|
||||
</AlphabeticalGrid>
|
||||
|
||||
{#if query.hasNextPage}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded bg-surface py-2 text-sm hover:bg-surface-hover disabled:opacity-60"
|
||||
disabled={query.isFetchingNextPage}
|
||||
onclick={() => query.fetchNextPage()}
|
||||
>
|
||||
{query.isFetchingNextPage ? 'Loading…' : 'Load more'}
|
||||
</button>
|
||||
<InfiniteScrollSentinel
|
||||
enabled={!query.isFetchingNextPage}
|
||||
onIntersect={() => query.fetchNextPage()}
|
||||
/>
|
||||
{#if query.isFetchingNextPage}
|
||||
<p class="py-2 text-center text-sm text-text-secondary">Loading more…</p>
|
||||
{/if}
|
||||
{:else if albums.length > 0}
|
||||
<p class="py-2 text-center text-sm text-text-secondary">End of library</p>
|
||||
{/if}
|
||||
|
||||
@@ -37,6 +37,13 @@ vi.mock('$lib/api/likes', () => ({
|
||||
likeEntity: vi.fn(),
|
||||
unlikeEntity: vi.fn()
|
||||
}));
|
||||
// AlbumCard renders LikeButton, which calls useQueryClient() to get the
|
||||
// cache for invalidation. The page test isn't wrapped in a QueryClientProvider,
|
||||
// so we stub useQueryClient with a noop client.
|
||||
vi.mock('@tanstack/svelte-query', async (orig) => {
|
||||
const actual = (await orig()) as Record<string, unknown>;
|
||||
return { ...actual, useQueryClient: () => ({}) };
|
||||
});
|
||||
|
||||
import Page from './+page.svelte';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import ArtistCard from '$lib/components/ArtistCard.svelte';
|
||||
import AlphabeticalGrid from '$lib/components/AlphabeticalGrid.svelte';
|
||||
import ApiErrorBanner from '$lib/components/ApiErrorBanner.svelte';
|
||||
import InfiniteScrollSentinel from '$lib/components/InfiniteScrollSentinel.svelte';
|
||||
import { useDelayed } from '$lib/utils/useDelayed.svelte';
|
||||
import type { ArtistRef } from '$lib/api/types';
|
||||
|
||||
@@ -40,14 +41,13 @@
|
||||
</AlphabeticalGrid>
|
||||
|
||||
{#if query.hasNextPage}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded bg-surface py-2 text-sm hover:bg-surface-hover disabled:opacity-60"
|
||||
disabled={query.isFetchingNextPage}
|
||||
onclick={() => query.fetchNextPage()}
|
||||
>
|
||||
{query.isFetchingNextPage ? 'Loading…' : 'Load more'}
|
||||
</button>
|
||||
<InfiniteScrollSentinel
|
||||
enabled={!query.isFetchingNextPage}
|
||||
onIntersect={() => query.fetchNextPage()}
|
||||
/>
|
||||
{#if query.isFetchingNextPage}
|
||||
<p class="py-2 text-center text-sm text-text-secondary">Loading more…</p>
|
||||
{/if}
|
||||
{:else if artists.length > 0}
|
||||
<p class="py-2 text-center text-sm text-text-secondary">End of library</p>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user