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