import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; import { emptyLikesMock } from '../../test-utils/mocks/likes'; import { makeTrack } from '$test-utils/fixtures/track'; // QueueTrackRow now renders a LikeButton, which reads createLikedIdsQuery. // Stub the likes API so the row doesn't need a real QueryClient in context. // The mock (and its emptyLikesMock import) must precede the component import // below: importing QueueTrackRow transitively loads LikeButton → the mocked // module, and the hoisted factory needs emptyLikesMock already initialized. vi.mock('$lib/api/likes', () => emptyLikesMock()); import QueueTrackRow from './QueueTrackRow.svelte'; const playFromQueueIndex = vi.fn(); const removeFromQueue = vi.fn(); const moveQueueItem = vi.fn(); vi.mock('$lib/player/store.svelte', () => ({ playFromQueueIndex: (...args: unknown[]) => playFromQueueIndex(...args), removeFromQueue: (...args: unknown[]) => removeFromQueue(...args), moveQueueItem: (...args: unknown[]) => moveQueueItem(...args) })); const sampleTrack = makeTrack({ title: 'Song Title', artist_name: 'Artist Name' }); describe('QueueTrackRow', () => { beforeEach(() => { playFromQueueIndex.mockClear(); removeFromQueue.mockClear(); moveQueueItem.mockClear(); }); it('renders track title and artist', () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); expect(screen.getByText('Song Title')).toBeInTheDocument(); expect(screen.getByText(/Artist Name/)).toBeInTheDocument(); }); it('clicking the body calls playFromQueueIndex with the row index', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const body = screen.getByRole('button', { name: /play song title/i }); await fireEvent.click(body); expect(playFromQueueIndex).toHaveBeenCalledWith(3); }); it('clicking the remove button calls removeFromQueue with the row index', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const remove = screen.getByLabelText(/remove from queue/i); await fireEvent.click(remove); expect(removeFromQueue).toHaveBeenCalledWith(3); }); it('current row shows the "Now playing" chip', () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: true } }); expect(screen.getByText(/now playing/i)).toBeInTheDocument(); }); it('current row body click does NOT call playFromQueueIndex', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: true } }); // The body button has a different aria-label when isCurrent const body = screen.getByLabelText(/now playing/i); await fireEvent.click(body); expect(playFromQueueIndex).not.toHaveBeenCalled(); }); it('ArrowDown on the drag handle calls moveQueueItem with index+1', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const handle = screen.getByLabelText(/reorder track/i); await fireEvent.keyDown(handle, { key: 'ArrowDown' }); expect(moveQueueItem).toHaveBeenCalledWith(3, 4); }); it('ArrowUp on the drag handle calls moveQueueItem with index-1', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const handle = screen.getByLabelText(/reorder track/i); await fireEvent.keyDown(handle, { key: 'ArrowUp' }); expect(moveQueueItem).toHaveBeenCalledWith(3, 2); }); it('Enter on the drag handle prevents default but does not call moveQueueItem', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const handle = screen.getByLabelText(/reorder track/i); await fireEvent.keyDown(handle, { key: 'Enter' }); expect(moveQueueItem).not.toHaveBeenCalled(); }); it('Space on the drag handle prevents default but does not call moveQueueItem', async () => { render(QueueTrackRow, { props: { track: sampleTrack, index: 3, isCurrent: false } }); const handle = screen.getByLabelText(/reorder track/i); await fireEvent.keyDown(handle, { key: ' ' }); expect(moveQueueItem).not.toHaveBeenCalled(); }); });