import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import { RangeSlider } from './range-slider';
describe('RangeSlider', () => {
it('renders one thumb per value', () => {
render();
expect(screen.getAllByRole('slider')).toHaveLength(2);
});
it('renders a single thumb by default', () => {
render();
expect(screen.getAllByRole('slider')).toHaveLength(1);
});
it('exposes the value range to assistive tech', () => {
render();
const thumb = screen.getByRole('slider');
expect(thumb).toHaveAttribute('aria-valuenow', '40');
expect(thumb).toHaveAttribute('aria-valuemin', '10');
expect(thumb).toHaveAttribute('aria-valuemax', '90');
});
describe('accessible names', () => {
it('uses thumbLabels in order', () => {
render();
expect(screen.getByRole('slider', { name: 'Minimum' })).toBeInTheDocument();
expect(screen.getByRole('slider', { name: 'Maximum' })).toBeInTheDocument();
});
it('gives a lone thumb the slider’s own label', () => {
render();
expect(screen.getByRole('slider', { name: 'Volume' })).toBeInTheDocument();
});
it('suffixes the position when several thumbs share one label', () => {
render();
expect(screen.getByRole('slider', { name: 'Price 1' })).toBeInTheDocument();
expect(screen.getByRole('slider', { name: 'Price 2' })).toBeInTheDocument();
});
});
describe('keyboard', () => {
it('steps the focused thumb with the arrow keys', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render(
);
await user.tab();
await user.keyboard('{ArrowRight}');
expect(onValueChange).toHaveBeenLastCalledWith([41]);
await user.keyboard('{ArrowLeft}{ArrowLeft}');
expect(onValueChange).toHaveBeenLastCalledWith([39]);
});
it('honours the step size', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render(
);
await user.tab();
await user.keyboard('{ArrowRight}');
expect(onValueChange).toHaveBeenLastCalledWith([75]);
});
it('jumps to the bounds with Home and End', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render(
);
await user.tab();
await user.keyboard('{Home}');
expect(onValueChange).toHaveBeenLastCalledWith([0]);
await user.keyboard('{End}');
expect(onValueChange).toHaveBeenLastCalledWith([100]);
});
it('moves only the focused thumb of a range', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render(
);
/* Second tab lands on the upper thumb; the lower one must not shift. */
await user.tab();
await user.tab();
await user.keyboard('{ArrowRight}');
expect(onValueChange).toHaveBeenLastCalledWith([20, 61]);
});
});
it('stays put when disabled', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render(
);
await user.tab();
await user.keyboard('{ArrowRight}');
expect(onValueChange).not.toHaveBeenCalled();
});
it('holds a controlled value until the caller updates it', async () => {
const user = userEvent.setup();
const onValueChange = vi.fn();
render();
await user.tab();
await user.keyboard('{ArrowRight}');
expect(onValueChange).toHaveBeenCalledWith([41]);
expect(screen.getByRole('slider')).toHaveAttribute('aria-valuenow', '40');
});
describe('marks', () => {
it('renders the label, falling back to the value', () => {
render(
);
expect(screen.getByText('Low')).toBeInTheDocument();
expect(screen.getByText('100')).toBeInTheDocument();
});
it('positions each mark by its share of the scale', () => {
render(
);
expect(screen.getByText('Quarter')).toHaveStyle({ left: '25%' });
});
it('keeps marks out of the accessibility tree', () => {
const { container } = render(
);
expect(container.querySelector('[data-slot="range-slider-marks"]')).toHaveAttribute(
'aria-hidden'
);
});
});
});