import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MiniPlayer } from './MiniPlayer'; import { usePlayer } from '../hooks/usePlayer'; import type { Track } from '../types'; // Mock usePlayer vi.mock('../hooks/usePlayer', () => ({ usePlayer: vi.fn(), })); // Mock child components vi.mock('./TrackInfo', () => ({ TrackInfo: ({ track }: { track: Track | null }) => (
{track?.title || 'No track'}
), })); vi.mock('./PlayPauseButton', () => ({ PlayPauseButton: ({ isPlaying, onClick, }: { isPlaying: boolean; onClick: () => void; }) => ( ), })); vi.mock('./NextPreviousButtons', () => ({ NextPreviousButtons: ({ onNext, onPrevious, canGoNext, canGoPrevious, }: { onNext: () => void; onPrevious: () => void; canGoNext: boolean; canGoPrevious: boolean; }) => (
), })); vi.mock('./ProgressBar', () => ({ ProgressBar: ({ currentTime, duration, onSeek, }: { currentTime: number; duration: number; onSeek: (time: number) => void; }) => (
onSeek(30)}> {currentTime} / {duration}
), })); vi.mock('./VolumeControl', () => ({ VolumeControl: ({ volume, muted, onVolumeChange, onMuteToggle, }: { volume: number; muted: boolean; onVolumeChange: (vol: number) => void; onMuteToggle: () => void; }) => (
), })); const mockTrack: Track = { id: 1, title: 'Test Track', artist: 'Test Artist', duration: 180, url: 'https://example.com/track.mp3', }; const defaultPlayerState = { currentTrack: mockTrack, isPlaying: false, currentTime: 0, duration: 180, volume: 100, muted: false, queue: [mockTrack], currentIndex: 0, repeat: 'off' as const, shuffle: false, play: vi.fn(), pause: vi.fn(), resume: vi.fn(), stop: vi.fn(), next: vi.fn(), previous: vi.fn(), seek: vi.fn(), setVolume: vi.fn(), toggleMute: vi.fn(), toggleShuffle: vi.fn(), setRepeat: vi.fn(), addToQueue: vi.fn(), clearQueue: vi.fn(), }; describe('MiniPlayer', () => { const mockOnToggle = vi.fn(); const mockOnClose = vi.fn(); beforeEach(() => { vi.clearAllMocks(); vi.mocked(usePlayer).mockReturnValue(defaultPlayerState as any); }); it('should not render when isVisible is false', () => { render(); expect( screen.queryByRole('region', { name: 'Mini lecteur audio' }), ).not.toBeInTheDocument(); }); it('should not render when no track is playing', () => { vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, currentTrack: null, } as any); render(); expect( screen.queryByRole('region', { name: 'Mini lecteur audio' }), ).not.toBeInTheDocument(); }); it('should render when visible and track is playing', () => { render(); expect( screen.getByRole('region', { name: 'Mini lecteur audio' }), ).toBeInTheDocument(); }); it('should display track info', () => { render(); expect(screen.getByTestId('track-info')).toBeInTheDocument(); expect(screen.getByText('Test Track')).toBeInTheDocument(); }); it('should display play/pause button', () => { render(); expect(screen.getByTestId('play-pause')).toBeInTheDocument(); }); it('should call pause when play button is clicked and playing', async () => { const user = userEvent.setup(); const mockPause = vi.fn(); vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, isPlaying: true, pause: mockPause, } as any); render(); const playPauseButton = screen.getByTestId('play-pause'); await user.click(playPauseButton); expect(mockPause).toHaveBeenCalled(); }); it('should call resume when play button is clicked and paused', async () => { const user = userEvent.setup(); const mockResume = vi.fn(); vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, isPlaying: false, resume: mockResume, } as any); render(); const playPauseButton = screen.getByTestId('play-pause'); await user.click(playPauseButton); expect(mockResume).toHaveBeenCalled(); }); it('should display next/previous buttons', () => { render(); expect(screen.getByTestId('next-previous')).toBeInTheDocument(); }); it('should call next when next button is clicked', async () => { const user = userEvent.setup(); const mockNext = vi.fn(); vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, next: mockNext, queue: [mockTrack, { ...mockTrack, id: 2 }], currentIndex: 0, } as any); render(); const nextButton = screen.getByText('Next'); await user.click(nextButton); expect(mockNext).toHaveBeenCalled(); }); it('should call previous when previous button is clicked', async () => { const user = userEvent.setup(); const mockPrevious = vi.fn(); vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, previous: mockPrevious, queue: [mockTrack, { ...mockTrack, id: 2 }], currentIndex: 1, } as any); render(); const previousButton = screen.getByText('Previous'); await user.click(previousButton); expect(mockPrevious).toHaveBeenCalled(); }); it('should display progress bar', () => { render(); expect(screen.getByTestId('progress-bar')).toBeInTheDocument(); }); it('should call seek when progress bar is clicked', async () => { const user = userEvent.setup(); const mockSeek = vi.fn(); vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, seek: mockSeek, } as any); render(); const progressBar = screen.getByTestId('progress-bar'); await user.click(progressBar); expect(mockSeek).toHaveBeenCalledWith(30); }); it('should display volume control', () => { render(); expect(screen.getByTestId('volume-control')).toBeInTheDocument(); }); it('should call toggle when toggle button is clicked', async () => { const user = userEvent.setup(); render(); const toggleButton = screen.getByLabelText('Agrandir le lecteur'); await user.click(toggleButton); expect(mockOnToggle).toHaveBeenCalled(); }); it('should display close button when onClose is provided', () => { render( , ); expect(screen.getByLabelText('Fermer le mini lecteur')).toBeInTheDocument(); }); it('should not display close button when onClose is not provided', () => { render(); expect( screen.queryByLabelText('Fermer le mini lecteur'), ).not.toBeInTheDocument(); }); it('should call onClose when close button is clicked', async () => { const user = userEvent.setup(); render( , ); const closeButton = screen.getByLabelText('Fermer le mini lecteur'); await user.click(closeButton); expect(mockOnClose).toHaveBeenCalled(); }); it('should have fixed position at bottom by default', () => { const { container } = render( , ); const miniPlayer = container.querySelector('[role="region"]'); expect(miniPlayer).toHaveClass('fixed', 'bottom-0'); }); it('should have fixed position at top when position prop is top', () => { const { container } = render( , ); const miniPlayer = container.querySelector('[role="region"]'); expect(miniPlayer).toHaveClass('fixed', 'top-0'); expect(miniPlayer).not.toHaveClass('bottom-0'); }); it('should apply custom className', () => { const { container } = render( , ); const miniPlayer = container.querySelector('[role="region"]'); expect(miniPlayer).toHaveClass('custom-class'); }); it('should disable next button when cannot go next', () => { vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, queue: [mockTrack], currentIndex: 0, } as any); render(); const nextButton = screen.getByText('Next'); expect(nextButton).toBeDisabled(); }); it('should disable previous button when cannot go previous', () => { vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, queue: [mockTrack], currentIndex: 0, } as any); render(); const previousButton = screen.getByText('Previous'); expect(previousButton).toBeDisabled(); }); it('should enable next button when can go next', () => { vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, queue: [mockTrack, { ...mockTrack, id: 2 }], currentIndex: 0, } as any); render(); const nextButton = screen.getByText('Next'); expect(nextButton).not.toBeDisabled(); }); it('should enable previous button when can go previous', () => { vi.mocked(usePlayer).mockReturnValue({ ...defaultPlayerState, queue: [mockTrack, { ...mockTrack, id: 2 }], currentIndex: 1, } as any); render(); const previousButton = screen.getByText('Previous'); expect(previousButton).not.toBeDisabled(); }); });