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