import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { SearchBar } from '../SearchBar' import * as canvasStore from '@/stores/canvasStore' vi.mock('@/stores/canvasStore') vi.mock('@xyflow/react', () => ({ useReactFlow: () => ({ setCenter: vi.fn() }), })) function makeNode(id: string, overrides = {}) { return { id, type: 'server', position: { x: 0, y: 0 }, data: { label: id, type: 'server', status: 'online', services: [], ip: null, hostname: null }, ...overrides, } } function setupStore(nodes: unknown[] = []) { vi.mocked(canvasStore.useCanvasStore).mockReturnValue({ nodes, setSelectedNode: vi.fn(), } as unknown as ReturnType) } function openSearch() { fireEvent.keyDown(window, { key: 'f', ctrlKey: true }) } describe('SearchBar', () => { beforeEach(() => { setupStore([]) vi.clearAllMocks() }) it('is hidden by default', () => { render() expect(screen.queryByPlaceholderText(/search/i)).toBeNull() }) it('opens on Ctrl+F', () => { render() openSearch() expect(screen.getByPlaceholderText(/search/i)).toBeDefined() }) it('opens on Cmd+F', () => { render() fireEvent.keyDown(window, { key: 'f', metaKey: true }) expect(screen.getByPlaceholderText(/search/i)).toBeDefined() }) it('closes on Escape', () => { render() openSearch() fireEvent.keyDown(window, { key: 'Escape' }) expect(screen.queryByPlaceholderText(/search/i)).toBeNull() }) it('closes when X button is clicked', () => { render() openSearch() fireEvent.click(screen.getByLabelText('Close search')) expect(screen.queryByPlaceholderText(/search/i)).toBeNull() }) it('filters by label', () => { setupStore([ makeNode('n1', { data: { label: 'My Router', type: 'router', status: 'online', services: [], ip: null, hostname: null } }), makeNode('n2', { data: { label: 'My NAS', type: 'nas', status: 'online', services: [], ip: null, hostname: null } }), ]) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'router' } }) expect(screen.getByText('My Router')).toBeDefined() expect(screen.queryByText('My NAS')).toBeNull() }) it('filters by IP', () => { setupStore([ makeNode('n1', { data: { label: 'Server A', type: 'server', status: 'online', services: [], ip: '192.168.1.10', hostname: null } }), makeNode('n2', { data: { label: 'Server B', type: 'server', status: 'online', services: [], ip: '10.0.0.1', hostname: null } }), ]) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: '192.168' } }) expect(screen.getByText('Server A')).toBeDefined() expect(screen.queryByText('Server B')).toBeNull() }) it('filters by service name', () => { setupStore([ makeNode('n1', { data: { label: 'Web Server', type: 'server', status: 'online', services: [{ service_name: 'nginx', port: 80, protocol: 'tcp' }], ip: null, hostname: null } }), makeNode('n2', { data: { label: 'DB Server', type: 'server', status: 'online', services: [{ service_name: 'mysql', port: 3306, protocol: 'tcp' }], ip: null, hostname: null } }), ]) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'nginx' } }) expect(screen.getByText('Web Server')).toBeDefined() expect(screen.queryByText('DB Server')).toBeNull() }) it('excludes groupRect nodes from results', () => { setupStore([ makeNode('gr1', { data: { label: 'DMZ Zone', type: 'groupRect', status: 'unknown', services: [], ip: null, hostname: null } }), ]) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'dmz' } }) expect(screen.queryByText('DMZ Zone')).toBeNull() }) it('shows no-results message when query has no matches', () => { render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'zzznomatch' } }) expect(screen.getByText(/no results/i)).toBeDefined() }) it('calls setSelectedNode when a result is clicked', () => { const setSelectedNode = vi.fn() vi.mocked(canvasStore.useCanvasStore).mockReturnValue({ nodes: [makeNode('n1', { data: { label: 'My Server', type: 'server', status: 'online', services: [], ip: null, hostname: null } })], setSelectedNode, } as unknown as ReturnType) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'my server' } }) fireEvent.click(screen.getByText('My Server')) expect(setSelectedNode).toHaveBeenCalledWith('n1') }) it('shows result count', () => { setupStore([ makeNode('n1', { data: { label: 'Alpha', type: 'server', status: 'online', services: [], ip: null, hostname: null } }), makeNode('n2', { data: { label: 'Beta', type: 'server', status: 'online', services: [], ip: null, hostname: null } }), ]) render() openSearch() fireEvent.change(screen.getByPlaceholderText(/search/i), { target: { value: 'a' } }) expect(screen.getByText(/2 results/i)).toBeDefined() }) })