veza/apps/web/src/features/player/components/MiniPlayer.test.tsx

389 lines
11 KiB
TypeScript
Raw Normal View History

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 }) => (
<div data-testid="track-info">{track?.title || 'No track'}</div>
),
}));
vi.mock('./PlayPauseButton', () => ({
2025-12-13 02:34:34 +00:00
PlayPauseButton: ({
isPlaying,
onClick,
}: {
isPlaying: boolean;
onClick: () => void;
}) => (
<button data-testid="play-pause" onClick={onClick}>
{isPlaying ? 'Pause' : 'Play'}
</button>
),
}));
vi.mock('./NextPreviousButtons', () => ({
NextPreviousButtons: ({
onNext,
onPrevious,
canGoNext,
canGoPrevious,
}: {
onNext: () => void;
onPrevious: () => void;
canGoNext: boolean;
canGoPrevious: boolean;
}) => (
<div data-testid="next-previous">
<button onClick={onPrevious} disabled={!canGoPrevious}>
Previous
</button>
<button onClick={onNext} disabled={!canGoNext}>
Next
</button>
</div>
),
}));
vi.mock('./ProgressBar', () => ({
ProgressBar: ({
currentTime,
duration,
onSeek,
}: {
currentTime: number;
duration: number;
onSeek: (time: number) => void;
}) => (
<div data-testid="progress-bar" onClick={() => onSeek(30)}>
{currentTime} / {duration}
</div>
),
}));
vi.mock('./VolumeControl', () => ({
VolumeControl: ({
volume,
muted,
onVolumeChange,
onMuteToggle,
}: {
volume: number;
muted: boolean;
onVolumeChange: (vol: number) => void;
onMuteToggle: () => void;
}) => (
<div data-testid="volume-control">
<button onClick={onMuteToggle}>{muted ? 'Unmute' : 'Mute'}</button>
<button onClick={() => onVolumeChange(50)}>Set Volume</button>
</div>
),
}));
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(<MiniPlayer isVisible={false} onToggle={mockOnToggle} />);
2025-12-13 02:34:34 +00:00
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
2025-12-13 02:34:34 +00:00
expect(
screen.queryByRole('region', { name: 'Mini lecteur audio' }),
).not.toBeInTheDocument();
});
it('should render when visible and track is playing', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
2025-12-13 02:34:34 +00:00
expect(
screen.getByRole('region', { name: 'Mini lecteur audio' }),
).toBeInTheDocument();
});
it('should display track info', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
expect(screen.getByTestId('track-info')).toBeInTheDocument();
expect(screen.getByText('Test Track')).toBeInTheDocument();
});
it('should display play/pause button', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
const playPauseButton = screen.getByTestId('play-pause');
await user.click(playPauseButton);
expect(mockResume).toHaveBeenCalled();
});
it('should display next/previous buttons', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
const previousButton = screen.getByText('Previous');
await user.click(previousButton);
expect(mockPrevious).toHaveBeenCalled();
});
it('should display progress bar', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
const progressBar = screen.getByTestId('progress-bar');
await user.click(progressBar);
expect(mockSeek).toHaveBeenCalledWith(30);
});
it('should display volume control', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
expect(screen.getByTestId('volume-control')).toBeInTheDocument();
});
it('should call toggle when toggle button is clicked', async () => {
const user = userEvent.setup();
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
const toggleButton = screen.getByLabelText('Agrandir le lecteur');
await user.click(toggleButton);
expect(mockOnToggle).toHaveBeenCalled();
});
it('should display close button when onClose is provided', () => {
2025-12-13 02:34:34 +00:00
render(
<MiniPlayer
isVisible={true}
onToggle={mockOnToggle}
onClose={mockOnClose}
/>,
);
expect(screen.getByLabelText('Fermer le mini lecteur')).toBeInTheDocument();
});
it('should not display close button when onClose is not provided', () => {
render(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
2025-12-13 02:34:34 +00:00
expect(
screen.queryByLabelText('Fermer le mini lecteur'),
).not.toBeInTheDocument();
});
it('should call onClose when close button is clicked', async () => {
const user = userEvent.setup();
2025-12-13 02:34:34 +00:00
render(
<MiniPlayer
isVisible={true}
onToggle={mockOnToggle}
onClose={mockOnClose}
/>,
);
const closeButton = screen.getByLabelText('Fermer le mini lecteur');
await user.click(closeButton);
expect(mockOnClose).toHaveBeenCalled();
});
it('should have fixed position at bottom by default', () => {
2025-12-13 02:34:34 +00:00
const { container } = render(
<MiniPlayer isVisible={true} onToggle={mockOnToggle} />,
);
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(
2025-12-13 02:34:34 +00:00
<MiniPlayer isVisible={true} onToggle={mockOnToggle} position="top" />,
);
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(
2025-12-13 02:34:34 +00:00
<MiniPlayer
isVisible={true}
onToggle={mockOnToggle}
className="custom-class"
/>,
);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
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(<MiniPlayer isVisible={true} onToggle={mockOnToggle} />);
const previousButton = screen.getByText('Previous');
expect(previousButton).not.toBeDisabled();
});
});