d83b3eab25
CI svelte-check failed on 5 type errors caused by the slice using
duration_ms/album_name fields that don't exist on TrackRef (the real
shape uses duration_sec + album_id/album_title). Fixed across
QueueDrawer, QueueDrawer.test, QueueTrackRow.test, persisted.test,
and the inline `state.current = null` in PlayerBar.test (TrackRef |
undefined, not | null).
Final whole-slice review concerns also rolled in:
- C1 (Critical): persisted position was restored to UI but never
seeked into the audio element — first timeupdate snapped _position
back to 0. Added a one-shot _pendingRestorePosition module ref +
consumePendingRestorePosition() export; layout's loadedmetadata
handler now seeks once on restore.
- I1 (Important): throttled-write effect now wraps _queue / _index
reads in untrack so the dependency tracking matches the design
intent. Position remains the only tracked dep.
- I4 (Important): drawer gets inert={!queueDrawerOpen} so its inner
buttons drop out of tab order when closed (aria-hidden alone
doesn't do that). Removed redundant role="complementary" from
<aside> (implied by the element). New test asserts inert binding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
3.2 KiB
TypeScript
81 lines
3.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, fireEvent } from '@testing-library/svelte';
|
|
import QueueTrackRow from './QueueTrackRow.svelte';
|
|
import type { TrackRef } from '$lib/api/types';
|
|
|
|
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: TrackRef = {
|
|
id: 't1',
|
|
title: 'Song Title',
|
|
album_id: 'al1',
|
|
album_title: 'Album',
|
|
artist_id: 'ar1',
|
|
artist_name: 'Artist Name',
|
|
duration_sec: 200,
|
|
stream_url: '/stream/t1'
|
|
};
|
|
|
|
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);
|
|
});
|
|
});
|