From da39ff847b2127a3be13aa1f1c5bcad8913bb9b1 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 4 May 2026 06:19:59 -0400 Subject: [PATCH] feat(web/m7-365): HistoryRow component with click-to-play --- web/src/lib/components/HistoryRow.svelte | 64 +++++++++++++++++++++++ web/src/lib/components/HistoryRow.test.ts | 53 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 web/src/lib/components/HistoryRow.svelte create mode 100644 web/src/lib/components/HistoryRow.test.ts diff --git a/web/src/lib/components/HistoryRow.svelte b/web/src/lib/components/HistoryRow.svelte new file mode 100644 index 00000000..44e6db99 --- /dev/null +++ b/web/src/lib/components/HistoryRow.svelte @@ -0,0 +1,64 @@ + + + diff --git a/web/src/lib/components/HistoryRow.test.ts b/web/src/lib/components/HistoryRow.test.ts new file mode 100644 index 00000000..bd5b050c --- /dev/null +++ b/web/src/lib/components/HistoryRow.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/svelte'; +import HistoryRow from './HistoryRow.svelte'; +import type { HistoryEvent } from '$lib/api/history'; +import type { TrackRef } from '$lib/api/types'; + +const playQueue = vi.fn(); + +vi.mock('$lib/player/store.svelte', () => ({ + playQueue: (...args: unknown[]) => playQueue(...args) +})); + +const sampleTrack: TrackRef = { + id: 'track-1', + title: 'Song Title', + album_id: 'album-1', + album_title: 'Album', + artist_id: 'artist-1', + artist_name: 'Artist Name', + duration_sec: 200, + stream_url: '/stream/track-1' +}; + +const sampleEvent: HistoryEvent = { + id: 'event-1', + played_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(), + track: sampleTrack +}; + +describe('HistoryRow', () => { + beforeEach(() => { + playQueue.mockClear(); + }); + + it('renders track title, artist, and album', () => { + render(HistoryRow, { props: { event: sampleEvent } }); + expect(screen.getByText('Song Title')).toBeInTheDocument(); + expect(screen.getByText(/Artist Name/)).toBeInTheDocument(); + expect(screen.getByText(/Album/)).toBeInTheDocument(); + }); + + it('clicking the row calls playQueue([track], 0)', async () => { + render(HistoryRow, { props: { event: sampleEvent } }); + const row = screen.getByRole('button', { name: /play song title/i }); + await fireEvent.click(row); + expect(playQueue).toHaveBeenCalledWith([sampleTrack], 0); + }); + + it('renders a relative timestamp for events under 1 hour ago', () => { + render(HistoryRow, { props: { event: sampleEvent } }); + expect(screen.getByText(/\d+m ago/)).toBeInTheDocument(); + }); +});