import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { PlaybackSpeedControl } from './PlaybackSpeedControl'; import type { PlaybackSpeed } from './PlaybackSpeedControl'; describe('PlaybackSpeedControl', () => { const mockOnSpeedChange = vi.fn(); const defaultProps = { currentSpeed: 1 as PlaybackSpeed, onSpeedChange: mockOnSpeedChange, }; beforeEach(() => { vi.clearAllMocks(); }); it('should render playback speed control', () => { render(); expect(screen.getByLabelText('Vitesse de lecture: 1x')).toBeInTheDocument(); }); it('should display current speed', () => { render(); expect(screen.getByText('1.5x')).toBeInTheDocument(); }); it('should open dropdown when button is clicked', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { expect(screen.getByRole('listbox')).toBeInTheDocument(); }); }); it('should close dropdown when clicking outside', async () => { const user = userEvent.setup(); render(
Outside
, ); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { expect(screen.getByRole('listbox')).toBeInTheDocument(); }); const outside = screen.getByTestId('outside'); await user.click(outside); await waitFor(() => { expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); }); it('should call onSpeedChange when speed is selected', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { expect(screen.getByRole('listbox')).toBeInTheDocument(); }); const speed15x = screen.getByText('1.5x'); await user.click(speed15x); expect(mockOnSpeedChange).toHaveBeenCalledWith(1.5); }); it('should close dropdown after selecting speed', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { expect(screen.getByRole('listbox')).toBeInTheDocument(); }); const speed15x = screen.getByText('1.5x'); await user.click(speed15x); await waitFor(() => { expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); }); it('should display all speed options', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); const listbox = screen.getByRole('listbox'); expect(listbox).toHaveTextContent('0.5x'); expect(listbox).toHaveTextContent('0.75x'); expect(listbox).toHaveTextContent('1x'); expect(listbox).toHaveTextContent('1.25x'); expect(listbox).toHaveTextContent('1.5x'); expect(listbox).toHaveTextContent('1.75x'); expect(listbox).toHaveTextContent('2x'); }); it('should highlight current speed', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1.5x'); await user.click(button); await waitFor(() => { const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); const listbox = screen.getByRole('listbox'); const speed15xOption = Array.from( listbox.querySelectorAll('[role="option"]'), ).find((option) => option.textContent?.includes('1.5x')); expect(speed15xOption).toHaveAttribute('aria-selected', 'true'); }); it('should show check icon for current speed', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1.5x'); await user.click(button); await waitFor(() => { const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); const listbox = screen.getByRole('listbox'); const checkIcon = listbox.querySelector('svg[class*="text-muted"]'); expect(checkIcon).toBeInTheDocument(); }); it('should filter available speeds', async () => { const user = userEvent.setup(); const { container } = render( , ); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); await waitFor(() => { const listbox = screen.getByRole('listbox'); expect(listbox).toBeInTheDocument(); }); const listbox = screen.getByRole('listbox'); const options = listbox.querySelectorAll('[role="option"]'); expect(options).toHaveLength(4); expect(listbox).toHaveTextContent('0.5x'); expect(listbox).toHaveTextContent('1x'); expect(listbox).toHaveTextContent('1.5x'); expect(listbox).toHaveTextContent('2x'); expect(listbox).not.toHaveTextContent('0.75x'); expect(listbox).not.toHaveTextContent('1.25x'); expect(listbox).not.toHaveTextContent('1.75x'); }); it('should be disabled when disabled prop is true', () => { render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); expect(button).toBeDisabled(); expect(button).toHaveAttribute('aria-disabled', 'true'); }); it('should not open dropdown when disabled', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); await user.click(button); expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); it('should apply custom className', () => { const { container } = render( , ); expect(container.firstChild).toHaveClass('custom-class'); }); it('should have accessible attributes', () => { render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); expect(button).toHaveAttribute('aria-expanded', 'false'); expect(button).toHaveAttribute('aria-haspopup', 'listbox'); }); it('should update aria-expanded when dropdown opens', async () => { const user = userEvent.setup(); render(); const button = screen.getByLabelText('Vitesse de lecture: 1x'); expect(button).toHaveAttribute('aria-expanded', 'false'); await user.click(button); await waitFor(() => { expect(screen.getByRole('listbox')).toBeInTheDocument(); expect(button).toHaveAttribute('aria-expanded', 'true'); }); }); it('should handle all speed values', () => { const { rerender } = render( , ); expect(screen.getByText('0.5x')).toBeInTheDocument(); rerender(); expect(screen.getByText('0.75x')).toBeInTheDocument(); rerender(); expect(screen.getByText('1x')).toBeInTheDocument(); rerender(); expect(screen.getByText('1.25x')).toBeInTheDocument(); rerender(); expect(screen.getByText('1.5x')).toBeInTheDocument(); rerender(); expect(screen.getByText('1.75x')).toBeInTheDocument(); rerender(); expect(screen.getByText('2x')).toBeInTheDocument(); }); });