309 lines
10 KiB
TypeScript
309 lines
10 KiB
TypeScript
|
|
import {
|
||
|
|
fireEvent,
|
||
|
|
render,
|
||
|
|
} from '@testing-library/svelte';
|
||
|
|
import {
|
||
|
|
beforeEach,
|
||
|
|
describe,
|
||
|
|
expect,
|
||
|
|
it,
|
||
|
|
vi,
|
||
|
|
} from 'vitest';
|
||
|
|
import ComboControl from './ComboControl.svelte';
|
||
|
|
|
||
|
|
describe('ComboControl', () => {
|
||
|
|
const onChangeMock = vi.fn() as (value: number) => void;
|
||
|
|
const onIncreaseMock = vi.fn() as () => void;
|
||
|
|
const onDecreaseMock = vi.fn() as () => void;
|
||
|
|
|
||
|
|
it('renders with default values', () => {
|
||
|
|
const { container } = render(ComboControl, {
|
||
|
|
value: 50,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Check that the control button displays the value
|
||
|
|
const controlButton = container.querySelector(
|
||
|
|
'button[variant="outline"][size="icon"]:nth-child(2)',
|
||
|
|
);
|
||
|
|
expect(controlButton?.textContent).toBe('50');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('renders with custom min/max/step', () => {
|
||
|
|
const { container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 10,
|
||
|
|
step: 0.5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
});
|
||
|
|
|
||
|
|
const controlButton = container.querySelector(
|
||
|
|
'button[variant="outline"][size="icon"]:nth-child(2)',
|
||
|
|
);
|
||
|
|
expect(controlButton?.textContent).toBe('5');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('calls onIncrease when increase button is clicked', async () => {
|
||
|
|
const { getByLabelText } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
increaseLabel: 'Increase value',
|
||
|
|
});
|
||
|
|
|
||
|
|
const increaseButton = getByLabelText('Increase value');
|
||
|
|
await fireEvent.click(increaseButton);
|
||
|
|
|
||
|
|
expect(onIncreaseMock).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('calls onDecrease when decrease button is clicked', async () => {
|
||
|
|
const { getByLabelText } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
decreaseLabel: 'Decrease value',
|
||
|
|
});
|
||
|
|
|
||
|
|
const decreaseButton = getByLabelText('Decrease value');
|
||
|
|
await fireEvent.click(decreaseButton);
|
||
|
|
|
||
|
|
expect(onDecreaseMock).toHaveBeenCalledTimes(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('disables increase button when increaseDisabled is true', () => {
|
||
|
|
const { getByLabelText } = render(ComboControl, {
|
||
|
|
value: 100,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 100,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
increaseDisabled: true,
|
||
|
|
increaseLabel: 'Increase value',
|
||
|
|
});
|
||
|
|
|
||
|
|
const increaseButton = getByLabelText('Increase value');
|
||
|
|
expect(increaseButton).toBeDisabled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('disables decrease button when decreaseDisabled is true', () => {
|
||
|
|
const { getByLabelText } = render(ComboControl, {
|
||
|
|
value: 0,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 100,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
decreaseDisabled: true,
|
||
|
|
decreaseLabel: 'Decrease value',
|
||
|
|
});
|
||
|
|
|
||
|
|
const decreaseButton = getByLabelText('Decrease value');
|
||
|
|
expect(decreaseButton).toBeDisabled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('opens popover when control button is clicked', async () => {
|
||
|
|
const { getByLabelText, queryByRole } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Initially, popover content should not be visible
|
||
|
|
expect(queryByRole('dialog')).not.toBeInTheDocument();
|
||
|
|
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// After clicking, popover content should be visible
|
||
|
|
expect(queryByRole('dialog')).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('updates value when slider changes', async () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 10,
|
||
|
|
step: 1,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find slider - the Slider component should render an input with role slider
|
||
|
|
const slider = container.querySelector('[role="slider"]');
|
||
|
|
expect(slider).toBeInTheDocument();
|
||
|
|
|
||
|
|
// Simulate slider change
|
||
|
|
await fireEvent.input(slider!, { target: { value: '7' } });
|
||
|
|
|
||
|
|
expect(onChangeMock).toHaveBeenCalledWith(7);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('updates value when number input changes', async () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 10,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find number input
|
||
|
|
const input = container.querySelector('input[type="text"], input[type="number"]');
|
||
|
|
expect(input).toBeInTheDocument();
|
||
|
|
|
||
|
|
// Simulate input change
|
||
|
|
await fireEvent.change(input!, { target: { value: '8' } });
|
||
|
|
|
||
|
|
expect(onChangeMock).toHaveBeenCalledWith(8);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('respects min and max values on input', () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 10,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find input
|
||
|
|
const input = container.querySelector('input[type="text"], input[type="number"]');
|
||
|
|
|
||
|
|
// Check min and max attributes
|
||
|
|
expect(input).toHaveAttribute('min', '0');
|
||
|
|
expect(input).toHaveAttribute('max', '10');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('uses custom aria-labels', () => {
|
||
|
|
const { getByLabelText } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
increaseLabel: 'Increase by step',
|
||
|
|
decreaseLabel: 'Decrease by step',
|
||
|
|
controlLabel: 'Change value',
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(getByLabelText('Increase by step')).toBeInTheDocument();
|
||
|
|
expect(getByLabelText('Decrease by step')).toBeInTheDocument();
|
||
|
|
expect(getByLabelText('Change value')).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('uses default min/max/step values when not provided', () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 50,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find input
|
||
|
|
const input = container.querySelector('input[type="text"], input[type="number"]');
|
||
|
|
|
||
|
|
// Check default values (0, 100, 1)
|
||
|
|
expect(input).toHaveAttribute('min', '0');
|
||
|
|
expect(input).toHaveAttribute('max', '100');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('does not call onChange when input value is invalid', async () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find input
|
||
|
|
const input = container.querySelector('input[type="text"], input[type="number"]');
|
||
|
|
|
||
|
|
// Simulate invalid input
|
||
|
|
await fireEvent.change(input!, { target: { value: 'invalid' } });
|
||
|
|
|
||
|
|
expect(onChangeMock).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('displays current value in input field', async () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 42,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find input
|
||
|
|
const input = container.querySelector('input[type="text"], input[type="number"]');
|
||
|
|
|
||
|
|
expect(input).toHaveValue('42');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles step value for slider precision', async () => {
|
||
|
|
const { getByLabelText, container } = render(ComboControl, {
|
||
|
|
value: 5,
|
||
|
|
minValue: 0,
|
||
|
|
maxValue: 10,
|
||
|
|
step: 0.25,
|
||
|
|
onChange: onChangeMock,
|
||
|
|
onIncrease: onIncreaseMock,
|
||
|
|
onDecrease: onDecreaseMock,
|
||
|
|
controlLabel: 'Control value',
|
||
|
|
});
|
||
|
|
|
||
|
|
// Open popover
|
||
|
|
const controlButton = getByLabelText('Control value');
|
||
|
|
await fireEvent.click(controlButton);
|
||
|
|
|
||
|
|
// Find slider
|
||
|
|
const slider = container.querySelector('[role="slider"]');
|
||
|
|
|
||
|
|
// Simulate slider change
|
||
|
|
await fireEvent.input(slider!, { target: { value: '5.5' } });
|
||
|
|
|
||
|
|
expect(onChangeMock).toHaveBeenCalledWith(5.5);
|
||
|
|
});
|
||
|
|
});
|