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(, 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(, 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(, 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(, 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(
,
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(, 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(, 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(, 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(, 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(, 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(, 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(, 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(, 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(, options);
await tick();
wrapper.update();
setQuery(wrapper, 'browser:Nothing');
setQuery(wrapper, 'browser:Something');
expect(emptyTagValuesMock).toHaveBeenCalledTimes(2);
});
});