import {mountWithTheme} from 'sentry-test/enzyme';
import {initializeOrg} from 'sentry-test/initializeOrg';
import TagStore from 'sentry/stores/tagStore';
import IssueListSearchBar from 'sentry/views/issueList/searchBar';
describe('IssueListSearchBar', function () {
let tagValuePromise;
let supportedTags;
let recentSearchMock;
const {routerContext, organization} = initializeOrg({
organization: {access: [], features: []},
});
const mockCursorPosition = (wrapper, pos) => {
const component = wrapper.find('SmartSearchBar').instance();
delete component.cursorPosition;
Object.defineProperty(component, 'cursorPosition', {
get: jest.fn().mockReturnValue(pos),
configurable: true,
});
};
beforeEach(function () {
TagStore.reset();
TagStore.loadTagsSuccess(TestStubs.Tags());
supportedTags = TagStore.getStateTags();
// Add a tag that is preseeded with values.
supportedTags.is = {
key: 'is',
name: 'is',
values: ['assigned', 'unresolved', 'ignored'],
predefined: true,
};
tagValuePromise = Promise.resolve([]);
recentSearchMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/recent-searches/',
method: 'GET',
body: [],
});
});
afterEach(function () {
MockApiClient.clearMockResponses();
});
describe('updateAutoCompleteItems()', function () {
it('sets state with complete tag', async () => {
const loader = (key, value) => {
expect(key).toEqual('url');
expect(value).toEqual('fu');
return tagValuePromise;
};
const props = {
organization,
query: 'url:"fu"',
tagValueLoader: loader,
supportedTags,
onSearch: jest.fn(),
};
const searchBar = mountWithTheme(, routerContext);
mockCursorPosition(searchBar, 5);
searchBar.find('textarea').simulate('click');
searchBar.find('textarea').simulate('focus');
await tick();
expect(searchBar.find('SearchDropdown').prop('searchSubstring')).toEqual('"fu"');
expect(searchBar.find('SearchDropdown').prop('items')).toEqual([]);
});
it('sets state when value has colon', async () => {
const loader = (key, value) => {
expect(key).toEqual('url');
expect(value).toEqual('http://example.com');
return tagValuePromise;
};
const props = {
organization,
projectId: '456',
query: 'url:"http://example.com"',
tagValueLoader: loader,
supportedTags,
onSearch: jest.fn(),
};
const searchBar = mountWithTheme(, routerContext);
mockCursorPosition(searchBar, 5);
searchBar.find('textarea').simulate('click');
searchBar.find('textarea').simulate('focus');
await tick();
expect(searchBar.state.searchTerm).toEqual();
expect(searchBar.find('SearchDropdown').prop('searchSubstring')).toEqual(
'"http://example.com"'
);
expect(searchBar.find('SearchDropdown').prop('items')).toEqual([]);
});
it('does not request values when tag is `timesSeen`', async () => {
// This should never get called
const loader = jest.fn(x => x);
const props = {
organization,
projectId: '456',
query: 'timesSeen:',
tagValueLoader: loader,
supportedTags,
onSearch: jest.fn(),
};
const searchBar = mountWithTheme(, routerContext);
searchBar.find('textarea').simulate('click');
searchBar.find('textarea').simulate('focus');
await tick();
expect(loader).not.toHaveBeenCalled();
});
});
describe('Recent Searches', function () {
it('saves search query as a recent search', async function () {
const saveRecentSearch = MockApiClient.addMockResponse({
url: '/organizations/org-slug/recent-searches/',
method: 'POST',
body: {},
});
const loader = (key, value) => {
expect(key).toEqual('url');
expect(value).toEqual('fu');
return tagValuePromise;
};
const onSearch = jest.fn();
const props = {
organization,
query: 'url:"fu"',
onSearch,
tagValueLoader: loader,
supportedTags,
};
const searchBar = mountWithTheme(, routerContext);
mockCursorPosition(searchBar, 5);
searchBar.find('textarea').simulate('focus');
searchBar.find('textarea').simulate('click');
await tick();
expect(searchBar.find('SearchDropdown').prop('searchSubstring')).toEqual('"fu"');
expect(searchBar.find('SearchDropdown').prop('items')).toEqual([]);
jest.useRealTimers();
searchBar.find('form').simulate('submit');
expect(onSearch).toHaveBeenCalledWith('url:"fu"');
await tick();
searchBar.update();
expect(saveRecentSearch).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
data: {
query: 'url:"fu"',
type: 0,
},
})
);
});
it('queries for recent searches', async function () {
const props = {
organization,
query: 'timesSeen:',
tagValueLoader: () => {},
savedSearchType: 0,
displayRecentSearches: true,
supportedTags,
};
jest.useRealTimers();
const wrapper = mountWithTheme(, routerContext);
wrapper.find('textarea').simulate('focus');
wrapper.find('textarea').simulate('change', {target: {value: 'is:'}});
await tick();
wrapper.update();
expect(recentSearchMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: {
query: 'is:',
limit: 3,
type: 0,
},
})
);
});
it('cycles through keyboard navigation for selection', async function () {
const props = {
organization,
query: 'timesSeen:',
tagValueLoader: () => {},
savedSearchType: 0,
displayRecentSearches: true,
supportedTags,
};
jest.useRealTimers();
const wrapper = mountWithTheme(, routerContext);
wrapper
.find('textarea')
.simulate('focus')
.simulate('change', {target: {value: 'is:'}});
await tick();
wrapper.update();
expect(
wrapper.find('SearchListItem').at(0).find('li').prop('className')
).not.toContain('active');
wrapper.find('textarea').simulate('keyDown', {key: 'ArrowDown'});
expect(wrapper.find('SearchListItem').at(0).find('li').prop('className')).toContain(
'active'
);
wrapper.find('textarea').simulate('keyDown', {key: 'ArrowDown'});
expect(wrapper.find('SearchListItem').at(1).find('li').prop('className')).toContain(
'active'
);
wrapper.find('textarea').simulate('keyDown', {key: 'ArrowUp'});
wrapper.find('textarea').simulate('keyDown', {key: 'ArrowUp'});
expect(
wrapper.find('SearchListItem').last().find('li').prop('className')
).toContain('active');
});
});
describe('Pinned Searches', function () {
let pinSearch;
let unpinSearch;
beforeEach(function () {
MockApiClient.clearMockResponses();
pinSearch = MockApiClient.addMockResponse({
url: '/organizations/org-slug/pinned-searches/',
method: 'PUT',
body: {},
});
unpinSearch = MockApiClient.addMockResponse({
url: '/organizations/org-slug/pinned-searches/',
method: 'DELETE',
body: {},
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/recent-searches/',
method: 'GET',
body: [],
});
});
it('has pin icon', function () {
const props = {
query: 'url:"fu"',
onSearch: jest.fn(),
tagValueLoader: () => Promise.resolve([]),
supportedTags,
organization,
};
const searchBar = mountWithTheme(, routerContext);
expect(searchBar.find('ActionButton[data-test-id="pin-icon"]')).toHaveLength(1);
});
it('pins a search from the searchbar', function () {
const props = {
query: 'url:"fu"',
onSearch: jest.fn(),
tagValueLoader: () => Promise.resolve([]),
supportedTags,
organization,
};
const searchBar = mountWithTheme(, routerContext);
searchBar.find('ActionButton[data-test-id="pin-icon"] button').simulate('click');
expect(pinSearch).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
method: 'PUT',
data: {
query: 'url:"fu"',
type: 0,
},
})
);
});
it('unpins a search from the searchbar', function () {
const props = {
query: 'url:"fu"',
onSearch: jest.fn(),
tagValueLoader: () => Promise.resolve([]),
supportedTags,
organization,
savedSearch: {id: '1', isPinned: true, query: 'url:"fu"'},
};
const searchBar = mountWithTheme(, routerContext);
searchBar
.find('ActionButton[aria-label="Unpin this search"] button')
.simulate('click');
expect(unpinSearch).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
method: 'DELETE',
data: {
type: 0,
},
})
);
});
});
});