import { describe, it, expect, beforeEach, vi } from 'vitest'; import { renderHook, act, waitFor } from '@testing-library/react'; import { useItemSearch } from '@/hooks/useItemSearch'; describe('useItemSearch Hook', () => { beforeEach(() => { vi.clearAllMocks(); global.fetch = vi.fn(); }); it('should return empty results for empty query', () => { const { result } = renderHook(() => useItemSearch('', true)); expect(result.current.results).toEqual([]); expect(result.current.isLoading).toBe(false); expect(result.current.error).toBeNull(); }); it('should return empty results for query < 2 chars', () => { const { result } = renderHook(() => useItemSearch('a', true)); expect(result.current.results).toEqual([]); expect(result.current.isLoading).toBe(false); expect(result.current.error).toBeNull(); }); it('should fetch items when query >= 2 chars', async () => { const mockItems = [ { id: 1, name: 'Resistor', part_number: 'R-10K', barcode: 'BAR001', quantity: 100 }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockItems, }); const { result } = renderHook(() => useItemSearch('Resistor', true)); await waitFor(() => { expect(result.current.results).toEqual(mockItems); }); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('/items/search?q=Resistor'), expect.any(Object) ); }); it('should debounce search requests', async () => { (global.fetch as any).mockResolvedValue({ ok: true, json: async () => [], }); const { rerender } = renderHook( ({ query }) => useItemSearch(query, true), { initialProps: { query: '' } } ); act(() => { rerender({ query: 'te' }); }); act(() => { rerender({ query: 'test' }); }); await waitFor(() => { expect(global.fetch).toHaveBeenCalledTimes(1); }); }); it('should cache results per query', async () => { const mockItems = [ { id: 1, name: 'Test Item', part_number: 'T-001', barcode: 'BAR-T001', quantity: 5 }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockItems, }); const { result, rerender } = renderHook( ({ query }) => useItemSearch(query, true), { initialProps: { query: 'test' } } ); await waitFor(() => { expect(result.current.results).toEqual(mockItems); }); const firstCallCount = (global.fetch as any).mock.calls.length; rerender({ query: 'test' }); await waitFor(() => { expect((global.fetch as any).mock.calls.length).toBe(firstCallCount); }); }); it('should handle search errors', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); const { result } = renderHook(() => useItemSearch('error-test', true)); await waitFor(() => { expect(result.current.error).toBeTruthy(); expect(result.current.results).toEqual([]); }); }); it('should handle failed API responses', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false, status: 500, }); const { result } = renderHook(() => useItemSearch('api-error', true)); await waitFor(() => { expect(result.current.error).toBeTruthy(); }); }); it('should return empty results when enabled is false', () => { const { result } = renderHook(() => useItemSearch('test', false)); expect(result.current.results).toEqual([]); expect(result.current.isLoading).toBe(false); }); }); describe('SearchModal Component', () => { it('should render search input when open', () => { // This test would require rendering the component with React Testing Library // Placeholder for component testing expect(true).toBe(true); }); it('should close on Escape key', () => { // Test escape key handling expect(true).toBe(true); }); it('should display results from API', () => { // Test result rendering expect(true).toBe(true); }); it('should call onSelectItem when item is clicked', () => { // Test item selection callback expect(true).toBe(true); }); }); describe('Search Integration', () => { it('should allow user to search and select item', async () => { // End-to-end test scenario const mockItems = [ { id: 1, name: 'Power Supply', part_number: 'PSU-500', barcode: 'PSU-001', quantity: 3 }, ]; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockItems, }); const { result } = renderHook(() => useItemSearch('Power', true)); await waitFor(() => { expect(result.current.results).toHaveLength(1); expect(result.current.results[0].name).toBe('Power Supply'); }); }); it('should handle special characters in search query', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => [], }); const { result } = renderHook(() => useItemSearch('test@#$%', true)); await waitFor(() => { expect(global.fetch).toHaveBeenCalled(); }); }); it('should handle empty search results gracefully', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => [], }); const { result } = renderHook(() => useItemSearch('nonexistent', true)); await waitFor(() => { expect(result.current.results).toEqual([]); expect(result.current.error).toBeNull(); }); }); });