File size: 6,410 Bytes
44a2550 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | /**
* Tests for PlaybackControls component.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import PlaybackControls from '../../components/PlaybackControls';
describe('PlaybackControls Component', () => {
const mockOnPlay = vi.fn();
const mockOnPause = vi.fn();
const mockOnStop = vi.fn();
const mockOnTempoChange = vi.fn();
const mockOnSeek = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('should render play, pause, and stop buttons', () => {
render(<PlaybackControls />);
expect(screen.getByLabelText(/play/i)).toBeInTheDocument();
expect(screen.getByLabelText(/pause/i)).toBeInTheDocument();
expect(screen.getByLabelText(/stop/i)).toBeInTheDocument();
});
it('should call onPlay when play button clicked', async () => {
const user = userEvent.setup();
render(<PlaybackControls onPlay={mockOnPlay} />);
const playButton = screen.getByLabelText(/play/i);
await user.click(playButton);
expect(mockOnPlay).toHaveBeenCalledTimes(1);
});
it('should call onPause when pause button clicked', async () => {
const user = userEvent.setup();
render(<PlaybackControls onPause={mockOnPause} isPlaying />);
const pauseButton = screen.getByLabelText(/pause/i);
await user.click(pauseButton);
expect(mockOnPause).toHaveBeenCalledTimes(1);
});
it('should call onStop when stop button clicked', async () => {
const user = userEvent.setup();
render(<PlaybackControls onStop={mockOnStop} />);
const stopButton = screen.getByLabelText(/stop/i);
await user.click(stopButton);
expect(mockOnStop).toHaveBeenCalledTimes(1);
});
it('should disable play button when already playing', () => {
render(<PlaybackControls isPlaying />);
const playButton = screen.getByLabelText(/play/i);
expect(playButton).toBeDisabled();
});
it('should show pause button only when playing', () => {
const { rerender } = render(<PlaybackControls isPlaying={false} />);
const pauseButton = screen.getByLabelText(/pause/i);
expect(pauseButton).toBeDisabled();
rerender(<PlaybackControls isPlaying />);
expect(pauseButton).not.toBeDisabled();
});
it('should render tempo control', () => {
render(<PlaybackControls tempo={120} />);
expect(screen.getByLabelText(/tempo/i)).toBeInTheDocument();
expect(screen.getByDisplayValue('120')).toBeInTheDocument();
});
it('should update tempo when slider moved', async () => {
render(<PlaybackControls tempo={120} onTempoChange={mockOnTempoChange} />);
const tempoSlider = screen.getByLabelText(/tempo/i);
fireEvent.change(tempoSlider, { target: { value: '140' } });
expect(mockOnTempoChange).toHaveBeenCalledWith(140);
});
it('should enforce tempo min and max bounds', () => {
render(<PlaybackControls tempo={120} minTempo={40} maxTempo={240} />);
const tempoSlider = screen.getByLabelText(/tempo/i) as HTMLInputElement;
expect(tempoSlider.min).toBe('40');
expect(tempoSlider.max).toBe('240');
});
it('should display current playback position', () => {
render(<PlaybackControls currentTime={30} duration={180} />);
// Should show time in format like 0:30 / 3:00
expect(screen.getByText(/0:30/)).toBeInTheDocument();
expect(screen.getByText(/3:00/)).toBeInTheDocument();
});
it('should render seek bar', () => {
render(<PlaybackControls currentTime={30} duration={180} />);
const seekBar = screen.getByRole('slider', { name: /seek/i });
expect(seekBar).toBeInTheDocument();
});
it('should seek when seek bar moved', async () => {
render(
<PlaybackControls
currentTime={30}
duration={180}
onSeek={mockOnSeek}
/>
);
const seekBar = screen.getByRole('slider', { name: /seek/i });
fireEvent.change(seekBar, { target: { value: '90' } });
expect(mockOnSeek).toHaveBeenCalledWith(90);
});
it('should show loading state when initializing', () => {
render(<PlaybackControls loading />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
expect(screen.getByLabelText(/play/i)).toBeDisabled();
});
it('should disable controls when no audio loaded', () => {
render(<PlaybackControls audioLoaded={false} />);
expect(screen.getByLabelText(/play/i)).toBeDisabled();
expect(screen.getByLabelText(/pause/i)).toBeDisabled();
expect(screen.getByLabelText(/stop/i)).toBeDisabled();
});
it('should show volume control', () => {
render(<PlaybackControls showVolumeControl />);
expect(screen.getByLabelText(/volume/i)).toBeInTheDocument();
});
it('should update volume when volume slider moved', async () => {
const mockOnVolumeChange = vi.fn();
render(
<PlaybackControls
showVolumeControl
volume={0.8}
onVolumeChange={mockOnVolumeChange}
/>
);
const volumeSlider = screen.getByLabelText(/volume/i);
fireEvent.change(volumeSlider, { target: { value: '0.5' } });
expect(mockOnVolumeChange).toHaveBeenCalledWith(0.5);
});
it('should toggle loop mode', async () => {
const user = userEvent.setup();
const mockOnLoopToggle = vi.fn();
render(<PlaybackControls onLoopToggle={mockOnLoopToggle} />);
const loopButton = screen.getByLabelText(/loop/i);
await user.click(loopButton);
expect(mockOnLoopToggle).toHaveBeenCalledWith(true);
});
it('should show loop indicator when loop enabled', () => {
render(<PlaybackControls loop />);
const loopButton = screen.getByLabelText(/loop/i);
expect(loopButton).toHaveClass(/active|enabled/);
});
it('should format time correctly', () => {
render(<PlaybackControls currentTime={125} duration={3665} />);
// 125 seconds = 2:05, 3665 seconds = 1:01:05
expect(screen.getByText(/2:05/)).toBeInTheDocument();
expect(screen.getByText(/1:01:05/)).toBeInTheDocument();
});
it('should support keyboard shortcuts', async () => {
const user = userEvent.setup();
render(
<PlaybackControls
onPlay={mockOnPlay}
onPause={mockOnPause}
supportKeyboardShortcuts
/>
);
// Spacebar should toggle play/pause
await user.keyboard(' ');
expect(mockOnPlay).toHaveBeenCalled();
});
});
|