From a4a5a2030897c973a2a02f29bd5e904950ac850d Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 25 Apr 2026 15:54:18 -0400 Subject: [PATCH] feat(web): AlbumCard + queue overlay button Wraps the existing link in a relative container; adds an absolutely positioned + button. On click stops propagation, fetches /api/albums/:id once, and feeds tracks into enqueueTracks. --- web/src/lib/components/AlbumCard.svelte | 51 ++++++++++++++++-------- web/src/lib/components/AlbumCard.test.ts | 41 +++++++++++++++++-- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/web/src/lib/components/AlbumCard.svelte b/web/src/lib/components/AlbumCard.svelte index 4ce23b53..828daa79 100644 --- a/web/src/lib/components/AlbumCard.svelte +++ b/web/src/lib/components/AlbumCard.svelte @@ -1,27 +1,44 @@ - - -
{album.title}
- {#if album.year} -
{album.year}
- {/if} -
+
+ + +
{album.title}
+ {#if album.year} +
{album.year}
+ {/if} +
+ +
diff --git a/web/src/lib/components/AlbumCard.test.ts b/web/src/lib/components/AlbumCard.test.ts index 5eb6d4aa..1f512b6a 100644 --- a/web/src/lib/components/AlbumCard.test.ts +++ b/web/src/lib/components/AlbumCard.test.ts @@ -1,9 +1,19 @@ -import { describe, expect, test } from 'vitest'; +import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; -import AlbumCard from './AlbumCard.svelte'; -import type { AlbumRef } from '$lib/api/types'; +import type { AlbumRef, AlbumDetail, TrackRef } from '$lib/api/types'; import { FALLBACK_COVER } from '$lib/media/covers'; +vi.mock('$lib/api/client', () => ({ + api: { get: vi.fn() } +})); +vi.mock('$lib/player/store.svelte', () => ({ + enqueueTracks: vi.fn() +})); + +import AlbumCard from './AlbumCard.svelte'; +import { api } from '$lib/api/client'; +import { enqueueTracks } from '$lib/player/store.svelte'; + const album: AlbumRef = { id: 'xyz', title: 'Kind of Blue', @@ -15,6 +25,8 @@ const album: AlbumRef = { cover_url: '/api/albums/xyz/cover', }; +afterEach(() => vi.clearAllMocks()); + describe('AlbumCard', () => { test('renders cover, title, year inside a link to /albums/:id', () => { const { container } = render(AlbumCard, { props: { album } }); @@ -29,7 +41,6 @@ describe('AlbumCard', () => { test('year is omitted when not present', () => { render(AlbumCard, { props: { album: { ...album, year: undefined } } }); - // No crash; no year text. expect(screen.queryByText(/1959/)).not.toBeInTheDocument(); }); @@ -39,4 +50,26 @@ describe('AlbumCard', () => { await fireEvent.error(img); expect(img.src).toBe(FALLBACK_COVER); }); + + test('+ queue button fetches album detail and calls enqueueTracks', 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: /add to queue/i })); + + expect(api.get).toHaveBeenCalledWith('/api/albums/xyz'); + await Promise.resolve(); + await Promise.resolve(); + expect(enqueueTracks).toHaveBeenCalledWith(tracks); + }); });