import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; import { emptyLikesMock } from '../../test-utils/mocks/likes'; import type { TrackRef } from '$lib/api/types'; import { makeTrack } from '$test-utils/fixtures/track'; // QueueTrackRow (rendered inside QueueDrawer → QueueList) now renders a // LikeButton, which reads createLikedIdsQuery. Stub the likes API so the // rows don't need a real QueryClient in context. Both mocks — and the // emptyLikesMock import — must precede the QueueDrawer import below, since // loading the component evaluates the mocked modules and the hoisted // factories need their referenced bindings already initialized. vi.mock('$lib/api/likes', () => emptyLikesMock()); const closeQueueDrawer = vi.fn(); let queueValue: TrackRef[] = []; let indexValue = 0; let openValue = true; vi.mock('$lib/player/store.svelte', () => ({ closeQueueDrawer: (...args: unknown[]) => closeQueueDrawer(...args), player: { get queue() { return queueValue; }, get index() { return indexValue; }, get queueDrawerOpen() { return openValue; } }, // QueueTrackRow imports these from the store; provide stubs so its // module-load doesn't break when QueueDrawer renders rows. QueueList // imports clearQueue for its header action. playFromQueueIndex: vi.fn(), removeFromQueue: vi.fn(), moveQueueItem: vi.fn(), clearQueue: vi.fn() })); import QueueDrawer from './QueueDrawer.svelte'; const t = (id: string): TrackRef => makeTrack({ id, title: `Song ${id}` }); describe('QueueDrawer', () => { beforeEach(() => { closeQueueDrawer.mockClear(); queueValue = []; indexValue = 0; openValue = true; }); it('renders Queue title and track-count summary', () => { queueValue = [t('a'), t('b'), t('c')]; render(QueueDrawer); expect(screen.getByText(/^Queue$/i)).toBeInTheDocument(); expect(screen.getByText(/3 tracks/i)).toBeInTheDocument(); }); it('renders one row per queue entry', () => { queueValue = [t('a'), t('b')]; render(QueueDrawer); expect(screen.getByText('Song a')).toBeInTheDocument(); expect(screen.getByText('Song b')).toBeInTheDocument(); }); it('shows empty-state copy when queue is empty', () => { queueValue = []; render(QueueDrawer); expect(screen.getByText(/no tracks queued/i)).toBeInTheDocument(); }); it('clicking the close button calls closeQueueDrawer', async () => { render(QueueDrawer); const close = screen.getByLabelText('Close queue'); await fireEvent.click(close); expect(closeQueueDrawer).toHaveBeenCalled(); }); it('clicking the backdrop calls closeQueueDrawer', async () => { render(QueueDrawer); const backdrop = screen.getByLabelText(/close queue backdrop/i); await fireEvent.click(backdrop); expect(closeQueueDrawer).toHaveBeenCalled(); }); it('drawer is hidden (aria-hidden=true) when queueDrawerOpen is false', () => { openValue = false; queueValue = [t('a')]; render(QueueDrawer); const aside = document.querySelector('aside[aria-label="Playback queue"]'); expect(aside).not.toBeNull(); expect(aside!.getAttribute('aria-hidden')).toBe('true'); }); it('drawer is inert when closed', () => { openValue = false; queueValue = [t('a')]; render(QueueDrawer); const aside = document.querySelector('aside[aria-label="Playback queue"]'); expect(aside).not.toBeNull(); // Svelte 5 + jsdom uses property binding for `inert`, not attribute. Check both // to be robust regardless of which jsdom version handles the mirror. const inertProp = (aside as unknown as { inert?: boolean }).inert === true; const inertAttr = aside!.hasAttribute('inert'); expect(inertProp || inertAttr).toBe(true); }); });