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();
});
});