import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; import SearchInput from '$lib/components/molecules/SearchInput/SearchInput.svelte'; describe('SearchInput', () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it('renders input with default placeholder', () => { render(SearchInput, { props: { onSearch: vi.fn() } }); expect(screen.getByPlaceholderText('Rechercher...')).toBeTruthy(); }); it('renders input with custom placeholder', () => { render(SearchInput, { props: { onSearch: vi.fn(), placeholder: 'Chercher un utilisateur...' } }); expect(screen.getByPlaceholderText('Chercher un utilisateur...')).toBeTruthy(); }); it('debounces input and calls onSearch after delay', async () => { const onSearch = vi.fn(); render(SearchInput, { props: { onSearch, debounceMs: 300 } }); const input = screen.getByRole('searchbox'); await fireEvent.input(input, { target: { value: 'test' } }); // Should not be called immediately expect(onSearch).not.toHaveBeenCalled(); // Advance past the debounce time vi.advanceTimersByTime(300); expect(onSearch).toHaveBeenCalledOnce(); expect(onSearch).toHaveBeenCalledWith('test'); }); it('cancels pending debounce on new input', async () => { const onSearch = vi.fn(); render(SearchInput, { props: { onSearch, debounceMs: 300 } }); const input = screen.getByRole('searchbox'); // Type first value await fireEvent.input(input, { target: { value: 'te' } }); vi.advanceTimersByTime(200); // Type second value before debounce fires await fireEvent.input(input, { target: { value: 'test' } }); vi.advanceTimersByTime(300); // Should only be called once with the final value expect(onSearch).toHaveBeenCalledOnce(); expect(onSearch).toHaveBeenCalledWith('test'); }); it('clears input on Escape key', async () => { const onSearch = vi.fn(); render(SearchInput, { props: { value: 'initial', onSearch } }); const input = screen.getByRole('searchbox'); await fireEvent.keyDown(input, { key: 'Escape' }); expect(onSearch).toHaveBeenCalledWith(''); }); it('calls onSearch immediately on clear button click', async () => { const onSearch = vi.fn(); render(SearchInput, { props: { value: 'something', onSearch } }); const clearButton = screen.getByRole('button', { name: 'Effacer la recherche' }); await fireEvent.click(clearButton); // Should be called immediately (no debounce) expect(onSearch).toHaveBeenCalledWith(''); }); it('shows clear button only when input has value', () => { const { container } = render(SearchInput, { props: { onSearch: vi.fn() } }); // No clear button initially (empty value) expect(container.querySelector('.search-clear')).toBeNull(); }); it('shows clear button when initial value is provided', () => { const { container } = render(SearchInput, { props: { value: 'test', onSearch: vi.fn() } }); expect(container.querySelector('.search-clear')).not.toBeNull(); }); });