import {Fragment} from 'react'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {TagsFixture} from 'sentry-fixture/tags'; import { act, fireEvent, render, screen, userEvent, waitFor, } from 'sentry-test/reactTestingLibrary'; import {SmartSearchBar} from 'sentry/components/smartSearchBar'; import TagStore from 'sentry/stores/tagStore'; import {FieldKey} from 'sentry/utils/fields'; import {ItemType} from './types'; describe('SmartSearchBar', function () { let defaultProps; beforeEach(function () { TagStore.reset(); TagStore.loadTagsSuccess(TagsFixture()); const supportedTags = TagStore.getState(); supportedTags.firstRelease = { key: 'firstRelease', name: 'firstRelease', }; supportedTags.is = { key: 'is', name: 'is', }; const organization = OrganizationFixture({id: '123'}); const location = { pathname: '/organizations/org-slug/recent-searches/', query: { projectId: '0', }, }; MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [], }); defaultProps = { query: '', organization, location, supportedTags, onGetTagValues: jest.fn().mockResolvedValue([]), onSearch: jest.fn(), }; }); afterEach(function () { MockApiClient.clearMockResponses(); }); it('quotes in values with spaces when autocompleting', async function () { const onGetTagValuesMock = jest .fn() .mockResolvedValue(['this is filled with spaces']); render(); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); await userEvent.type(textbox, 'device:this'); const option = await screen.findByText(/this is filled with spaces/); await userEvent.click(option); expect(textbox).toHaveValue('device:"this is filled with spaces" '); }); it('escapes quotes in values properly when autocompleting', async function () { const onGetTagValuesMock = jest .fn() .mockResolvedValue(['this " is " filled " with " quotes']); render(); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); await userEvent.type(textbox, 'device:this'); const option = await screen.findByText(/this \\" is \\" filled \\" with \\" quotes/); await userEvent.click(option); expect(textbox).toHaveValue('device:"this \\" is \\" filled \\" with \\" quotes" '); }); it('does not search when pressing enter on a tag without a value', async function () { const onSearchMock = jest.fn(); render(); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'browser:{enter}'); expect(onSearchMock).not.toHaveBeenCalled(); }); it('autocompletes value with tab', async function () { const onSearchMock = jest.fn(); render(); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'bro'); expect( await screen.findByRole('option', {name: 'bro wser - field'}) ).toBeInTheDocument(); // down once to 'browser' dropdown item await userEvent.keyboard('{ArrowDown}{Tab}'); await waitFor(() => { expect(textbox).toHaveValue('browser:'); }); expect(textbox).toHaveFocus(); // Should not have executed the search expect(onSearchMock).not.toHaveBeenCalled(); }); it('autocompletes value with enter', async function () { const onSearchMock = jest.fn(); render(); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'bro'); expect( await screen.findByRole('option', {name: 'bro wser - field'}) ).toBeInTheDocument(); // down once to 'browser' dropdown item await userEvent.keyboard('{ArrowDown}{Enter}'); await waitFor(() => { expect(textbox).toHaveValue('browser:'); }); expect(textbox).toHaveFocus(); // Should not have executed the search expect(onSearchMock).not.toHaveBeenCalled(); }); it('searches and completes tags with negation operator', async function () { render(); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, '!bro'); const field = await screen.findByRole('option', {name: 'bro wser - field'}); await userEvent.click(field); expect(textbox).toHaveValue('!browser:'); }); describe('componentWillReceiveProps()', function () { it('should add a space when setting query', function () { render(); expect(screen.getByRole('textbox')).toHaveValue('one '); }); it('updates query when prop changes', function () { const {rerender} = render(); rerender(); expect(screen.getByRole('textbox')).toHaveValue('two '); }); it('updates query when prop set to falsey value', function () { const {rerender} = render(); rerender(); expect(screen.getByRole('textbox')).toHaveValue(''); }); it('should not reset user textarea if a noop props change happens', async function () { const {rerender} = render(); await userEvent.type(screen.getByRole('textbox'), 'two'); rerender(); expect(screen.getByRole('textbox')).toHaveValue('one two'); }); it('should reset user textarea if a meaningful props change happens', async function () { const {rerender} = render(); await userEvent.type(screen.getByRole('textbox'), 'two'); rerender(); expect(screen.getByRole('textbox')).toHaveValue('blah '); }); }); describe('clear search', function () { it('clicking the clear search button clears the query and calls onSearch', async function () { const mockOnSearch = jest.fn(); render( ); expect(screen.getByRole('textbox')).toHaveValue('is:unresolved '); await userEvent.click(screen.getByRole('button', {name: 'Clear search'})); expect(screen.getByRole('textbox')).toHaveValue(''); expect(mockOnSearch).toHaveBeenCalledTimes(1); expect(mockOnSearch).toHaveBeenCalledWith(''); }); }); describe('dropdown open state', function () { it('opens the dropdown when the search box is clicked', async function () { render(); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); expect(screen.getByTestId('smart-search-dropdown')).toBeInTheDocument(); }); it('opens the dropdown when the search box gains focus', function () { render(); const textbox = screen.getByRole('textbox'); fireEvent.focus(textbox); expect(screen.getByTestId('smart-search-dropdown')).toBeInTheDocument(); }); it('hides the drop down when clicking outside', async function () { render(
); const textbox = screen.getByRole('textbox'); // Open the dropdown fireEvent.focus(textbox); await userEvent.click(screen.getByTestId('test-container')); expect(screen.queryByTestId('smart-search-dropdown')).not.toBeInTheDocument(); }); it('hides the drop down when pressing escape', async function () { render(); const textbox = screen.getByRole('textbox'); // Open the dropdown fireEvent.focus(textbox); await userEvent.type(textbox, '{Escape}'); expect(screen.queryByTestId('smart-search-dropdown')).not.toBeInTheDocument(); }); }); describe('pasting', function () { it('trims pasted content', async function () { const mockOnChange = jest.fn(); render(); const textbox = screen.getByRole('textbox'); fireEvent.paste(textbox, {clipboardData: {getData: () => ' something'}}); expect(textbox).toHaveValue('something'); await waitFor(() => expect(mockOnChange).toHaveBeenCalledWith('something', expect.anything()) ); }); }); it('invokes onSearch() on enter', async function () { const mockOnSearch = jest.fn(); render(); await userEvent.type(screen.getByRole('textbox'), '{Enter}'); expect(mockOnSearch).toHaveBeenCalledWith('test'); }); it('handles an empty query', function () { render(); expect(screen.getByRole('textbox')).toHaveValue(''); }); it('does not fetch tag values with environment tag and excludeEnvironment', async function () { const getTagValuesMock = jest.fn().mockResolvedValue([]); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'environment:'); expect(getTagValuesMock).not.toHaveBeenCalled(); }); it('does not fetch tag values with timesSeen tag', async function () { const getTagValuesMock = jest.fn().mockResolvedValue([]); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'timesSeen:'); expect(getTagValuesMock).not.toHaveBeenCalled(); }); it('fetches and displays tag values with other tags', async function () { const getTagValuesMock = jest.fn().mockResolvedValue([]); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'browser:'); expect(getTagValuesMock).toHaveBeenCalledTimes(1); }); it('shows correct options on cursor changes for keys and values', async function () { const getTagValuesMock = jest.fn().mockResolvedValue([]); render( ); const textbox = screen.getByRole('textbox'); // Set cursor to beginning of "is" tag await userEvent.click(textbox); textbox.setSelectionRange(0, 0); // Should show "Keys" section expect(await screen.findByText('Keys')).toBeInTheDocument(); // Set cursor to middle of "is" tag await userEvent.keyboard('{ArrowRight}'); // Should show "Keys" and NOT "Operator Helpers" or "Values" expect(await screen.findByText('Keys')).toBeInTheDocument(); expect(screen.queryByText('Operator Helpers')).not.toBeInTheDocument(); expect(screen.queryByText('Values')).not.toBeInTheDocument(); // Set cursor to end of "is" tag await userEvent.keyboard('{ArrowRight}'); // Should show "Tags" and "Operator Helpers" but NOT "Values" expect(await screen.findByText('Keys')).toBeInTheDocument(); expect(screen.queryByText('Operator Helpers')).toBeInTheDocument(); expect(screen.queryByText('Values')).not.toBeInTheDocument(); // Set cursor after the ":" await userEvent.keyboard('{ArrowRight}'); // Should show "Values" and "Operator Helpers" but NOT "Keys" expect(await screen.findByText('Values')).toBeInTheDocument(); expect(await screen.findByText('Operator Helpers')).toBeInTheDocument(); expect(screen.queryByText('Keys')).not.toBeInTheDocument(); // Set cursor inside value await userEvent.keyboard('{ArrowRight}'); // Should show "Values" and NOT "Operator Helpers" or "Keys" expect(await screen.findByText('Values')).toBeInTheDocument(); expect(screen.queryByText('Operator Helpers')).not.toBeInTheDocument(); expect(screen.queryByText('Keys')).not.toBeInTheDocument(); }); it('shows syntax error for incorrect tokens', function () { render(); // Should have three invalid tokens (tag:, is:, and has:) expect(screen.getAllByTestId('filter-token-invalid')).toHaveLength(3); }); it('renders nested keys correctly', async function () { render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'nest'); await screen.findByText('Keys'); }); it('filters keys on name and description', async function () { render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'event'); await screen.findByText('Keys'); // Should show event.type (has event in key) and device.charging (has event in description) expect(screen.getByRole('option', {name: /event . type/})).toBeInTheDocument(); expect(screen.getByRole('option', {name: /charging/})).toBeInTheDocument(); // But not device.arch (not in key or description) expect(screen.queryByRole('option', {name: /arch/})).not.toBeInTheDocument(); }); it('handles autocomplete race conditions when cursor position changed', async function () { jest.useFakeTimers(); const user = userEvent.setup({delay: null}); const mockOnGetTagValues = jest.fn().mockImplementation( () => new Promise(resolve => { setTimeout(() => { resolve(['value']); }, 300); }) ); render( ); const textbox = screen.getByRole('textbox'); // Type key and start searching values await user.type(textbox, 'is:'); act(() => jest.advanceTimersByTime(200)); // Before values have finished searching, clear the textbox await user.clear(textbox); act(jest.runAllTimers); // Should show keys, not values in dropdown expect(await screen.findByText('Keys')).toBeInTheDocument(); expect(screen.queryByText('Values')).not.toBeInTheDocument(); jest.useRealTimers(); }); it('autocompletes tag values', async function () { const mockOnChange = jest.fn(); const getTagValuesMock = jest.fn().mockResolvedValue(['Chrome', 'Firefox']); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'browser:'); const option = await screen.findByRole('option', {name: /Firefox/}); await userEvent.click(option, {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'browser:Firefox ', expect.anything() ); }); }); it('autocompletes tag values when there are other tags', async function () { const mockOnChange = jest.fn(); const getTagValuesMock = jest.fn().mockResolvedValue(['Chrome', 'Firefox']); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'is:unresolved browser'.length, initialSelectionEnd: 'is:unresolved browser'.length, }); const option = await screen.findByRole('option', {name: /Firefox/}); await userEvent.click(option, {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'is:unresolved browser:Firefox error.handled:true ', expect.anything() ); }); }); it('autocompletes tag values (user tag)', async function () { jest.useFakeTimers(); const mockOnChange = jest.fn(); const getTagValuesMock = jest.fn().mockResolvedValue(['id:1']); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'user:', {delay: null}); act(jest.runOnlyPendingTimers); const option = await screen.findByRole('option', {name: /id:1/}); await userEvent.click(option, {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith('user:"id:1" ', expect.anything()); }); jest.useRealTimers(); }); it('autocompletes assigned from string values', async function () { const mockOnChange = jest.fn(); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'assigned:', {delay: null}); await userEvent.click(await screen.findByRole('option', {name: /#team-a/}), { delay: null, }); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'assigned:#team-a ', expect.anything() ); }); }); it('autocompletes assigned from SearchGroup objects', async function () { const mockOnChange = jest.fn(); render( , children: [ { value: 'me', desc: 'me', type: ItemType.TAG_VALUE, }, ], }, { title: 'All Values', type: 'header', icon: , children: [ { value: '#team-a', desc: '#team-a', type: ItemType.TAG_VALUE, }, ], }, ], }, }} /> ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'assigned:', {delay: null}); expect(await screen.findByText('Suggested Values')).toBeInTheDocument(); expect(screen.getByText('All Values')).toBeInTheDocument(); // Filter down to "team" await userEvent.type(textbox, 'team', {delay: null}); expect(screen.queryByText('Suggested Values')).not.toBeInTheDocument(); await userEvent.click(screen.getByRole('option', {name: /#team-a/}), {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'assigned:#team-a ', expect.anything() ); }); }); it('autocompletes tag values (predefined values with spaces)', async function () { jest.useFakeTimers(); const mockOnChange = jest.fn(); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'predefined:', {delay: null}); act(jest.runOnlyPendingTimers); const option = await screen.findByRole('option', { name: /predefined tag with spaces/, }); await userEvent.click(option, {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'predefined:"predefined tag with spaces" ', expect.anything() ); }); jest.useRealTimers(); }); it('autocompletes tag values (predefined values with quotes)', async function () { jest.useFakeTimers(); const mockOnChange = jest.fn(); render( ); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'predefined:', {delay: null}); act(jest.runOnlyPendingTimers); const option = await screen.findByRole('option', { name: /quotes/, }); await userEvent.click(option, {delay: null}); await waitFor(() => { expect(mockOnChange).toHaveBeenLastCalledWith( 'predefined:"\\"predefined\\" \\"tag\\" \\"with\\" \\"quotes\\"" ', expect.anything() ); }); jest.useRealTimers(); }); describe('quick actions', function () { it('can delete tokens', async function () { render( ); const textbox = screen.getByRole('textbox'); // Put cursor inside is:resolved await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 0, initialSelectionEnd: 0, }); await userEvent.click(screen.getByRole('button', {name: /Delete/})); expect(textbox).toHaveValue('sdk.name:sentry-cocoa has:key'); }); it('can delete a middle token', async function () { render( ); const textbox = screen.getByRole('textbox'); // Put cursor inside sdk.name await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'is:unresolved '.length, initialSelectionEnd: 'is:unresolved '.length, }); await userEvent.click(screen.getByRole('button', {name: /Delete/})); expect(textbox).toHaveValue('is:unresolved has:key'); }); it('can exclude a token', async function () { render( ); const textbox = screen.getByRole('textbox'); // Put cursor inside sdk.name await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'is:unresolved '.length, initialSelectionEnd: 'is:unresolved '.length, }); await userEvent.click(screen.getByRole('button', {name: /Exclude/})); expect(textbox).toHaveValue('is:unresolved !sdk.name:sentry-cocoa has:key '); }); it('can include a token', async function () { render( ); const textbox = screen.getByRole('textbox'); // Put cursor inside sdk.name await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'is:unresolved !'.length, initialSelectionEnd: 'is:unresolved !'.length, }); expect(textbox).toHaveValue('is:unresolved !sdk.name:sentry-cocoa has:key '); await screen.findByRole('button', {name: /Include/}); await userEvent.click(screen.getByRole('button', {name: /Include/})); expect(textbox).toHaveValue('is:unresolved sdk.name:sentry-cocoa has:key '); }); }); it('displays invalid field message', async function () { render(); const textbox = screen.getByRole('textbox'); await userEvent.type(textbox, 'invalid:'); expect( await screen.findByRole('option', {name: /the field invalid isn't supported here/i}) ).toBeInTheDocument(); }); it('displays invalid field messages for when wildcard is disallowed', async function () { render(); const textbox = screen.getByRole('textbox'); // Value await userEvent.type(textbox, 'release:*'); expect( await screen.findByRole('option', {name: /Wildcards aren't supported here/i}) ).toBeInTheDocument(); await userEvent.clear(textbox); // FreeText await userEvent.type(textbox, 'rel*ease'); expect( await screen.findByRole('option', {name: /Wildcards aren't supported here/i}) ).toBeInTheDocument(); }); describe('date fields', () => { // Transpile the lazy-loaded datepicker up front so tests don't flake beforeAll(async function () { await import('sentry/components/calendar/datePicker'); }); it('displays date picker dropdown when appropriate', async () => { render(); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); expect(screen.queryByTestId('search-bar-date-picker')).not.toBeInTheDocument(); // Just lastSeen: will display relative and absolute options, not the datepicker await userEvent.type(textbox, 'lastSeen:'); expect(screen.queryByTestId('search-bar-date-picker')).not.toBeInTheDocument(); expect(screen.getByText('Last hour')).toBeInTheDocument(); expect(screen.getByText('After a custom datetime')).toBeInTheDocument(); // lastSeen:> should open the date picker await userEvent.type(textbox, '>'); expect(screen.getByTestId('search-bar-date-picker')).toBeInTheDocument(); // Continues to display with date typed out await userEvent.type(textbox, '2022-01-01'); expect(screen.getByTestId('search-bar-date-picker')).toBeInTheDocument(); // Goes away when on next term await userEvent.type(textbox, ' '); expect(screen.queryByTestId('search-bar-date-picker')).not.toBeInTheDocument(); // Pops back up when cursor is back in date token await userEvent.keyboard('{arrowleft}'); expect(screen.getByTestId('search-bar-date-picker')).toBeInTheDocument(); // Moving cursor inside the `lastSeen` token hides the date picker textbox.setSelectionRange(1, 1); await userEvent.click(textbox); expect(screen.queryByTestId('search-bar-date-picker')).not.toBeInTheDocument(); }); it('can select a suggested relative time value', async () => { render(); await userEvent.type(screen.getByRole('textbox'), 'lastSeen:'); await userEvent.click(screen.getByText('Last hour')); expect(screen.getByRole('textbox')).toHaveValue('lastSeen:-1h '); }); it('can select a specific date/time', async () => { render(); await userEvent.type(screen.getByRole('textbox'), 'lastSeen:'); await userEvent.click(screen.getByText('After a custom datetime')); // Should have added '>' to query and show a date picker expect(screen.getByRole('textbox')).toHaveValue('lastSeen:>'); expect(screen.getByTestId('search-bar-date-picker')).toBeInTheDocument(); // Select a day on the calendar const dateInput = await screen.findByTestId('date-picker'); fireEvent.change(dateInput, {target: {value: '2022-01-02'}}); expect(screen.getByRole('textbox')).toHaveValue( // -05:00 because our tests run in EST 'lastSeen:>2022-01-02T00:00:00-05:00' ); const timeInput = screen.getByLabelText('Time'); // Simulate changing time input one bit at a time await userEvent.click(timeInput); fireEvent.change(timeInput, {target: {value: '01:00:00'}}); fireEvent.change(timeInput, {target: {value: '01:02:00'}}); fireEvent.change(timeInput, {target: {value: '01:02:03'}}); // Time input should have retained focus this whole time expect(timeInput).toHaveFocus(); fireEvent.blur(timeInput); expect(screen.getByRole('textbox')).toHaveValue( 'lastSeen:>2022-01-02T01:02:03-05:00' ); // Toggle UTC on, which should remove the timezone (-05:00) from the query await userEvent.click(screen.getByLabelText('Use UTC')); expect(screen.getByRole('textbox')).toHaveValue('lastSeen:>2022-01-02T01:02:03'); }); it('can change an existing datetime', async () => { render(); const textbox = screen.getByRole('textbox'); fireEvent.change(textbox, { target: {value: 'lastSeen:2022-01-02 firstSeen:2022-01-01'}, }); // Move cursor to the lastSeen date await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'lastSeen:2022-01-0'.length, initialSelectionEnd: 'lastSeen:2022-01-0'.length, }); const dateInput = await screen.findByTestId('date-picker'); expect(dateInput).toHaveValue('2022-01-02'); expect(screen.getByLabelText('Time')).toHaveValue('00:00:00'); expect(screen.getByLabelText('Use UTC')).toBeChecked(); fireEvent.change(dateInput, {target: {value: '2022-01-03'}}); expect(textbox).toHaveValue('lastSeen:2022-01-03T00:00:00 firstSeen:2022-01-01'); // Cursor should be at end of the value we just replaced expect(textbox.selectionStart).toBe('lastSeen:2022-01-03T00:00:00'.length); }); it('populates the date picker correctly for date without time', async () => { render(); const textbox = screen.getByRole('textbox'); // Move cursor to the timestamp await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'lastSeen:2022-01-0'.length, initialSelectionEnd: 'lastSeen:2022-01-0'.length, }); const dateInput = await screen.findByTestId('date-picker'); expect(dateInput).toHaveValue('2022-01-01'); // No time provided, so time input should be the default value expect(screen.getByLabelText('Time')).toHaveValue('00:00:00'); // UTC is checked because there is no timezone expect(screen.getByLabelText('Use UTC')).toBeChecked(); }); it('populates the date picker correctly for date with time and no timezone', async () => { render(); const textbox = screen.getByRole('textbox'); // Move cursor to the timestamp await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'lastSeen:2022-01-0'.length, initialSelectionEnd: 'lastSeen:2022-01-0'.length, }); const dateInput = await screen.findByTestId('date-picker'); expect(dateInput).toHaveValue('2022-01-01'); expect(screen.getByLabelText('Time')).toHaveValue('09:45:12'); expect(screen.getByLabelText('Use UTC')).toBeChecked(); }); it('populates the date picker correctly for date with time and timezone', async () => { render( ); const textbox = screen.getByRole('textbox'); // Move cursor to the timestamp await userEvent.type(textbox, '{ArrowRight}', { initialSelectionStart: 'lastSeen:2022-01-0'.length, initialSelectionEnd: 'lastSeen:2022-01-0'.length, }); const dateInput = await screen.findByTestId('date-picker'); expect(dateInput).toHaveValue('2022-01-01'); expect(screen.getByLabelText('Time')).toHaveValue('09:45:12'); expect(screen.getByLabelText('Use UTC')).not.toBeChecked(); }); }); describe('defaultSearchGroup', () => { const defaultSearchGroup = { title: 'default search group', type: 'header', // childrenWrapper allows us to arrange the children with custom styles childrenWrapper: props => (
), children: [ { type: ItemType.RECOMMENDED, title: 'Assignee', value: 'assigned_or_suggested:', }, ], }; it('displays a default group with custom wrapper', async function () { const mockOnChange = jest.fn(); render( ); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); expect(screen.getByTestId('default-search-group-wrapper')).toBeInTheDocument(); expect(screen.getByText('default search group')).toBeInTheDocument(); // Default group is correctly added to the dropdown await userEvent.keyboard('{ArrowDown}{Enter}'); expect(mockOnChange).toHaveBeenCalledWith( 'assigned_or_suggested:', expect.anything() ); }); it('hides the default group after typing', async function () { render( ); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); expect(screen.getByTestId('default-search-group-wrapper')).toBeInTheDocument(); await userEvent.type(textbox, 'f'); expect( screen.queryByTestId('default-search-group-wrapper') ).not.toBeInTheDocument(); }); it('hides the default group after picking item with applyFilter', async function () { render( item.title === 'device', }, ], }} /> ); const textbox = screen.getByRole('textbox'); await userEvent.click(textbox); expect(await screen.findByText('User identification value')).toBeInTheDocument(); await userEvent.click(screen.getByText('Custom Tags')); expect(screen.queryByText('Custom Tags')).not.toBeInTheDocument(); expect(screen.queryByText('User identification value')).not.toBeInTheDocument(); expect(screen.getByText('device')).toBeInTheDocument(); }); }); });