255 lines
7.4 KiB
TypeScript
255 lines
7.4 KiB
TypeScript
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(<VolumeControl {...defaultProps} />);
|
|
|
|
expect(screen.getByLabelText('Volume: 50%')).toBeInTheDocument();
|
|
expect(screen.getByRole('slider')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display volume slider', () => {
|
|
const { container } = render(<VolumeControl {...defaultProps} />);
|
|
|
|
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(<VolumeControl {...defaultProps} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume: 50%');
|
|
expect(muteButton).toBeInTheDocument();
|
|
});
|
|
|
|
it('should call onMuteToggle when mute button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(<VolumeControl {...defaultProps} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume: 50%');
|
|
await user.click(muteButton);
|
|
|
|
expect(mockOnMuteToggle).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should display VolumeX icon when muted', () => {
|
|
render(<VolumeControl {...defaultProps} muted={true} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume muet');
|
|
expect(muteButton).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display Volume1 icon when volume is low', () => {
|
|
render(<VolumeControl {...defaultProps} volume={30} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume: 30%');
|
|
expect(muteButton).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display Volume2 icon when volume is high', () => {
|
|
render(<VolumeControl {...defaultProps} volume={70} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume: 70%');
|
|
expect(muteButton).toBeInTheDocument();
|
|
});
|
|
|
|
it('should call onVolumeChange when slider is clicked', async () => {
|
|
const { container } = render(<VolumeControl {...defaultProps} />);
|
|
|
|
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(<VolumeControl {...defaultProps} />);
|
|
|
|
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(<VolumeControl {...defaultProps} showValue={true} />);
|
|
|
|
expect(screen.getByText('50%')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display "Mute" when muted and showValue is true', () => {
|
|
render(<VolumeControl {...defaultProps} muted={true} showValue={true} />);
|
|
|
|
expect(screen.getByText('Mute')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not display volume value when showValue is false', () => {
|
|
render(<VolumeControl {...defaultProps} showValue={false} />);
|
|
|
|
expect(screen.queryByText('50%')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should hide slider when showSlider is false', () => {
|
|
render(<VolumeControl {...defaultProps} showSlider={false} />);
|
|
|
|
expect(screen.queryByRole('slider')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should be disabled when disabled prop is true', () => {
|
|
render(<VolumeControl {...defaultProps} disabled={true} />);
|
|
|
|
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(<VolumeControl {...defaultProps} disabled={true} />);
|
|
|
|
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(<VolumeControl {...defaultProps} />);
|
|
|
|
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(
|
|
<VolumeControl {...defaultProps} muted={true} />,
|
|
);
|
|
|
|
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(
|
|
<VolumeControl {...defaultProps} className="custom-class" />,
|
|
);
|
|
|
|
expect(container.firstChild).toHaveClass('custom-class');
|
|
});
|
|
|
|
it('should handle volume at 0', () => {
|
|
render(<VolumeControl {...defaultProps} volume={0} />);
|
|
|
|
const muteButton = screen.getByLabelText('Volume: 0%');
|
|
expect(muteButton).toBeInTheDocument();
|
|
});
|
|
|
|
it('should handle volume at 100', () => {
|
|
render(<VolumeControl {...defaultProps} volume={100} />);
|
|
|
|
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(<VolumeControl {...defaultProps} />);
|
|
|
|
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();
|
|
});
|
|
});
|