import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { VolumeControl } from './VolumeControl'; describe('VolumeControl', () => { const mockOnVolumeChange = vi.fn(); const mockOnMuteToggle = vi.fn(); const defaultProps = { volume: 50, muted: false, onVolumeChange: mockOnVolumeChange, onMuteToggle: mockOnMuteToggle, }; beforeEach(() => { vi.clearAllMocks(); }); it('should render volume control', () => { render(); expect(screen.getByLabelText('Volume: 50%')).toBeInTheDocument(); expect(screen.getByRole('slider')).toBeInTheDocument(); }); it('should display volume slider', () => { const { container } = render(); const slider = screen.getByRole('slider'); expect(slider).toBeInTheDocument(); const volumeTrack = container.querySelector('[class*="bg-primary"]'); expect(volumeTrack).toHaveStyle({ width: '50%' }); }); it('should display mute button', () => { render(); const muteButton = screen.getByLabelText('Volume: 50%'); expect(muteButton).toBeInTheDocument(); }); it('should call onMuteToggle when mute button is clicked', async () => { const user = userEvent.setup(); render(); const muteButton = screen.getByLabelText('Volume: 50%'); await user.click(muteButton); expect(mockOnMuteToggle).toHaveBeenCalledTimes(1); }); it('should display VolumeX icon when muted', () => { render(); const muteButton = screen.getByLabelText('Volume muet'); expect(muteButton).toBeInTheDocument(); }); it('should display Volume1 icon when volume is low', () => { render(); const muteButton = screen.getByLabelText('Volume: 30%'); expect(muteButton).toBeInTheDocument(); }); it('should display Volume2 icon when volume is high', () => { render(); const muteButton = screen.getByLabelText('Volume: 70%'); expect(muteButton).toBeInTheDocument(); }); it('should call onVolumeChange when slider is clicked', async () => { const { container } = render(); const slider = container.querySelector('[role="slider"]') as HTMLElement; const mockRect = { left: 0, top: 0, width: 100, height: 10, bottom: 10, right: 100, x: 0, y: 0, toJSON: vi.fn(), }; vi.spyOn(slider, 'getBoundingClientRect').mockReturnValue( mockRect as DOMRect, ); fireEvent.mouseDown(slider, { clientX: 75, }); await waitFor(() => { expect(mockOnVolumeChange).toHaveBeenCalled(); }); const volumeCall = mockOnVolumeChange.mock.calls[0][0]; expect(volumeCall).toBeGreaterThan(70); expect(volumeCall).toBeLessThan(80); }); it('should handle drag interaction', async () => { const { container } = render(); const slider = container.querySelector('[role="slider"]') as HTMLElement; const mockRect = { left: 0, top: 0, width: 100, height: 10, bottom: 10, right: 100, x: 0, y: 0, toJSON: vi.fn(), }; vi.spyOn(slider, 'getBoundingClientRect').mockReturnValue( mockRect as DOMRect, ); fireEvent.mouseDown(slider, { clientX: 25 }); fireEvent.mouseMove(document, { clientX: 75 }); fireEvent.mouseUp(document); await waitFor(() => { expect(mockOnVolumeChange).toHaveBeenCalled(); }); }); it('should display volume value when showValue is true', () => { render(); expect(screen.getByText('50%')).toBeInTheDocument(); }); it('should display "Mute" when muted and showValue is true', () => { render(); expect(screen.getByText('Mute')).toBeInTheDocument(); }); it('should not display volume value when showValue is false', () => { render(); expect(screen.queryByText('50%')).not.toBeInTheDocument(); }); it('should hide slider when showSlider is false', () => { render(); expect(screen.queryByRole('slider')).not.toBeInTheDocument(); }); it('should be disabled when disabled prop is true', () => { render(); const muteButton = screen.getByLabelText('Volume: 50%'); expect(muteButton).toBeDisabled(); expect(muteButton).toHaveAttribute('aria-disabled', 'true'); const slider = screen.getByRole('slider'); expect(slider).toHaveAttribute('aria-disabled', 'true'); }); it('should not call callbacks when disabled', async () => { const user = userEvent.setup(); render(); const muteButton = screen.getByLabelText('Volume: 50%'); // Disabled buttons should not trigger callbacks fireEvent.click(muteButton); expect(mockOnMuteToggle).not.toHaveBeenCalled(); }); it('should have correct aria attributes', () => { render(); const slider = screen.getByRole('slider'); expect(slider).toHaveAttribute('aria-label', 'Volume'); expect(slider).toHaveAttribute('aria-valuemin', '0'); expect(slider).toHaveAttribute('aria-valuemax', '100'); expect(slider).toHaveAttribute('aria-valuenow', '50'); }); it('should show volume as 0 when muted', () => { const { container } = render( , ); const slider = screen.getByRole('slider'); expect(slider).toHaveAttribute('aria-valuenow', '0'); const volumeTrack = container.querySelector('[class*="bg-primary"]'); expect(volumeTrack).toHaveStyle({ width: '0%' }); }); it('should apply custom className', () => { const { container } = render( , ); expect(container.firstChild).toHaveClass('custom-class'); }); it('should handle volume at 0', () => { render(); const muteButton = screen.getByLabelText('Volume: 0%'); expect(muteButton).toBeInTheDocument(); }); it('should handle volume at 100', () => { render(); const muteButton = screen.getByLabelText('Volume: 100%'); expect(muteButton).toBeInTheDocument(); }); it('should clean up event listeners on unmount', () => { const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener'); const { container, unmount } = render(); const slider = container.querySelector('[role="slider"]') as HTMLElement; const mockRect = { left: 0, top: 0, width: 100, height: 10, bottom: 10, right: 100, x: 0, y: 0, toJSON: vi.fn(), }; vi.spyOn(slider, 'getBoundingClientRect').mockReturnValue( mockRect as DOMRect, ); fireEvent.mouseDown(slider); fireEvent.mouseUp(document); unmount(); expect(removeEventListenerSpy).toHaveBeenCalled(); }); });