import { describe, test, expect, beforeEach, vi, afterEach } from 'vitest' import { render, screen, fireEvent, act } from '@testing-library/react' import { SearcherToggle } from './searcher-toggle' import { Searcher } from './searcher' import { useWidgetStore } from '../../stores/widget-store' import type { EchartWidgetData } from '../../echart/types' import type { SearcherState } from './types' // Test data const mockData: EchartWidgetData = [ [ { name: 'Electronics', value: 440 }, { name: 'Clothing', value: 280 }, { name: 'Books', value: 100 }, ], ] describe('SearcherToggle', () => { const widgetId = 'test-searcher-toggle' beforeEach(() => { useWidgetStore.getState().clearWidgets() }) test('renders search toggle button', () => { render() const button = screen.getByRole('button') expect(button).toBeTruthy() }) test('shows enable tooltip when search is disabled', () => { render() const button = screen.getByRole('button', { name: 'Enable search' }) expect(button).toBeTruthy() }) test('shows disable tooltip when search is enabled', () => { render() const button = screen.getByRole('button', { name: 'Disable search' }) expect(button).toBeTruthy() }) test('toggles isSearchEnabled in store when clicked', () => { render() const button = screen.getByRole('button') fireEvent.click(button) const widget = useWidgetStore.getState().getWidget(widgetId) expect(widget?.isSearchEnabled).toBe(true) }) test('has active state when enabled via store', () => { render() const button = screen.getByRole('button') expect(button.getAttribute('data-active')).toBe('true') }) test('has inactive state when disabled', () => { render() const button = screen.getByRole('button') expect(button.getAttribute('data-active')).toBe('false') }) test('uses custom labels', () => { render( , ) const button = screen.getByRole('button', { name: 'Toggle search' }) expect(button).toBeTruthy() }) test('initializes store with defaultEnabled value', () => { render() const widget = useWidgetStore.getState().getWidget(widgetId) expect(widget?.isSearchEnabled).toBe(true) }) }) describe('Searcher', () => { const widgetId = 'test-searcher-input' beforeEach(() => { useWidgetStore.getState().clearWidgets() vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) test('does not render when search is disabled in store', () => { // Set isSearchEnabled to false in store useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: false }) render() const input = screen.queryByRole('textbox') expect(input).toBeNull() }) test('renders input when search is enabled in store', () => { // Set isSearchEnabled to true in store useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true }) render() const input = screen.getByRole('textbox') expect(input).toBeTruthy() }) test('shows placeholder text', () => { useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true }) render() const input = screen.getByPlaceholderText('Search...') expect(input).toBeTruthy() }) test('updates store with debounced filtered data', async () => { // Initialize widget with data and enabled state useWidgetStore .getState() .setWidget(widgetId, { data: mockData, isSearchEnabled: true }) render() const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'Electronics' } }) // Wait for debounce (300ms) and flush all pending promises await act(async () => { await vi.advanceTimersByTimeAsync(300) }) // Execute the tool pipeline to apply the filter transformation await useWidgetStore.getState().executeToolPipeline(widgetId, mockData) const widget = useWidgetStore.getState().getWidget(widgetId) const filteredData = widget?.data as EchartWidgetData | undefined // Should only have Electronics item expect(filteredData?.[0]?.length).toBe(1) expect(filteredData?.[0]?.[0]?.name).toBe('Electronics') }) test('shows clear button when there is text', () => { useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true }) render() const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'test' } }) const clearButton = screen.getByRole('button', { name: 'Clear search' }) expect(clearButton).toBeTruthy() }) test('does not show clear button when input is empty', () => { useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true }) render() const clearButton = screen.queryByRole('button', { name: 'Clear search' }) expect(clearButton).toBeNull() }) test('clears search text and restores original data when clear button is clicked', () => { // Initialize widget with data and enabled state useWidgetStore .getState() .setWidget(widgetId, { data: mockData, isSearchEnabled: true }) render() const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'Electronics' } }) // Wait for debounce act(() => { vi.advanceTimersByTime(300) }) // Click clear const clearButton = screen.getByRole('button', { name: 'Clear search' }) fireEvent.click(clearButton) // Data should be restored to original const widget = useWidgetStore.getState().getWidget(widgetId) const restoredData = widget?.data as EchartWidgetData | undefined expect(restoredData?.[0]?.length).toBe(3) }) test('uses custom clear button aria label', () => { useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true }) render( , ) const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'text' } }) const clearButton = screen.getByRole('button', { name: 'Limpiar búsqueda', }) expect(clearButton).toBeTruthy() }) test('uses custom filter function', async () => { // Custom filter that only matches exact values const customFilterFn = async ( data: EchartWidgetData, searchText: string, ) => { return Promise.resolve( data.map((series) => series.filter((item) => item.value === parseInt(searchText, 10)), ), ) } // Initialize widget with data and enabled state useWidgetStore .getState() .setWidget(widgetId, { data: mockData, isSearchEnabled: true }) render() const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: '440' } }) // Wait for debounce and flush all pending promises await act(async () => { await vi.advanceTimersByTimeAsync(300) }) // Execute the tool pipeline to apply the filter transformation await useWidgetStore.getState().executeToolPipeline(widgetId, mockData) // Should only have Electronics (value: 440) const widget = useWidgetStore.getState().getWidget(widgetId) const filteredData = widget?.data as EchartWidgetData | undefined expect(filteredData?.[0]?.length).toBe(1) expect(filteredData?.[0]?.[0]?.value).toBe(440) }) }) describe('SearcherToggle + Searcher integration', () => { const widgetId = 'test-integration' beforeEach(() => { useWidgetStore.getState().clearWidgets() }) // Helper component that uses both SearcherToggle and Searcher with same id function IntegrationTest() { return ( <> ) } test('searcher becomes visible when toggle is clicked', () => { render() // Initially searcher should not be visible expect(screen.queryByRole('textbox')).toBeNull() // Click toggle const toggleButton = screen.getByRole('button') fireEvent.click(toggleButton) // Searcher should now be visible expect(screen.getByRole('textbox')).toBeTruthy() }) test('searcher is hidden when toggle is clicked again', () => { render() const toggleButton = screen.getByRole('button') // Enable fireEvent.click(toggleButton) expect(screen.getByRole('textbox')).toBeTruthy() // Disable fireEvent.click(toggleButton) expect(screen.queryByRole('textbox')).toBeNull() }) test('toggle button shows correct active state', () => { render() const toggleButton = screen.getByRole('button') // Initially inactive expect(toggleButton.getAttribute('data-active')).toBe('false') // Enable - should be active fireEvent.click(toggleButton) expect(toggleButton.getAttribute('data-active')).toBe('true') // Disable - should be inactive fireEvent.click(toggleButton) expect(toggleButton.getAttribute('data-active')).toBe('false') }) test('store state is correctly updated when toggling', () => { render() const toggleButton = screen.getByRole('button') // Initially should be false expect( useWidgetStore.getState().getWidget(widgetId) ?.isSearchEnabled, ).toBe(false) // Enable fireEvent.click(toggleButton) expect( useWidgetStore.getState().getWidget(widgetId) ?.isSearchEnabled, ).toBe(true) // Disable fireEvent.click(toggleButton) expect( useWidgetStore.getState().getWidget(widgetId) ?.isSearchEnabled, ).toBe(false) }) })