import { describe, it, expect, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useChartSelection } from './use-chart-selection' describe('useChartSelection', () => { it('clicking an item toggles it into and out of the selection', () => { const onSelectionChange = vi.fn() const { result, rerender } = renderHook( ({ selection }: { selection: readonly string[] }) => useChartSelection({ selection, onSelectionChange, keyFromClick: (p) => (p as { name?: string }).name ?? null, keysFromBrush: () => [], }), { initialProps: { selection: [] as readonly string[] } }, ) act(() => { result.current.onEvents.click?.({ name: 'A' }) }) expect(onSelectionChange).toHaveBeenLastCalledWith(['A']) rerender({ selection: ['A'] }) act(() => { result.current.onEvents.click?.({ name: 'A' }) }) expect(onSelectionChange).toHaveBeenLastCalledWith([]) }) it('emits the brushed selection only on brushEnd (brushSelected just caches)', () => { const onSelectionChange = vi.fn() const { result } = renderHook(() => useChartSelection({ selection: [99], onSelectionChange, keyFromClick: () => null, keysFromBrush: (selected) => selected.flatMap((s) => s.dataIndex), }), ) // Mid-drag updates fire brushSelected — these should NOT call back. act(() => { result.current.onEvents.brushSelected?.({ batch: [{ selected: [{ seriesIndex: 0, dataIndex: [3] }] }], }) result.current.onEvents.brushSelected?.({ batch: [{ selected: [{ seriesIndex: 0, dataIndex: [3, 7] }] }], }) result.current.onEvents.brushSelected?.({ batch: [{ selected: [{ seriesIndex: 0, dataIndex: [3, 7, 11] }] }], }) }) expect(onSelectionChange).not.toHaveBeenCalled() // Releasing the brush fires brushEnd — that's when we commit. act(() => { result.current.onEvents.brushEnd?.({}) }) expect(onSelectionChange).toHaveBeenCalledTimes(1) expect(onSelectionChange).toHaveBeenLastCalledWith([3, 7, 11]) }) it('reports null `selection` when the consumer passes nothing or an empty list', () => { const { result, rerender } = renderHook( ({ selection }: { selection?: readonly string[] }) => useChartSelection({ selection, onSelectionChange: () => undefined, keyFromClick: () => null, keysFromBrush: () => [], }), { initialProps: { selection: undefined } as { selection?: readonly string[] }, }, ) expect(result.current.selection).toBeNull() rerender({ selection: [] }) expect(result.current.selection).toBeNull() rerender({ selection: ['A'] }) expect(result.current.selection).toEqual(['A']) }) it('is a no-op when `onSelectionChange` is missing', () => { const { result } = renderHook(() => useChartSelection({ selection: [], keyFromClick: (p) => (p as { name?: string }).name ?? null, keysFromBrush: () => ['x'], }), ) // Neither call should throw — handler short-circuits without a callback. expect(() => result.current.onEvents.click?.({ name: 'A' })).not.toThrow() expect(() => result.current.onEvents.brushSelected?.({ batch: [{ selected: [{ seriesIndex: 0, dataIndex: [0] }] }], }), ).not.toThrow() }) it('skips clicks where keyFromClick returns null (e.g. axis-label clicks)', () => { const onSelectionChange = vi.fn() const { result } = renderHook(() => useChartSelection({ selection: [], onSelectionChange, keyFromClick: () => null, keysFromBrush: () => [], }), ) act(() => { result.current.onEvents.click?.({ name: 'A' }) }) expect(onSelectionChange).not.toHaveBeenCalled() }) })