feat(web): add CompactTrackCard for Most played rows
Small 140px clickable card showing album cover, title, and artist name. Clicking calls playQueue(sectionTracks, index) to play through the full section list. Includes Vitest tests for click and render behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import type { TrackRef } from '$lib/api/types';
|
||||
import { playQueue } from '$lib/player/store.svelte';
|
||||
import { FALLBACK_COVER } from '$lib/media/covers';
|
||||
|
||||
let {
|
||||
track,
|
||||
sectionTracks,
|
||||
index
|
||||
}: {
|
||||
track: TrackRef;
|
||||
sectionTracks: TrackRef[];
|
||||
index: number;
|
||||
} = $props();
|
||||
|
||||
const coverUrl = $derived(`/api/albums/${track.album_id}/cover`);
|
||||
|
||||
function onImgError(e: Event) {
|
||||
(e.currentTarget as HTMLImageElement).src = FALLBACK_COVER;
|
||||
}
|
||||
|
||||
function onClick() {
|
||||
playQueue(sectionTracks, index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Play ${track.title}`}
|
||||
onclick={onClick}
|
||||
class="card group block w-36 rounded text-left focus-visible:ring-2 focus-visible:ring-accent"
|
||||
>
|
||||
<div class="aspect-square w-full overflow-hidden rounded-md bg-surface-hover">
|
||||
<img
|
||||
src={coverUrl}
|
||||
alt=""
|
||||
class="h-full w-full object-cover transition-transform group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
onerror={onImgError}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 truncate text-xs font-medium text-text-primary">{track.title}</div>
|
||||
<div class="truncate text-xs text-text-secondary">{track.artist_name}</div>
|
||||
</button>
|
||||
@@ -0,0 +1,42 @@
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/svelte';
|
||||
import type { TrackRef } from '$lib/api/types';
|
||||
|
||||
vi.mock('$lib/player/store.svelte', () => ({
|
||||
playQueue: vi.fn()
|
||||
}));
|
||||
|
||||
import CompactTrackCard from './CompactTrackCard.svelte';
|
||||
import { playQueue } from '$lib/player/store.svelte';
|
||||
|
||||
const tracks: TrackRef[] = [
|
||||
{ id: 't1', title: 'First', album_id: 'a1', album_title: 'A',
|
||||
artist_id: 'art-1', artist_name: 'Artist',
|
||||
track_number: 1, disc_number: 1, duration_sec: 1, stream_url: '/a' },
|
||||
{ id: 't2', title: 'Second', album_id: 'a1', album_title: 'A',
|
||||
artist_id: 'art-1', artist_name: 'Artist',
|
||||
track_number: 2, disc_number: 1, duration_sec: 1, stream_url: '/b' },
|
||||
{ id: 't3', title: 'Third', album_id: 'a1', album_title: 'A',
|
||||
artist_id: 'art-1', artist_name: 'Artist',
|
||||
track_number: 3, disc_number: 1, duration_sec: 1, stream_url: '/c' }
|
||||
];
|
||||
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('CompactTrackCard', () => {
|
||||
test('clicking the card calls playQueue with sectionTracks at the right index', async () => {
|
||||
render(CompactTrackCard, {
|
||||
props: { track: tracks[1], sectionTracks: tracks, index: 1 }
|
||||
});
|
||||
await fireEvent.click(screen.getByRole('button', { name: /play second/i }));
|
||||
expect(playQueue).toHaveBeenCalledWith(tracks, 1);
|
||||
});
|
||||
|
||||
test('renders title and artist name', () => {
|
||||
render(CompactTrackCard, {
|
||||
props: { track: tracks[0], sectionTracks: tracks, index: 0 }
|
||||
});
|
||||
expect(screen.getByText('First')).toBeInTheDocument();
|
||||
expect(screen.getByText('Artist')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user