From 3d0a42865eb09dcb70e5d4b666e21b41a5e8276f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 1 May 2026 20:26:02 -0400 Subject: [PATCH] feat(web): add /library/albums wrapping-grid page Co-Authored-By: Claude Sonnet 4.6 --- web/src/routes/library/albums/+page.svelte | 55 ++++++++++++++++++++++ web/src/routes/library/albums/page.test.ts | 53 +++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 web/src/routes/library/albums/+page.svelte create mode 100644 web/src/routes/library/albums/page.test.ts diff --git a/web/src/routes/library/albums/+page.svelte b/web/src/routes/library/albums/+page.svelte new file mode 100644 index 00000000..a8da430b --- /dev/null +++ b/web/src/routes/library/albums/+page.svelte @@ -0,0 +1,55 @@ + + +
+
+

Albums

+ {#if !query.isPending && !query.isError} +

+ {total} {total === 1 ? 'album' : 'albums'} +

+ {/if} +
+ + {#if query.isError} + + {:else if showSkeleton.value && albums.length === 0} +

Loading…

+ {:else if !query.isPending && total === 0} +

No albums yet — scan a library folder via the server's config.

+ {:else} + a.sort_title || a.title} + > + {#snippet item(album: AlbumRef)} + + {/snippet} + + + {#if query.hasNextPage} + + {:else if albums.length > 0} +

End of library

+ {/if} + {/if} +
diff --git a/web/src/routes/library/albums/page.test.ts b/web/src/routes/library/albums/page.test.ts new file mode 100644 index 00000000..437cac0f --- /dev/null +++ b/web/src/routes/library/albums/page.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, test, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import { readable } from 'svelte/store'; + +vi.mock('$lib/api/albums', () => ({ + createAlbumsAlphaInfiniteQuery: () => readable({ + data: { pages: [{ + items: [ + { id: 'a1', title: 'Apple', sort_title: 'Apple', + artist_id: 'art-1', artist_name: 'X', + track_count: 5, duration_sec: 100, cover_url: '/api/albums/a1/cover' }, + { id: 'b1', title: 'Banana', sort_title: 'Banana', + artist_id: 'art-1', artist_name: 'X', + track_count: 3, duration_sec: 50, cover_url: '/api/albums/b1/cover' } + ], + total: 2, limit: 50, offset: 0 + }] }, + isPending: false, + isError: false, + hasNextPage: false, + isFetchingNextPage: false, + refetch: () => {}, + fetchNextPage: () => {} + }), + ALBUM_PAGE_SIZE: 50 +})); +vi.mock('$lib/api/client', () => ({ api: { get: vi.fn() } })); +vi.mock('$lib/player/store.svelte', () => ({ + enqueueTracks: vi.fn(), + playQueue: vi.fn() +})); +vi.mock('$lib/api/likes', () => ({ + createLikedIdsQuery: () => readable({ + data: { track_ids: [], album_ids: [], artist_ids: [] }, + isPending: false, isError: false + }), + likeEntity: vi.fn(), + unlikeEntity: vi.fn() +})); + +import Page from './+page.svelte'; + +describe('/library/albums', () => { + test('renders title + count + alphabetical dividers', () => { + render(Page); + expect(screen.getByRole('heading', { name: /albums/i })).toBeInTheDocument(); + expect(screen.getByText(/2 albums/)).toBeInTheDocument(); + expect(screen.getByText('A')).toBeInTheDocument(); + expect(screen.getByText('B')).toBeInTheDocument(); + expect(screen.getByText('Apple')).toBeInTheDocument(); + expect(screen.getByText('Banana')).toBeInTheDocument(); + }); +});