import { describe, test, expect, beforeEach, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { RangeUI } from './range-ui' import { useWidgetStore } from '../stores/widget-store' import type { RangeWidgetState } from './types' describe('RangeUI', () => { beforeEach(() => { useWidgetStore.getState().clearWidgets() }) test('returns null when widget does not exist', () => { const { container } = render() expect(container.firstChild).toBeNull() }) test('renders with single range item', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], }) render() const inputs = screen.getAllByRole('textbox') expect(inputs).toHaveLength(2) expect((inputs[0] as HTMLInputElement).value).toBe('20') expect((inputs[1] as HTMLInputElement).value).toBe('80') }) test('renders with multiple range items', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [ { min: 0, max: 100, value: [20, 80] }, { min: 0, max: 50, value: [10, 40] }, ], }) render() const inputs = screen.getAllByRole('textbox') expect(inputs).toHaveLength(4) expect((inputs[0] as HTMLInputElement).value).toBe('20') expect((inputs[1] as HTMLInputElement).value).toBe('80') expect((inputs[2] as HTMLInputElement).value).toBe('10') expect((inputs[3] as HTMLInputElement).value).toBe('40') }) test('uses formatter when provided', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], formatter: (val: number) => `$${val}`, }) render() const inputs = screen.getAllByRole('textbox') expect((inputs[0] as HTMLInputElement).value).toBe('$20') expect((inputs[1] as HTMLInputElement).value).toBe('$80') }) test('handles onChange callback', () => { const onChange = vi.fn() useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], onChange, }) render() const minInput = screen.getAllByRole('textbox')[0] fireEvent.change(minInput!, { target: { value: '30' } }) fireEvent.blur(minInput!) expect(onChange).toHaveBeenCalledWith([30, 80], 0) }) test('renders disabled state', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, disabled: true }], }) render() const inputs = screen.getAllByRole('textbox') expect((inputs[0] as HTMLInputElement).disabled).toBe(true) expect((inputs[1] as HTMLInputElement).disabled).toBe(true) }) test('handles empty data array', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [], }) const { container } = render() expect(container.firstChild).toBeNull() }) test('handles undefined data', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', }) const { container } = render() expect(container.firstChild).toBeNull() }) test('uses default min/max when value is not provided', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 10, max: 90 }], }) render() const inputs = screen.getAllByRole('textbox') expect((inputs[0] as HTMLInputElement).value).toBe('10') expect((inputs[1] as HTMLInputElement).value).toBe('90') }) test('validates min input on blur', () => { const onChange = vi.fn() useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], onChange, }) render() const minInput = screen.getAllByRole('textbox')[0] // Try to set min higher than max fireEvent.change(minInput!, { target: { value: '90' } }) fireEvent.blur(minInput!) // Should clamp to max value expect(onChange).toHaveBeenCalledWith([80, 80], 0) }) test('validates max input on blur', () => { const onChange = vi.fn() useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], onChange, }) render() const maxInput = screen.getAllByRole('textbox')[1] // Try to set max lower than min fireEvent.change(maxInput!, { target: { value: '10' } }) fireEvent.blur(maxInput!) // Should clamp to min value expect(onChange).toHaveBeenCalledWith([20, 20], 0) }) test('updates when widget data changes', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100, value: [20, 80] }], }) const { rerender } = render() let inputs = screen.getAllByRole('textbox') expect((inputs[0] as HTMLInputElement).value).toBe('20') expect((inputs[1] as HTMLInputElement).value).toBe('80') useWidgetStore.getState().setWidget('test-range', { data: [{ min: 0, max: 100, value: [30, 70] }], }) rerender() inputs = screen.getAllByRole('textbox') expect((inputs[0] as HTMLInputElement).value).toBe('30') expect((inputs[1] as HTMLInputElement).value).toBe('70') }) test('handles widget removal gracefully', () => { useWidgetStore.getState().setWidget('test-range', { type: 'range', data: [{ min: 0, max: 100 }], }) const { container, rerender } = render() expect(container.firstChild).toBeTruthy() useWidgetStore.getState().removeWidget('test-range') rerender() expect(container.firstChild).toBeNull() }) })