diff --git a/src/shared/ui/ComboControl/ComboControl.svelte.test.ts b/src/shared/ui/ComboControl/ComboControl.svelte.test.ts index d8d1298..f866a7a 100644 --- a/src/shared/ui/ComboControl/ComboControl.svelte.test.ts +++ b/src/shared/ui/ComboControl/ComboControl.svelte.test.ts @@ -1,38 +1,3 @@ -/** - * Test Suite for ComboControl Component - * - * IMPORTANT: These tests require a proper browser environment to run. - * - * Svelte 5's $state() and $effect() runes do not work in jsdom (server-side simulation). - * The current vitest.config.component.ts uses 'environment: jsdom', which doesn't support Svelte 5 reactivity. - * - * To run these tests, you need to: - * 1. Update vitest to use browser-based testing with @vitest/browser-playwright - * 2. OR use Playwright E2E tests in e2e/ComboControl.e2e.test.ts - * - * To run E2E tests (recommended): - * ```bash - * yarn test:e2e ComboControl - * ``` - * - * This suite tests the actual Svelte component rendering, interactions, and behavior. - * Tests for the createTypographyControl helper function are in createTypographyControl.test.ts - * - * Test Coverage: - * 1. Component Rendering: Button labels, icons, and initial state - * 2. Button States: Disabled states based on isAtMin/isAtMax - * 3. Button Clicks: Increase/decrease button functionality - * 4. Popover Behavior: Opening/closing popover with slider and input - * 5. Slider Interaction: Dragging slider to update values - * 6. Input Field: Typing values directly - * 7. Accessibility: ARIA labels and keyboard navigation - * 8. Reactivity: Value updates propagating through the component - * 9. Edge Cases: Boundary conditions and special values - * - * Note: This file is intentionally left as-is with comprehensive @testing-library/svelte tests - * as a reference for when the browser environment is properly set up. - */ - import { createTypographyControl } from '$shared/lib'; import { fireEvent, @@ -40,837 +5,131 @@ import { screen, waitFor, } from '@testing-library/svelte'; -import { - describe, - expect, - it, -} from 'vitest'; import ComboControl from './ComboControl.svelte'; -describe('ComboControl Component', () => { - /** - * Helper function to create a TypographyControl for testing - */ - function createTestControl(initialValue: number, options?: { - min?: number; - max?: number; - step?: number; - }) { - return createTypographyControl({ - value: initialValue, - min: options?.min ?? 0, - max: options?.max ?? 100, - step: options?.step ?? 1, - }); - } +function makeControl(value: number, opts: { min?: number; max?: number; step?: number } = {}) { + return createTypographyControl({ + value, + min: opts.min ?? 0, + max: opts.max ?? 100, + step: opts.step ?? 1, + }); +} +describe('ComboControl', () => { describe('Rendering', () => { - it('renders all three buttons (decrease, control, increase)', () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - const buttons = screen.getAllByRole('button'); - expect(buttons).toHaveLength(3); + it('renders decrease and increase buttons', () => { + render(ComboControl, { control: makeControl(50) }); + expect(screen.getByLabelText('Decrease')).toBeInTheDocument(); + expect(screen.getByLabelText('Increase')).toBeInTheDocument(); }); - it('displays current value on control button', () => { - const control = createTestControl(42); - render(ComboControl, { - control, - }); - + it('renders the current integer value', () => { + render(ComboControl, { control: makeControl(42) }); expect(screen.getByText('42')).toBeInTheDocument(); }); - it('displays decimal values on control button', () => { - const control = createTestControl(12.5, { min: 0, max: 100, step: 0.5 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('12.5')).toBeInTheDocument(); + it('formats decimal value to 1 decimal place when step >= 0.1', () => { + render(ComboControl, { control: makeControl(1.5, { step: 0.1 }) }); + expect(screen.getByText('1.5')).toBeInTheDocument(); }); - it('applies custom ARIA labels to buttons', () => { - const control = createTestControl(50); + it('formats decimal value to 2 decimal places when step < 0.1', () => { + render(ComboControl, { control: makeControl(1.55, { step: 0.01 }) }); + expect(screen.getByText('1.55')).toBeInTheDocument(); + }); + + it('renders label when label prop is provided', () => { + render(ComboControl, { control: makeControl(16), label: 'Size' }); + expect(screen.getByText('Size')).toBeInTheDocument(); + }); + + it('applies custom aria-labels to buttons', () => { render(ComboControl, { - control, + control: makeControl(50), decreaseLabel: 'Decrease font size', - controlLabel: 'Font size control', increaseLabel: 'Increase font size', }); - expect(screen.getByLabelText('Decrease font size')).toBeInTheDocument(); - expect(screen.getByLabelText('Font size control')).toBeInTheDocument(); expect(screen.getByLabelText('Increase font size')).toBeInTheDocument(); }); + }); - it('renders decrease button with minus icon', () => { - const control = createTestControl(50); - const { container } = render(ComboControl, { - control, - }); - - const decreaseBtn = screen.getAllByRole('button')[0]; - expect(decreaseBtn).toBeInTheDocument(); - // Check for lucide icon SVG - const svg = container.querySelector('button svg'); - expect(svg).toBeInTheDocument(); + describe('Disabled state', () => { + it('disables decrease button when at min', () => { + render(ComboControl, { control: makeControl(0, { min: 0 }) }); + expect(screen.getByLabelText('Decrease')).toBeDisabled(); }); - it('renders increase button with plus icon', () => { - const control = createTestControl(50); - const { container } = render(ComboControl, { - control, - }); - - const increaseBtn = screen.getAllByRole('button')[2]; - expect(increaseBtn).toBeInTheDocument(); - // Check for lucide icon SVG - const svgs = container.querySelectorAll('button svg'); - expect(svgs.length).toBeGreaterThan(0); + it('disables increase button when at max', () => { + render(ComboControl, { control: makeControl(100, { max: 100 }) }); + expect(screen.getByLabelText('Increase')).toBeDisabled(); }); - it('handles zero value correctly', () => { - const control = createTestControl(0, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('0')).toBeInTheDocument(); - }); - - it('handles negative values correctly', () => { - const control = createTestControl(-5, { min: -10, max: 10 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('-5')).toBeInTheDocument(); + it('enables both buttons when value is within bounds', () => { + render(ComboControl, { control: makeControl(50, { min: 0, max: 100 }) }); + expect(screen.getByLabelText('Decrease')).not.toBeDisabled(); + expect(screen.getByLabelText('Increase')).not.toBeDisabled(); }); }); - describe('Button States', () => { - it('disables decrease button when at min value', () => { - const control = createTestControl(0, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const buttons = container.querySelectorAll('button'); - const decreaseBtn = buttons[0]; - expect(decreaseBtn).toBeDisabled(); - }); - - it('disables increase button when at max value', () => { - const control = createTestControl(100, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const buttons = container.querySelectorAll('button'); - const increaseBtn = buttons[2]; - expect(increaseBtn).toBeDisabled(); - }); - - it('both buttons enabled when within bounds', () => { - const control = createTestControl(50, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const buttons = container.querySelectorAll('button'); - expect(buttons[0]).not.toBeDisabled(); // decrease - expect(buttons[1]).not.toBeDisabled(); // control - expect(buttons[2]).not.toBeDisabled(); // increase - }); - - it('control button always enabled regardless of value', () => { - const control = createTestControl(0, { min: 0, max: 0 }); - const { container } = render(ComboControl, { - control, - }); - - const buttons = container.querySelectorAll('button'); - const controlBtn = buttons[1]; - expect(controlBtn).not.toBeDisabled(); - }); - }); - - describe('Button Clicks', () => { - it('decrease button reduces value by step', async () => { - const control = createTestControl(50, { min: 0, max: 100, step: 5 }); - render(ComboControl, { - control, - }); - - const decreaseBtn = screen.getAllByRole('button')[0]; - await fireEvent.click(decreaseBtn); - + describe('Click interactions', () => { + it('decreases value on decrease button click', async () => { + const control = makeControl(50, { step: 5 }); + render(ComboControl, { control }); + await fireEvent.click(screen.getByLabelText('Decrease')); expect(control.value).toBe(45); - await waitFor(() => { - expect(screen.getByText('45')).toBeInTheDocument(); - }); }); - it('increase button increases value by step', async () => { - const control = createTestControl(50, { min: 0, max: 100, step: 5 }); - render(ComboControl, { - control, - }); - - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - + it('increases value on increase button click', async () => { + const control = makeControl(50, { step: 5 }); + render(ComboControl, { control }); + await fireEvent.click(screen.getByLabelText('Increase')); expect(control.value).toBe(55); - await waitFor(() => { - expect(screen.getByText('55')).toBeInTheDocument(); - }); }); - it('value updates on control button after multiple clicks', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const buttons = screen.getAllByRole('button'); - const decreaseBtn = buttons[0]; - const increaseBtn = buttons[2]; - - await fireEvent.click(increaseBtn); - await fireEvent.click(increaseBtn); - await fireEvent.click(increaseBtn); - expect(control.value).toBe(53); - await waitFor(() => { - expect(screen.getByText('53')).toBeInTheDocument(); - }); - - await fireEvent.click(decreaseBtn); - expect(control.value).toBe(52); - await waitFor(() => { - expect(screen.getByText('52')).toBeInTheDocument(); - }); - }); - - it('decrease button does not go below min', async () => { - const control = createTestControl(1, { min: 0, max: 100, step: 5 }); - render(ComboControl, { - control, - }); - - const decreaseBtn = screen.getAllByRole('button')[0]; - await fireEvent.click(decreaseBtn); - + it('clamps decrease at min', async () => { + const control = makeControl(2, { min: 0, step: 5 }); + render(ComboControl, { control }); + await fireEvent.click(screen.getByLabelText('Decrease')); expect(control.value).toBe(0); - await waitFor(() => { - expect(screen.getByText('0')).toBeInTheDocument(); - }); }); - it('increase button does not go above max', async () => { - const control = createTestControl(99, { min: 0, max: 100, step: 5 }); - render(ComboControl, { - control, - }); - - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - + it('clamps increase at max', async () => { + const control = makeControl(98, { max: 100, step: 5 }); + render(ComboControl, { control }); + await fireEvent.click(screen.getByLabelText('Increase')); expect(control.value).toBe(100); - await waitFor(() => { - expect(screen.getByText('100')).toBeInTheDocument(); - }); }); - it('respects step precision on button clicks', async () => { - const control = createTestControl(5.5, { min: 0, max: 10, step: 0.25 }); - render(ComboControl, { - control, - }); - - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - - expect(control.value).toBeCloseTo(5.75); - await waitFor(() => { - expect(screen.getByText('5.75')).toBeInTheDocument(); - }); + it('updates displayed value after click', async () => { + const control = makeControl(50); + render(ComboControl, { control }); + await fireEvent.click(screen.getByLabelText('Increase')); + await waitFor(() => expect(screen.getByText('51')).toBeInTheDocument()); }); }); - describe('Popover Behavior', () => { - it('popover content not visible initially', () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - // Popover content should not be visible initially - const popover = screen.queryByTestId('combo-control-popover'); - expect(popover).not.toBeInTheDocument(); - - const sliderInput = screen.queryByRole('slider'); - expect(sliderInput).not.toBeInTheDocument(); - - const numberInput = screen.queryByTestId('combo-control-input'); - expect(numberInput).not.toBeInTheDocument(); - }); - - it('clicking control button toggles popover', async () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - - // Click to open popover - await fireEvent.click(controlBtn); - - // Wait for popover to render (it's portaled to body) - await waitFor(() => { - const popover = screen.getByTestId('combo-control-popover'); - expect(popover).toBeInTheDocument(); - }); - - await waitFor(() => { - const slider = screen.queryByRole('slider'); - expect(slider).toBeInTheDocument(); - }); - - await waitFor(() => { - const numberInput = screen.queryByTestId('combo-control-input'); - expect(numberInput).toBeInTheDocument(); - }); - }); - - it('popover contains slider and input', async () => { - const control = createTestControl(50, { min: 10, max: 90, step: 5 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - // Verify both slider and input are present - const slider = await screen.findByRole('slider'); - expect(slider).toBeInTheDocument(); - - const input = await screen.findByTestId('combo-control-input'); - expect(input).toBeInTheDocument(); - - // Both should show current value - const inputElement = input as HTMLInputElement; - expect(inputElement.value).toBe('50'); - }); - - it('popover contains input field with current value', async () => { - const control = createTestControl(42); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - await waitFor(async () => { - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - expect(input.value).toBe('42'); - }); - }); - - it('input field has min/max attributes', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - await waitFor(async () => { - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toHaveAttribute('min', '0'); - expect(input).toHaveAttribute('max', '100'); - }); + describe('Popover', () => { + it('opens popover with vertical slider on trigger click', async () => { + render(ComboControl, { control: makeControl(50), controlLabel: 'Size control' }); + expect(screen.queryByRole('slider')).not.toBeInTheDocument(); + await fireEvent.click(screen.getByLabelText('Size control')); + await waitFor(() => expect(screen.getByRole('slider')).toBeInTheDocument()); }); }); - describe('Slider Rendering', () => { - it('slider is present in popover', async () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - // Verify slider is present - const slider = await screen.findByRole('slider'); - expect(slider).toBeInTheDocument(); + describe('Reduced mode', () => { + it('renders horizontal slider directly without popover trigger', () => { + render(ComboControl, { control: makeControl(50), reduced: true }); + expect(screen.getByRole('slider')).toBeInTheDocument(); + expect(screen.queryByLabelText('Decrease')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Increase')).not.toBeInTheDocument(); }); - it('slider value syncs with control value', async () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - // Slider should be present and reflect initial value - const slider = await screen.findByRole('slider'); - expect(slider).toBeInTheDocument(); - - // Change value via input (which we know works) - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - await fireEvent.change(input, { target: { value: '75' } }); - await fireEvent.blur(input); - - // Slider should still be present (not re-rendered) - const sliderAfter = await screen.findByRole('slider'); - expect(sliderAfter).toBeInTheDocument(); - }); - }); - - describe('Input Field Interaction', () => { - it('typing valid number updates control value', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Type new value - await fireEvent.change(input, { target: { value: '75' } }); - await fireEvent.blur(input); // onchange fires on blur - - // Wait for control value to update - await waitFor(() => { - expect(control.value).toBe(75); - }); - - // Check that control button text updates - await waitFor(() => { - expect(screen.getByText('75')).toBeInTheDocument(); - }); - }); - - it('input respects step precision', async () => { - const control = createTestControl(5, { min: 0, max: 10, step: 0.25 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Type value with more precision than step allows (0.25 has 2 decimal places) - await fireEvent.change(input, { target: { value: '5.23' } }); - await fireEvent.blur(input); - - // Should be rounded to step precision (2 decimal places) - await waitFor(() => { - expect(control.value).toBeCloseTo(5.23, 1); - }); - }); - - it('input clamps to min', async () => { - const control = createTestControl(50, { min: 10, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Type below min - await fireEvent.change(input, { target: { value: '5' } }); - await fireEvent.blur(input); - - // Should be clamped to min - await waitFor(() => { - expect(control.value).toBe(10); - }); - }); - - it('input clamps to max', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Type above max - await fireEvent.change(input, { target: { value: '150' } }); - await fireEvent.blur(input); - - // Should be clamped to max - await waitFor(() => { - expect(control.value).toBe(100); - }); - }); - - it('rejects invalid input (non-numeric)', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const originalValue = control.value; - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Type invalid value - await fireEvent.change(input, { target: { value: 'abc' } }); - await fireEvent.blur(input); - - // Value should not change for invalid input - await waitFor(() => { - expect(control.value).toBe(originalValue); - }); - }); - - it('handles empty input gracefully', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const originalValue = control.value; - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - expect(input).toBeInTheDocument(); - - // Clear input - await fireEvent.change(input, { target: { value: '' } }); - await fireEvent.blur(input); - - // Value should not change for empty input - await waitFor(() => { - expect(control.value).toBe(originalValue); - }); - }); - }); - - describe('Reactivity', () => { - it('external control value change updates control button text', async () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - expect(screen.getByText('50')).toBeInTheDocument(); - - // Change value externally - control.value = 75; - - // Wait for UI to update - await waitFor(() => { - expect(screen.getByText('75')).toBeInTheDocument(); - }); - }); - - it('button states update when external value changes', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const buttons = container.querySelectorAll('button'); - - // Both should be enabled - expect(buttons[0]).not.toBeDisabled(); - expect(buttons[2]).not.toBeDisabled(); - - // Set to max - control.value = 100; - - // Wait for button state to update - await waitFor(() => { - expect(buttons[2]).toBeDisabled(); - }); - }); - - it('input and slider sync when external value changes', async () => { - const control = createTestControl(50, { min: 0, max: 100 }); - render(ComboControl, { - control, - }); - - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - // Both should be present - const _slider = await screen.findByRole('slider'); - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - - // Input should show initial value - expect(input.value).toBe('50'); - - // Change value externally - control.value = 75; - - // Wait for input to update - await waitFor(async () => { - const updatedInput = await screen.findByTestId( - 'combo-control-input', - ) as HTMLInputElement; - expect(updatedInput.value).toBe('75'); - }); - - // Slider should still be present - const updatedSlider = await screen.findByRole('slider'); - expect(updatedSlider).toBeInTheDocument(); - }); - - it('decrease button becomes enabled when value increases externally', async () => { - const control = createTestControl(0, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const decreaseBtn = container.querySelectorAll('button')[0]; - - // Initially disabled - expect(decreaseBtn).toBeDisabled(); - - // Increase value externally - control.value = 10; - - // Wait for button to become enabled - await waitFor(() => { - expect(decreaseBtn).not.toBeDisabled(); - }); - }); - - it('increase button becomes enabled when value decreases externally', async () => { - const control = createTestControl(100, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const increaseBtn = container.querySelectorAll('button')[2]; - - // Initially disabled - expect(increaseBtn).toBeDisabled(); - - // Decrease value externally - control.value = 90; - - // Wait for button to become enabled - await waitFor(() => { - expect(increaseBtn).not.toBeDisabled(); - }); - }); - }); - - describe('Edge Cases', () => { - it('handles equal min and max', () => { - const control = createTestControl(5, { min: 5, max: 5 }); - render(ComboControl, { - control, - }); - - // Should render without errors - expect(screen.getByText('5')).toBeInTheDocument(); - - // Both decrease and increase should be disabled - const { container } = render(ComboControl, { - control, - }); - const buttons = container.querySelectorAll('button'); - expect(buttons[0]).toBeDisabled(); - expect(buttons[2]).toBeDisabled(); - }); - - it('handles very small step values', () => { - const control = createTestControl(5, { min: 0, max: 10, step: 0.001 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('5')).toBeInTheDocument(); - }); - - it('handles negative range with positive and negative values', async () => { - const control = createTestControl(-5, { min: -10, max: 10, step: 1 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('-5')).toBeInTheDocument(); - - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - - expect(control.value).toBe(-4); - }); - - it('handles zero as min value', async () => { - const control = createTestControl(0, { min: 0, max: 10 }); - const { container } = render(ComboControl, { - control, - }); - - expect(screen.getByText('0')).toBeInTheDocument(); - - const decreaseBtn = container.querySelectorAll('button')[0]; - expect(decreaseBtn).toBeDisabled(); - }); - - it('handles large step value', async () => { - const control = createTestControl(5, { min: 0, max: 100, step: 50 }); - render(ComboControl, { - control, - }); - - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - - // Should jump by 50 - expect(control.value).toBe(55); - - await fireEvent.click(increaseBtn); - expect(control.value).toBe(100); // Clamped to max - }); - }); - - describe('Accessibility', () => { - it('all buttons have aria-label when provided', () => { - const control = createTestControl(50); - render(ComboControl, { - control, - decreaseLabel: 'Decrease value', - controlLabel: 'Current value', - increaseLabel: 'Increase value', - }); - - expect(screen.getByLabelText('Decrease value')).toBeInTheDocument(); - expect(screen.getByLabelText('Current value')).toBeInTheDocument(); - expect(screen.getByLabelText('Increase value')).toBeInTheDocument(); - }); - - it('buttons are keyboard accessible', async () => { - const control = createTestControl(50); - render(ComboControl, { - control, - }); - - const buttons = screen.getAllByRole('button'); - expect(buttons).toHaveLength(3); - - // All buttons should be focusable - buttons.forEach(btn => { - expect(btn).not.toHaveAttribute('disabled'); - }); - }); - - it('disabled buttons are properly marked', () => { - const control = createTestControl(0, { min: 0, max: 100 }); - const { container } = render(ComboControl, { - control, - }); - - const decreaseBtn = container.querySelectorAll('button')[0]; - expect(decreaseBtn).toBeDisabled(); - }); - }); - - describe('Integration Scenarios', () => { - it('typical font size control workflow', async () => { - const control = createTestControl(16, { min: 12, max: 72, step: 1 }); - render(ComboControl, { - control, - controlLabel: 'Font size', - decreaseLabel: 'Decrease font size', - increaseLabel: 'Increase font size', - }); - - // Initial state - expect(screen.getByText('16')).toBeInTheDocument(); - - // Increase via button - const increaseBtn = screen.getByTestId('increase-button'); - await fireEvent.click(increaseBtn); - expect(control.value).toBe(17); - - // Decrease via button - const decreaseBtn = screen.getByTestId('decrease-button'); - await fireEvent.click(decreaseBtn); - expect(control.value).toBe(16); - - // Open popover and use input - const controlBtn = screen.getByTestId('combo-control-value'); - await fireEvent.click(controlBtn); - - const input = await screen.findByTestId('combo-control-input') as HTMLInputElement; - await fireEvent.change(input, { target: { value: '24' } }); - await fireEvent.blur(input); - - expect(control.value).toBe(24); - }); - - it('letter spacing control with decimal precision', async () => { - const control = createTestControl(0, { min: -0.1, max: 0.5, step: 0.01 }); - const { container: _container } = render(ComboControl, { - control, - }); - - expect(screen.getByText('0')).toBeInTheDocument(); - - // Increase to positive value - const increaseBtn = screen.getAllByRole('button')[2]; - await fireEvent.click(increaseBtn); - await fireEvent.click(increaseBtn); - - expect(control.value).toBeCloseTo(0.02); - }); - - it('line height control with 0.1 step', async () => { - const control = createTestControl(1.5, { min: 0.8, max: 2.0, step: 0.1 }); - render(ComboControl, { - control, - }); - - expect(screen.getByText('1.5')).toBeInTheDocument(); - - // Decrease to 1.3 - const decreaseBtn = screen.getAllByRole('button')[0]; - await fireEvent.click(decreaseBtn); - await fireEvent.click(decreaseBtn); - - expect(control.value).toBeCloseTo(1.3); + it('shows formatted value in reduced mode', () => { + const { container } = render(ComboControl, { control: makeControl(75), reduced: true }); + expect(container.textContent).toContain('75'); }); }); }); diff --git a/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts b/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts index 085b646..d7d5d24 100644 --- a/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts +++ b/src/shared/ui/FilterGroup/FilterGroup.svelte.test.ts @@ -1,573 +1,104 @@ -import { - type Property, - createFilter, -} from '$shared/lib'; +import { createFilter } from '$shared/lib'; import { fireEvent, render, screen, waitFor, } from '@testing-library/svelte'; -import { - describe, - expect, - it, -} from 'vitest'; import FilterGroup from './FilterGroup.svelte'; -/** - * Test Suite for FilterGroup Component - * - * This suite tests the actual Svelte component rendering, interactions, and behavior - * using a real browser environment (Playwright) via @vitest/browser-playwright. - * - * Tests for the createFilter helper function are in createFilter.test.ts - * - * IMPORTANT: These tests use the browser environment because Svelte 5's $state, - * $derived, and onMount lifecycle require a browser environment. The bits-ui - * Checkbox component renders as