123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- import {mountWithTheme} from 'sentry-test/enzyme';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import SearchBar from 'sentry/components/events/searchBar';
- import TagStore from 'sentry/stores/tagStore';
- const focusTextarea = el => el.find('textarea[name="query"]').simulate('focus');
- const selectNthAutocompleteItem = async (el, index) => {
- focusTextarea(el);
- el.find('SearchListItem[data-test-id="search-autocomplete-item"]')
- .at(index)
- .simulate('click');
- const textarea = el.find('textarea');
- textarea
- .getDOMNode()
- .setSelectionRange(textarea.prop('value').length, textarea.prop('value').length);
- await tick();
- await el.update();
- };
- const setQuery = async (el, query) => {
- el.find('textarea').simulate('focus');
- el.find('textarea')
- .simulate('change', {target: {value: query}})
- .getDOMNode()
- .setSelectionRange(query.length, query.length);
- await tick();
- await el.update();
- };
- describe('Events > SearchBar', function () {
- let options;
- let tagValuesMock;
- let organization;
- let props;
- beforeEach(function () {
- organization = TestStubs.Organization();
- props = {
- organization,
- projectIds: [1, 2],
- };
- TagStore.reset();
- TagStore.loadTagsSuccess([
- {count: 3, key: 'gpu', name: 'Gpu'},
- {count: 3, key: 'mytag', name: 'Mytag'},
- {count: 0, key: 'browser', name: 'Browser'},
- ]);
- options = TestStubs.routerContext();
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/recent-searches/',
- method: 'POST',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/recent-searches/',
- body: [],
- });
- tagValuesMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/tags/gpu/values/',
- body: [{count: 2, name: 'Nvidia 1080ti'}],
- });
- });
- afterEach(function () {
- MockApiClient.clearMockResponses();
- });
- it('autocompletes measurement names', async function () {
- const initializationObj = initializeOrg({
- organization: {
- features: ['performance-view'],
- },
- });
- props.organization = initializationObj.organization;
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- setQuery(wrapper, 'fcp');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('fcp');
- expect(wrapper.find('SearchDropdown SearchItemTitleWrapper').first().text()).toEqual(
- 'measurements.fcp'
- );
- });
- it('autocompletes release semver queries', async function () {
- const initializationObj = initializeOrg();
- props.organization = initializationObj.organization;
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- setQuery(wrapper, 'release.');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('release.');
- expect(wrapper.find('SearchDropdown FirstWordWrapper').first().text()).toEqual(
- 'release'
- );
- expect(wrapper.find('SearchDropdown RestOfWordsContainer').first().text()).toEqual(
- '.build'
- );
- });
- it('autocomplete has suggestions correctly', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- setQuery(wrapper, 'has:');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('');
- expect(wrapper.find('SearchDropdown Value').contains('gpu')).toBe(true);
- const itemIndex = wrapper
- .find('SearchListItem[data-test-id="search-autocomplete-item"]')
- .map(node => node)
- .findIndex(node => node.text() === 'gpu');
- expect(itemIndex).not.toBe(-1);
- selectNthAutocompleteItem(wrapper, itemIndex);
- wrapper.update();
- // the trailing space is important here as without it, autocomplete suggestions will
- // try to complete `has:gpu` thinking the token has not ended yet
- expect(wrapper.find('textarea').prop('value')).toBe('has:gpu ');
- });
- it('searches and selects an event field value', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- setQuery(wrapper, 'gpu:');
- expect(tagValuesMock).toHaveBeenCalledWith(
- '/organizations/org-slug/tags/gpu/values/',
- expect.objectContaining({
- query: {project: ['1', '2'], statsPeriod: '14d', includeTransactions: '1'},
- })
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('');
- expect(wrapper.find('SearchDropdown Value').at(2).text()).toEqual('"Nvidia 1080ti"');
- selectNthAutocompleteItem(wrapper, 2);
- wrapper.update();
- expect(wrapper.find('textarea').prop('value')).toBe('gpu:"Nvidia 1080ti" ');
- });
- it('if `useFormWrapper` is false, pressing enter when there are no dropdown items selected should blur and call `onSearch` callback', async function () {
- const onBlur = jest.fn();
- const onSearch = jest.fn();
- const wrapper = mountWithTheme(
- <SearchBar {...props} useFormWrapper={false} onSearch={onSearch} onBlur={onBlur} />,
- options
- );
- await tick();
- wrapper.update();
- setQuery(wrapper, 'gpu:');
- await tick();
- wrapper.update();
- expect(tagValuesMock).toHaveBeenCalledWith(
- '/organizations/org-slug/tags/gpu/values/',
- expect.objectContaining({
- query: {project: ['1', '2'], statsPeriod: '14d', includeTransactions: '1'},
- })
- );
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('');
- expect(wrapper.find('SearchDropdown Value').contains('"Nvidia 1080ti"')).toBe(true);
- selectNthAutocompleteItem(wrapper, 2);
- wrapper.find('textarea').simulate('keydown', {key: 'Enter'});
- expect(onSearch).toHaveBeenCalledTimes(1);
- });
- it('filters dropdown to accommodate for num characters left in query', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} maxQueryLength={5} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, 'g');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('g');
- expect(wrapper.find('SearchDropdown SearchItemTitleWrapper')).toEqual({});
- expect(
- wrapper.find('SearchListItem[data-test-id="search-autocomplete-item"]')
- ).toHaveLength(2);
- });
- it('returns zero dropdown suggestions if out of characters', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} maxQueryLength={2} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, 'g');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchDropdown').prop('searchSubstring')).toEqual('g');
- expect(wrapper.find('SearchDropdown SearchItemTitleWrapper')).toEqual({});
- expect(
- wrapper.find('SearchListItem[data-test-id="search-autocomplete-item"]')
- ).toHaveLength(0);
- });
- it('sets maxLength property', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} maxQueryLength={10} />, options);
- await tick();
- expect(wrapper.find('textarea').prop('maxLength')).toBe(10);
- });
- it('does not requery for event field values if query does not change', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, 'gpu:');
- await tick();
- wrapper.update();
- // Click will fire "updateAutocompleteItems"
- wrapper.find('textarea').simulate('click');
- await tick();
- wrapper.update();
- expect(tagValuesMock).toHaveBeenCalledTimes(1);
- });
- it('removes highlight when query is empty', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, 'gpu');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchItemTitleWrapper strong').text()).toBe('gpu');
- // Should have nothing highlighted
- setQuery(wrapper, '');
- await tick();
- wrapper.update();
- expect(wrapper.find('SearchItemTitleWrapper strong')).toHaveLength(0);
- });
- it('ignores negation ("!") at the beginning of search term', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, '!gp');
- await tick();
- wrapper.update();
- expect(
- wrapper.find('SearchListItem[data-test-id="search-autocomplete-item"]')
- ).toHaveLength(1);
- expect(
- wrapper.find('SearchListItem[data-test-id="search-autocomplete-item"]').text()
- ).toMatch(/^gpu/);
- });
- it('ignores wildcard ("*") at the beginning of tag value query', async function () {
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, '!gpu:*');
- await tick();
- wrapper.update();
- expect(tagValuesMock).toHaveBeenCalledWith(
- '/organizations/org-slug/tags/gpu/values/',
- expect.objectContaining({
- query: {project: ['1', '2'], statsPeriod: '14d', includeTransactions: '1'},
- })
- );
- selectNthAutocompleteItem(wrapper, 0);
- expect(wrapper.find('textarea').prop('value')).toBe('!gpu:"Nvidia 1080ti" ');
- });
- it('stops searching after no values are returned', async function () {
- const emptyTagValuesMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/tags/browser/values/',
- body: [],
- });
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- // Do 3 searches, the first will find nothing, so no more requests should be made
- setQuery(wrapper, 'browser:Nothing');
- await tick();
- setQuery(wrapper, 'browser:NothingE');
- await tick();
- setQuery(wrapper, 'browser:NothingEls');
- await tick();
- expect(emptyTagValuesMock).toHaveBeenCalledTimes(1);
- });
- it('continues searching after no values if query changes', async function () {
- const emptyTagValuesMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/tags/browser/values/',
- body: [],
- });
- const wrapper = mountWithTheme(<SearchBar {...props} />, options);
- await tick();
- wrapper.update();
- setQuery(wrapper, 'browser:Nothing');
- setQuery(wrapper, 'browser:Something');
- expect(emptyTagValuesMock).toHaveBeenCalledTimes(2);
- });
- });
|