/** * SearchBar Component Tests * Verifies search UI rendering and interactions */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { ref } from 'vue'; import SearchBar from '../../../../src/components/gamemaster/SearchBar.vue'; import { useGamemasterSearch } from '../../../../src/composables/useGamemasterSearch.js'; vi.mock('../../../../src/composables/useGamemasterSearch.js', () => ({ useGamemasterSearch: vi.fn() })); const createSearchMock = overrides => ({ searchQuery: ref(''), searchResults: ref([]), currentResultIndex: ref(0), isSearching: ref(false), searchError: ref(null), searchHistory: { history: ref([]) }, clearSearch: vi.fn(), goToNextResult: vi.fn(), goToPrevResult: vi.fn(), applyHistoryItem: vi.fn(), currentResultLineNumber: ref(null), resultCountDisplay: ref('0 results'), hasSearchResults: ref(false), ...overrides }); describe('SearchBar Component', () => { let mockSearch; beforeEach(() => { mockSearch = createSearchMock(); useGamemasterSearch.mockReturnValue(mockSearch); }); it('renders the search input', () => { const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); const input = wrapper.find('input.search-input'); expect(input.exists()).toBe(true); expect(input.attributes('placeholder')).toContain('Search in file'); }); it('disables input when fileLines are empty', () => { const wrapper = mount(SearchBar, { props: { fileLines: [], displayLines: [] } }); const input = wrapper.find('input.search-input'); expect(input.attributes('disabled')).toBeDefined(); }); it('shows clear button when searchQuery is not empty', async () => { mockSearch.searchQuery.value = 'Pikachu'; const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); const clearButton = wrapper.find('button.btn-clear'); expect(clearButton.exists()).toBe(true); await clearButton.trigger('click'); expect(mockSearch.clearSearch).toHaveBeenCalled(); }); it('renders search results summary and line number', () => { mockSearch.hasSearchResults.value = true; mockSearch.resultCountDisplay.value = '2 / 5'; mockSearch.currentResultLineNumber.value = 42; const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); const results = wrapper.find('.search-results'); expect(results.exists()).toBe(true); expect(results.text()).toContain('2 / 5'); expect(results.text()).toContain('Line 42'); const titleSpan = results.find('span'); expect(titleSpan.attributes('title')).toBe('Line 42'); }); it('triggers next/previous navigation', async () => { mockSearch.hasSearchResults.value = true; const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); const buttons = wrapper.findAll('button.btn-nav'); expect(buttons.length).toBe(2); await buttons[0].trigger('click'); await buttons[1].trigger('click'); expect(mockSearch.goToPrevResult).toHaveBeenCalled(); expect(mockSearch.goToNextResult).toHaveBeenCalled(); }); it('renders search history items and applies selection', async () => { mockSearch.searchHistory.history.value = ['Pikachu', 'Charizard']; const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); const historyButtons = wrapper.findAll('button.history-item'); expect(historyButtons.length).toBe(2); await historyButtons[0].trigger('click'); expect(mockSearch.applyHistoryItem).toHaveBeenCalledWith('Pikachu'); }); it('shows error and searching status', () => { mockSearch.searchError.value = 'Worker failed'; mockSearch.isSearching.value = true; const wrapper = mount(SearchBar, { props: { fileLines: ['line one'], displayLines: [] } }); expect(wrapper.find('.search-error').text()).toContain('Worker failed'); expect(wrapper.find('.search-status').text()).toContain('Searching'); }); });