veza/apps/web/src/features/player/components/RepeatShuffleButtons.test.tsx
2025-12-12 21:34:34 -05:00

342 lines
9.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RepeatShuffleButtons } from './RepeatShuffleButtons';
describe('RepeatShuffleButtons', () => {
const mockOnRepeatChange = vi.fn();
const mockOnShuffleToggle = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('should render both buttons', () => {
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
expect(screen.getByLabelText('Répéter désactivé')).toBeInTheDocument();
expect(screen.getByLabelText('Mélanger désactivé')).toBeInTheDocument();
});
it('should call onRepeatChange when repeat button is clicked', async () => {
const user = userEvent.setup();
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const repeatButton = screen.getByLabelText('Répéter désactivé');
await user.click(repeatButton);
expect(mockOnRepeatChange).toHaveBeenCalledWith('track');
expect(mockOnShuffleToggle).not.toHaveBeenCalled();
});
it('should cycle through repeat modes: off -> track -> playlist -> off', async () => {
const user = userEvent.setup();
const { rerender } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const repeatButton = screen.getByLabelText('Répéter désactivé');
// off -> track
await user.click(repeatButton);
expect(mockOnRepeatChange).toHaveBeenCalledWith('track');
// track -> playlist
rerender(
<RepeatShuffleButtons
repeat="track"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
await user.click(screen.getByLabelText('Répéter la piste (actif)'));
expect(mockOnRepeatChange).toHaveBeenCalledWith('playlist');
// playlist -> off
rerender(
<RepeatShuffleButtons
repeat="playlist"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
await user.click(screen.getByLabelText('Répéter la playlist (actif)'));
expect(mockOnRepeatChange).toHaveBeenCalledWith('off');
});
it('should call onShuffleToggle when shuffle button is clicked', async () => {
const user = userEvent.setup();
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const shuffleButton = screen.getByLabelText('Mélanger désactivé');
await user.click(shuffleButton);
expect(mockOnShuffleToggle).toHaveBeenCalledTimes(1);
expect(mockOnRepeatChange).not.toHaveBeenCalled();
});
it('should display active state for repeat when repeat is track', () => {
render(
<RepeatShuffleButtons
repeat="track"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const repeatButton = screen.getByLabelText('Répéter la piste (actif)');
expect(repeatButton).toHaveAttribute('aria-pressed', 'true');
expect(repeatButton).toHaveClass('bg-blue-600');
});
it('should display active state for repeat when repeat is playlist', () => {
render(
<RepeatShuffleButtons
repeat="playlist"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const repeatButton = screen.getByLabelText('Répéter la playlist (actif)');
expect(repeatButton).toHaveAttribute('aria-pressed', 'true');
expect(repeatButton).toHaveClass('bg-blue-600');
});
it('should display active state for shuffle when shuffle is true', () => {
render(
<RepeatShuffleButtons
repeat="off"
shuffle={true}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
const shuffleButton = screen.getByLabelText('Mélanger activé');
expect(shuffleButton).toHaveAttribute('aria-pressed', 'true');
expect(shuffleButton).toHaveClass('bg-blue-600');
});
it('should disable buttons when disabled prop is true', () => {
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
disabled={true}
/>,
);
const repeatButton = screen.getByLabelText('Répéter désactivé');
const shuffleButton = screen.getByLabelText('Mélanger désactivé');
expect(repeatButton).toBeDisabled();
expect(shuffleButton).toBeDisabled();
expect(repeatButton).toHaveAttribute('aria-disabled', 'true');
expect(shuffleButton).toHaveAttribute('aria-disabled', 'true');
});
it('should not call callbacks when buttons are disabled', async () => {
const user = userEvent.setup();
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
disabled={true}
/>,
);
const repeatButton = screen.getByLabelText('Répéter désactivé');
const shuffleButton = screen.getByLabelText('Mélanger désactivé');
await user.click(repeatButton);
await user.click(shuffleButton);
expect(mockOnRepeatChange).not.toHaveBeenCalled();
expect(mockOnShuffleToggle).not.toHaveBeenCalled();
});
it('should apply custom className', () => {
const { container } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
className="custom-class"
/>,
);
expect(container.firstChild).toHaveClass('custom-class');
});
it('should apply size classes', () => {
const { container: smContainer } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
size="sm"
/>,
);
const buttons = smContainer.querySelectorAll('button');
buttons.forEach((button) => {
expect(button).toHaveClass('h-8', 'w-8');
});
const { container: mdContainer } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
size="md"
/>,
);
const mdButtons = mdContainer.querySelectorAll('button');
mdButtons.forEach((button) => {
expect(button).toHaveClass('h-10', 'w-10');
});
const { container: lgContainer } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
size="lg"
/>,
);
const lgButtons = lgContainer.querySelectorAll('button');
lgButtons.forEach((button) => {
expect(button).toHaveClass('h-12', 'w-12');
});
});
it('should apply variant classes', () => {
const { container: defaultContainer } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
variant="default"
/>,
);
const buttons = defaultContainer.querySelectorAll('button');
buttons.forEach((button) => {
expect(button).toHaveClass('bg-blue-600');
});
const { container: ghostContainer } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
variant="ghost"
/>,
);
const ghostButtons = ghostContainer.querySelectorAll('button');
ghostButtons.forEach((button) => {
expect(button).toHaveClass('bg-transparent');
});
});
it('should have accessible labels', () => {
render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
expect(screen.getByLabelText('Répéter désactivé')).toBeInTheDocument();
expect(screen.getByLabelText('Mélanger désactivé')).toBeInTheDocument();
});
it('should show correct labels for different repeat states', () => {
const { rerender } = render(
<RepeatShuffleButtons
repeat="off"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
expect(screen.getByLabelText('Répéter désactivé')).toBeInTheDocument();
rerender(
<RepeatShuffleButtons
repeat="track"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
expect(
screen.getByLabelText('Répéter la piste (actif)'),
).toBeInTheDocument();
rerender(
<RepeatShuffleButtons
repeat="playlist"
shuffle={false}
onRepeatChange={mockOnRepeatChange}
onShuffleToggle={mockOnShuffleToggle}
/>,
);
expect(
screen.getByLabelText('Répéter la playlist (actif)'),
).toBeInTheDocument();
});
});