import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; import { emptyLikesMock } from '../../test-utils/mocks/likes'; import { apiClientMock } from '../../test-utils/mocks/client'; import type { ArtistRef, TrackRef } from '#lib/api/types.js'; import { makeTrack, makeTracks } from '#test-utils/fixtures/track.js'; vi.mock('#lib/api/client.js', () => apiClientMock()); vi.mock('#lib/player/store.svelte.js', () => ({ playQueue: vi.fn(), enqueueTracks: vi.fn() })); vi.mock('#lib/api/artists.js', () => ({ listArtistTracks: vi.fn().mockResolvedValue([]) })); vi.mock('#lib/api/likes.js', () => emptyLikesMock()); import ArtistCard from './ArtistCard.svelte'; import { api } from '#lib/api/client.js'; import { playQueue } from '#lib/player/store.svelte.js'; const artist: ArtistRef = { id: 'art-1', name: 'Boards of Canada', sort_name: 'Boards of Canada', album_count: 5, cover_url: '/api/albums/cov-1/cover' }; afterEach(() => vi.clearAllMocks()); describe('ArtistCard', () => { test('renders cover img inside link to /artists/:id', () => { const { container } = render(ArtistCard, { props: { artist } }); const link = screen.getByRole('link'); expect(link).toHaveAttribute('href', '/artists/art-1'); const img = container.querySelector('img') as HTMLImageElement; expect(img.src).toContain('/api/albums/cov-1/cover'); }); test('falls back to Disc3 glyph when cover_url is empty', () => { const { container } = render(ArtistCard, { props: { artist: { ...artist, cover_url: '' } } }); expect(container.querySelector('img')).not.toBeInTheDocument(); expect(container.querySelector('svg')).toBeInTheDocument(); }); test('play overlay fetches tracks, shuffles, calls playQueue', async () => { const tracks: TrackRef[] = makeTracks(2, { album_id: 'al-1', album_title: 'X', artist_id: 'art-1', artist_name: 'BoC', duration_sec: 1 }); (api.get as ReturnType).mockResolvedValueOnce(tracks); render(ArtistCard, { props: { artist } }); await fireEvent.click(screen.getByRole('button', { name: /play boards of canada/i })); expect(api.get).toHaveBeenCalledWith('/api/artists/art-1/tracks'); await Promise.resolve(); await Promise.resolve(); expect(playQueue).toHaveBeenCalledOnce(); const [calledTracks, idx] = (playQueue as ReturnType).mock.calls[0]; expect(calledTracks).toHaveLength(2); expect(idx).toBe(0); expect(calledTracks.map((t: TrackRef) => t.id).sort()).toEqual(['t1', 't2']); }); test('renders the top-right action cluster (Like + Add-to-queue)', () => { render(ArtistCard, { props: { artist } }); // Like button (LikeButton's aria-label is dynamic Like|Unlike). expect(screen.getByRole('button', { name: /^(Like|Unlike)$/ })).toBeInTheDocument(); // Add-to-queue button. expect(screen.getByRole('button', { name: /Add Boards of Canada to queue/i })).toBeInTheDocument(); }); test('renders the bottom-right kebab', () => { render(ArtistCard, { props: { artist } }); expect(screen.getByRole('button', { name: /Artist actions for Boards of Canada/i })).toBeInTheDocument(); }); test('+queue button calls listArtistTracks then enqueueTracks', async () => { const fakeTracks = [ makeTrack({ artist_id: 'art-1', artist_name: 'BoC' }) ]; const { listArtistTracks } = await import('#lib/api/artists.js'); (listArtistTracks as unknown as ReturnType).mockResolvedValueOnce(fakeTracks); render(ArtistCard, { props: { artist } }); const btn = screen.getByRole('button', { name: /Add Boards of Canada to queue/i }); await fireEvent.click(btn); // Allow promise microtask to flush. await new Promise((r) => setTimeout(r, 0)); expect(listArtistTracks).toHaveBeenCalledWith('art-1'); const { enqueueTracks } = await import('#lib/player/store.svelte.js'); expect(enqueueTracks).toHaveBeenCalledWith(fakeTracks); }); });