From b5741e59b786e80c9ae30f10492ddc7a9ce16781 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 1 May 2026 20:16:43 -0400 Subject: [PATCH] feat(web): polish AlbumCard with FabledSword tokens + play overlay Co-Authored-By: Claude Sonnet 4.6 --- web/src/lib/components/AlbumCard.svelte | 64 +++++++++++++++++++----- web/src/lib/components/AlbumCard.test.ts | 24 ++++++++- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/web/src/lib/components/AlbumCard.svelte b/web/src/lib/components/AlbumCard.svelte index 4eeb0aed..a26fb4b8 100644 --- a/web/src/lib/components/AlbumCard.svelte +++ b/web/src/lib/components/AlbumCard.svelte @@ -2,7 +2,8 @@ import type { AlbumRef, AlbumDetail } from '$lib/api/types'; import { FALLBACK_COVER } from '$lib/media/covers'; import { api } from '$lib/api/client'; - import { enqueueTracks } from '$lib/player/store.svelte'; + import { enqueueTracks, playQueue } from '$lib/player/store.svelte'; + import { Plus, Play } from 'lucide-svelte'; import LikeButton from './LikeButton.svelte'; let { album }: { album: AlbumRef } = $props(); @@ -17,32 +18,69 @@ const detail = await api.get(`/api/albums/${album.id}`); enqueueTracks(detail.tracks); } + + async function onPlayClick(e: MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + const detail = await api.get(`/api/albums/${album.id}`); + playQueue(detail.tracks, 0); + } -
+
- -
{album.title}
+
+ + +
+
{album.title}
+ {#if album.artist_name} +
{album.artist_name}
+ {/if} {#if album.year}
{album.year}
{/if}
-
+
+ class="rounded-full bg-surface px-2 py-1 text-sm text-text-secondary hover:text-text-primary focus-visible:ring-2 focus-visible:ring-accent" + > + +
+ + diff --git a/web/src/lib/components/AlbumCard.test.ts b/web/src/lib/components/AlbumCard.test.ts index 9a29dd54..d85c8926 100644 --- a/web/src/lib/components/AlbumCard.test.ts +++ b/web/src/lib/components/AlbumCard.test.ts @@ -8,7 +8,8 @@ vi.mock('$lib/api/client', () => ({ api: { get: vi.fn() } })); vi.mock('$lib/player/store.svelte', () => ({ - enqueueTracks: vi.fn() + enqueueTracks: vi.fn(), + playQueue: vi.fn() })); vi.mock('$lib/api/likes', () => ({ @@ -28,7 +29,7 @@ vi.mock('@tanstack/svelte-query', async (orig) => { import AlbumCard from './AlbumCard.svelte'; import { api } from '$lib/api/client'; -import { enqueueTracks } from '$lib/player/store.svelte'; +import { enqueueTracks, playQueue } from '$lib/player/store.svelte'; const album: AlbumRef = { id: 'xyz', @@ -89,4 +90,23 @@ describe('AlbumCard', () => { await Promise.resolve(); expect(enqueueTracks).toHaveBeenCalledWith(tracks); }); + + test('play overlay click fetches album detail and calls playQueue at index 0', async () => { + const tracks: TrackRef[] = [ + { id: 't1', title: 'So What', + album_id: 'xyz', album_title: 'Kind of Blue', + artist_id: 'm-davis', artist_name: 'Miles Davis', + track_number: 1, disc_number: 1, duration_sec: 545, + stream_url: '/api/tracks/t1/stream' } + ]; + const detail: AlbumDetail = { ...album, tracks }; + (api.get as ReturnType).mockResolvedValueOnce(detail); + + render(AlbumCard, { props: { album } }); + await fireEvent.click(screen.getByRole('button', { name: /play kind of blue/i })); + expect(api.get).toHaveBeenCalledWith('/api/albums/xyz'); + await Promise.resolve(); + await Promise.resolve(); + expect(playQueue).toHaveBeenCalledWith(tracks, 0); + }); });