/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { render } from '../../../test-utils/render.js'; import { waitFor } from '../../../test-utils/async.js'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SearchableList, type SearchableListProps } from './SearchableList.js'; import { KeypressProvider } from '../../contexts/KeypressContext.js'; import { type GenericListItem } from '../../hooks/useFuzzyList.js'; // Mock UI State vi.mock('../../contexts/UIStateContext.js', () => ({ useUIState: () => ({ mainAreaWidth: 100, }), })); const mockItems: GenericListItem[] = [ { key: 'item-1', label: 'Item One', description: 'Description for item one', }, { key: 'item-2', label: 'Item Two', description: 'Description for item two', }, { key: 'item-3', label: 'Item Three', description: 'Description for item three', }, ]; describe('SearchableList', () => { let mockOnSelect: ReturnType; let mockOnClose: ReturnType; beforeEach(() => { vi.clearAllMocks(); mockOnSelect = vi.fn(); mockOnClose = vi.fn(); }); const renderList = ( props: Partial> = {}, ) => { const defaultProps: SearchableListProps = { title: 'Test List', items: mockItems, onSelect: mockOnSelect, onClose: mockOnClose, ...props, }; return render( , ); }; it('should render all items initially', async () => { const { lastFrame, waitUntilReady } = renderList(); await waitUntilReady(); const frame = lastFrame(); // Check for title expect(frame).toContain('Test List'); // Check for items expect(frame).toContain('Item One'); expect(frame).toContain('Item Two'); expect(frame).toContain('Item Three'); // Check for descriptions expect(frame).toContain('Description for item one'); }); it('should filter items based on search query', async () => { const { lastFrame, stdin } = renderList(); // Type "Two" into search await React.act(async () => { stdin.write('Two'); }); await waitFor(() => { const frame = lastFrame(); expect(frame).toContain('Item Two'); expect(frame).not.toContain('Item One'); expect(frame).not.toContain('Item Three'); }); }); it('should show "No items found." when no items match', async () => { const { lastFrame, stdin } = renderList(); // Type something that won't match await React.act(async () => { stdin.write('xyz123'); }); await waitFor(() => { const frame = lastFrame(); expect(frame).toContain('No items found.'); }); }); it('should handle selection with Enter', async () => { const { stdin } = renderList(); // Select first item (default active) await React.act(async () => { stdin.write('\r'); // Enter }); await waitFor(() => { expect(mockOnSelect).toHaveBeenCalledWith(mockItems[0]); }); }); it('should handle navigation and selection', async () => { const { stdin } = renderList(); // Navigate down to second item await React.act(async () => { stdin.write('\u001B[B'); // Down Arrow }); // Select second item await React.act(async () => { stdin.write('\r'); // Enter }); await waitFor(() => { expect(mockOnSelect).toHaveBeenCalledWith(mockItems[1]); }); }); it('should handle close with Esc', async () => { const { stdin } = renderList(); await React.act(async () => { stdin.write('\u001B'); // Esc }); await waitFor(() => { expect(mockOnClose).toHaveBeenCalled(); }); }); });