Download packages/cli/src/ui/components/shared/SearchableList.test.tsx from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 5.54 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/components/shared/SearchableList.test.tsx
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/ui/components/shared/SearchableList.test.tsx
-
curl -L -o SearchableList.test.tsx https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/components/shared/SearchableList.test.tsx
5.54 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import React from 'react'; | |
| import { renderWithProviders } from '../../../test-utils/render.js'; | |
| import { waitFor } from '../../../test-utils/async.js'; | |
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| import { | |
| SearchableList, | |
| type SearchableListProps, | |
| type SearchListState, | |
| type GenericListItem, | |
| } from './SearchableList.js'; | |
| import { useTextBuffer } from './text-buffer.js'; | |
| const useMockSearch = (props: { | |
| items: GenericListItem[]; | |
| initialQuery?: string; | |
| onSearch?: (query: string) => void; | |
| }): SearchListState<GenericListItem> => { | |
| const { onSearch, items, initialQuery = '' } = props; | |
| const [text, setText] = React.useState(initialQuery); | |
| const filteredItems = React.useMemo( | |
| () => | |
| items.filter((item: GenericListItem) => | |
| item.label.toLowerCase().includes(text.toLowerCase()), | |
| ), | |
| [items, text], | |
| ); | |
| React.useEffect(() => { | |
| onSearch?.(text); | |
| }, [text, onSearch]); | |
| const searchBuffer = useTextBuffer({ | |
| initialText: text, | |
| onChange: setText, | |
| viewport: { width: 100, height: 1 }, | |
| singleLine: true, | |
| }); | |
| return { | |
| filteredItems, | |
| searchBuffer, | |
| searchQuery: text, | |
| setSearchQuery: setText, | |
| maxLabelWidth: 10, | |
| }; | |
| }; | |
| 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<typeof vi.fn>; | |
| let mockOnClose: ReturnType<typeof vi.fn>; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| mockOnSelect = vi.fn(); | |
| mockOnClose = vi.fn(); | |
| }); | |
| const renderList = async ( | |
| props: Partial<SearchableListProps<GenericListItem>> = {}, | |
| ) => { | |
| const defaultProps: SearchableListProps<GenericListItem> = { | |
| title: 'Test List', | |
| items: mockItems, | |
| onSelect: mockOnSelect, | |
| onClose: mockOnClose, | |
| useSearch: useMockSearch, | |
| ...props, | |
| }; | |
| return renderWithProviders(<SearchableList {...defaultProps} />); | |
| }; | |
| it('should render all items initially', async () => { | |
| const { lastFrame } = await renderList(); | |
| const frame = lastFrame(); | |
| expect(frame).toContain('Test List'); | |
| expect(frame).toContain('Item One'); | |
| expect(frame).toContain('Item Two'); | |
| expect(frame).toContain('Item Three'); | |
| expect(frame).toContain('Description for item one'); | |
| }); | |
| it('should reset selection to top when items change if resetSelectionOnItemsChange is true', async () => { | |
| const { lastFrame, stdin } = await renderList({ | |
| resetSelectionOnItemsChange: true, | |
| }); | |
| await React.act(async () => { | |
| stdin.write('\u001B[B'); // Down arrow | |
| }); | |
| await waitFor(() => { | |
| const frame = lastFrame(); | |
| expect(frame).toContain('● Item Two'); | |
| }); | |
| expect(lastFrame()).toMatchSnapshot(); | |
| await React.act(async () => { | |
| stdin.write('One'); | |
| }); | |
| await waitFor(() => { | |
| const frame = lastFrame(); | |
| expect(frame).toContain('Item One'); | |
| expect(frame).not.toContain('Item Two'); | |
| }); | |
| expect(lastFrame()).toMatchSnapshot(); | |
| await React.act(async () => { | |
| // Backspace "One" (3 chars) | |
| stdin.write('\u007F\u007F\u007F'); | |
| }); | |
| await waitFor(() => { | |
| const frame = lastFrame(); | |
| expect(frame).toContain('Item Two'); | |
| expect(frame).toContain('● Item One'); | |
| expect(frame).not.toContain('● Item Two'); | |
| }); | |
| expect(lastFrame()).toMatchSnapshot(); | |
| }); | |
| it('should filter items based on search query', async () => { | |
| const { lastFrame, stdin } = await renderList(); | |
| 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 } = await renderList(); | |
| 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 } = await renderList(); | |
| await React.act(async () => { | |
| stdin.write('\r'); // Enter | |
| }); | |
| await waitFor(() => { | |
| expect(mockOnSelect).toHaveBeenCalledWith(mockItems[0]); | |
| }); | |
| }); | |
| it('should handle navigation and selection', async () => { | |
| const { stdin } = await renderList(); | |
| await React.act(async () => { | |
| stdin.write('\u001B[B'); // Down arrow | |
| }); | |
| await React.act(async () => { | |
| stdin.write('\r'); // Enter | |
| }); | |
| await waitFor(() => { | |
| expect(mockOnSelect).toHaveBeenCalledWith(mockItems[1]); | |
| }); | |
| }); | |
| it('should handle close with Esc', async () => { | |
| const { stdin } = await renderList(); | |
| await React.act(async () => { | |
| stdin.write('\u001B'); // Esc | |
| }); | |
| await waitFor(() => { | |
| expect(mockOnClose).toHaveBeenCalled(); | |
| }); | |
| }); | |
| it('should match snapshot', async () => { | |
| const { lastFrame } = await renderList(); | |
| expect(lastFrame()).toMatchSnapshot(); | |
| }); | |
| }); | |