import { StrictMode } from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SearchInput } from '../SearchInput'; import { FormGroup } from '../../Form'; import { Button } from '../../Button'; import { AnimationsProvider } from '../../../helpers/AnimationsProvider'; import RhUiExternalLinkFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-external-link-fill-icon'; import badgeStyles from '@patternfly/react-styles/css/components/Badge/badge'; import textInputGroupStyles from '@patternfly/react-styles/css/components/TextInputGroup/text-input-group'; import inputGroupStyles from '@patternfly/react-styles/css/components/InputGroup/input-group'; jest.mock('../../../helpers/OUIA/ouia'); jest.mock('../../../helpers/GenerateId/GenerateId'); const props = { onChange: jest.fn(), value: 'test input', onNextClick: jest.fn(), onPreviousClick: jest.fn(), onClear: jest.fn(), onSearch: jest.fn() }; describe('SearchInput', () => { test('simple search input', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); test('search input with hint', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); test('result count', () => { render(); expect(screen.getByTestId('test-id').querySelector(`.${badgeStyles.badge}`)).toBeInTheDocument(); }); test('renders search input in strict mode', async () => { const user = userEvent.setup(); const consoleError = jest.spyOn(console, 'error'); const { asFragment } = render( ); await user.click(screen.getByRole('button', { name: 'Search' })); expect(consoleError).not.toHaveBeenCalled(); expect(props.onSearch).toHaveBeenCalled(); expect(asFragment()).toMatchSnapshot(); }); test('navigable search results', async () => { const user = userEvent.setup(); render(); const input = screen.getByTestId('test-id'); expect(input.querySelector(`.${textInputGroupStyles.textInputGroupGroup}`)).toBeInTheDocument(); expect(input.querySelector(`.${badgeStyles.badge}`)).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Previous' })); expect(props.onPreviousClick).toHaveBeenCalled(); await user.click(screen.getByRole('button', { name: 'Next' })); expect(props.onNextClick).toHaveBeenCalled(); await user.click(screen.getByRole('button', { name: 'Reset' })); expect(props.onClear).toHaveBeenCalled(); }); test('hide clear button', () => { render( ); expect(screen.queryByRole('button', { name: 'Reset' })).not.toBeInTheDocument(); }); test('advanced search', async () => { const user = userEvent.setup(); const { asFragment } = render( ); const expandButton = screen.getByRole('button', { name: 'Open advanced search' }); expect(expandButton).toHaveAttribute('aria-expanded', 'false'); await user.click(screen.getByRole('button', { name: 'Search' })); expect(props.onSearch).toHaveBeenCalled(); expect(asFragment()).toMatchSnapshot(); }); test('advanced search with custom attributes', async () => { const user = userEvent.setup(); const { asFragment } = render( } value="username:player firstname:john" onChange={props.onChange} onSearch={props.onSearch} onClear={props.onClear} /> ); await user.click(screen.getByRole('button', { name: 'Search' })); const expandButton = screen.getByRole('button', { name: 'Open advanced search' }); expect(expandButton).toHaveAttribute('aria-expanded', 'false'); await user.click(expandButton); expect(expandButton).toHaveAttribute('aria-expanded', 'true'); expect(screen.getByTestId('test-id')).toContainElement(screen.getByText('First name')); expect(props.onSearch).toHaveBeenCalled(); expect(asFragment()).toMatchSnapshot(); }); test('advanced search with custom attributes and appendTo="inline', async () => { const user = userEvent.setup(); render( ); await user.click(screen.getByRole('button', { name: 'Open advanced search' })); expect(screen.getByTestId('test-id')).toContainElement(screen.getByText('test')); }); test('advanced search with custom attributes and appendTo external DOM element', async () => { const user = userEvent.setup(); render( ); await user.click(screen.getByRole('button', { name: 'Open advanced search' })); expect(screen.getByTestId('test-id')).not.toContainElement(screen.getByText('test')); expect(document.body).toContainElement(screen.getByText('test')); }); }); test('expandable toggle does not render by default', () => { render(); expect(screen.queryByRole('button')).not.toBeInTheDocument(); }); test('expandable toggle renders when a value is passed for expandableProps', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByRole('button')).toBeVisible(); }); test('expandable toggle is not aria-expanded when isExpanded is not true', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByRole('button', { expanded: false })).toBeVisible(); }); test('expandable toggle is aria-expanded when isExpanded is true', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByRole('button', { expanded: true })).toBeVisible(); }); test('text input is not rendered when isExpanded is not true', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.queryByRole('textbox')).not.toBeInTheDocument(); }); test('text input is rendered when isExpanded is true', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByRole('textbox')).toBeVisible(); }); test('animate classes & inert are not rendered when hasAnimations is false', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.expanded}`); expect(screen.getByTestId('test-id').children[0]).not.toHaveClass(`${inputGroupStyles.modifiers.searchInput}`); expect(screen.getByTestId('test-id').children[0].parentElement).not.toHaveAttribute('inert', ''); expect(screen.getAllByRole('button')).toHaveLength(1); expect(screen.getAllByRole('button')[0].parentElement).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpand}`); expect(screen.getAllByRole('button')[0].parentElement).not.toHaveClass(`${inputGroupStyles.modifiers.searchAction}`); expect(screen.getAllByRole('button')[0].parentElement).not.toHaveAttribute('inert', ''); }); test('animate classes & inert are properly rendered when hasAnimations is true and isExpanded is false', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.expanded}`); expect(screen.getByTestId('test-id').querySelector(`.${inputGroupStyles.modifiers.searchInput}`)).toBeInTheDocument(); expect(screen.getByTestId('test-id').querySelector(`.${inputGroupStyles.modifiers.searchInput}`)).toHaveAttribute( 'inert', '' ); expect(screen.getAllByRole('button')).toHaveLength(2); expect(screen.getAllByRole('button')[0].parentElement).toHaveClass(`${inputGroupStyles.modifiers.searchExpand}`); expect(screen.getAllByRole('button')[0].parentElement).not.toHaveAttribute('inert', ''); expect(screen.getAllByRole('button')[1].parentElement).toHaveClass(`${inputGroupStyles.modifiers.searchAction}`); expect(screen.getAllByRole('button')[1].parentElement).toHaveAttribute('inert', ''); }); test('animate classes and inert are properly rendered when hasAnimations and isExpanded are true', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); expect(screen.getByTestId('test-id')).toHaveClass(`${inputGroupStyles.modifiers.expanded}`); expect(screen.getByTestId('test-id').querySelector(`.${inputGroupStyles.modifiers.searchInput}`)).toBeInTheDocument(); expect(screen.getByTestId('test-id').querySelector(`.${inputGroupStyles.modifiers.searchInput}`)).not.toHaveAttribute( 'inert', '' ); expect(screen.getAllByRole('button')).toHaveLength(2); expect(screen.getAllByRole('button')[0].parentElement).toHaveClass(`${inputGroupStyles.modifiers.searchExpand}`); expect(screen.getAllByRole('button')[0].parentElement).toHaveAttribute('inert', ''); expect(screen.getAllByRole('button')[1].parentElement).toHaveClass(`${inputGroupStyles.modifiers.searchAction}`); expect(screen.getAllByRole('button')[1].parentElement).not.toHaveAttribute('inert', ''); }); test('onToggleExpand is not called if the expandable toggle is not clicked', () => { const mockOnToggleExpand = jest.fn(); render( ); expect(mockOnToggleExpand).not.toHaveBeenCalled(); }); test('onToggleExpand is called if the expandable toggle is clicked', async () => { const mockOnToggleExpand = jest.fn(); const user = userEvent.setup(); render( ); await user.click(screen.getByRole('button')); expect(mockOnToggleExpand).toHaveBeenCalledTimes(1); expect(mockOnToggleExpand).toHaveBeenCalledWith(expect.objectContaining({ type: 'click' }), true); }); test('toggleAriaLabel is applied to the expandable toggle', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByRole('button')).toHaveAccessibleName('Test label'); }); test('Utilities are rendered when areUtilitiesDisplayed is set', () => { render(); expect(screen.getByLabelText('test-util-display')).toBeVisible(); }); test('Additional props are spread when inputProps prop is passed', () => { render(); expect(screen.getByLabelText('Test input')).toHaveAttribute('autofocus', 'true'); }); // Animation context tests test('respects AnimationsProvider context when no local hasAnimations prop for expandable input', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); }); test('local hasAnimations prop takes precedence over context for expandable input', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); }); test('works without AnimationsProvider for expandable input (backward compatibility)', () => { render( {}, toggleAriaLabel: 'Test label' }} /> ); expect(screen.getByTestId('test-id')).not.toHaveClass(`${inputGroupStyles.modifiers.searchExpandable}`); });