index.spec.jsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {Client} from 'app/api';
  3. import {SmartSearchBar} from 'app/components/smartSearchBar';
  4. import TagStore from 'app/stores/tagStore';
  5. describe('SmartSearchBar', function () {
  6. let location, options, organization, supportedTags;
  7. let environmentTagValuesMock;
  8. const tagValuesMock = jest.fn(() => Promise.resolve([]));
  9. const mockCursorPosition = (component, pos) => {
  10. delete component.cursorPosition;
  11. Object.defineProperty(component, 'cursorPosition', {
  12. get: jest.fn().mockReturnValue(pos),
  13. configurable: true,
  14. });
  15. };
  16. beforeEach(function () {
  17. TagStore.reset();
  18. TagStore.onLoadTagsSuccess(TestStubs.Tags());
  19. tagValuesMock.mockClear();
  20. supportedTags = TagStore.getAllTags();
  21. supportedTags.firstRelease = {
  22. key: 'firstRelease',
  23. name: 'firstRelease',
  24. };
  25. organization = TestStubs.Organization({id: '123', features: ['improved-search']});
  26. location = {
  27. pathname: '/organizations/org-slug/recent-searches/',
  28. query: {
  29. projectId: '0',
  30. },
  31. };
  32. options = TestStubs.routerContext([
  33. {
  34. organization,
  35. location,
  36. router: {location},
  37. },
  38. ]);
  39. MockApiClient.clearMockResponses();
  40. MockApiClient.addMockResponse({
  41. url: '/organizations/org-slug/recent-searches/',
  42. body: [],
  43. });
  44. environmentTagValuesMock = MockApiClient.addMockResponse({
  45. url: '/projects/123/456/tags/environment/values/',
  46. body: [],
  47. });
  48. });
  49. afterEach(function () {
  50. MockApiClient.clearMockResponses();
  51. });
  52. it('quotes in values with spaces when autocompleting', async function () {
  53. jest.useRealTimers();
  54. const getTagValuesMock = jest.fn().mockImplementation(() => {
  55. return Promise.resolve(['this is filled with spaces']);
  56. });
  57. const onSearch = jest.fn();
  58. const props = {
  59. orgId: 'org-slug',
  60. projectId: '0',
  61. query: '',
  62. location,
  63. organization,
  64. supportedTags,
  65. onGetTagValues: getTagValuesMock,
  66. onSearch,
  67. };
  68. const searchBar = mountWithTheme(
  69. <SmartSearchBar {...props} api={new Client()} />,
  70. options
  71. );
  72. searchBar.find('textarea').simulate('focus');
  73. searchBar.find('textarea').simulate('change', {target: {value: 'device:this'}});
  74. await tick();
  75. const preventDefault = jest.fn();
  76. searchBar.find('textarea').simulate('keyDown', {key: 'ArrowDown'});
  77. searchBar.find('textarea').simulate('keyDown', {key: 'Enter', preventDefault});
  78. await tick();
  79. expect(searchBar.find('textarea').props().value).toEqual(
  80. 'device:"this is filled with spaces" '
  81. );
  82. });
  83. it('escapes quotes in values properly when autocompleting', async function () {
  84. jest.useRealTimers();
  85. const getTagValuesMock = jest.fn().mockImplementation(() => {
  86. return Promise.resolve(['this " is " filled " with " quotes']);
  87. });
  88. const onSearch = jest.fn();
  89. const props = {
  90. orgId: 'org-slug',
  91. projectId: '0',
  92. query: '',
  93. location,
  94. organization,
  95. supportedTags,
  96. onGetTagValues: getTagValuesMock,
  97. onSearch,
  98. };
  99. const searchBar = mountWithTheme(
  100. <SmartSearchBar {...props} api={new Client()} />,
  101. options
  102. );
  103. searchBar.find('textarea').simulate('focus');
  104. searchBar.find('textarea').simulate('change', {target: {value: 'device:this'}});
  105. await tick();
  106. const preventDefault = jest.fn();
  107. searchBar.find('textarea').simulate('keyDown', {key: 'ArrowDown'});
  108. searchBar.find('textarea').simulate('keyDown', {key: 'Enter', preventDefault});
  109. await tick();
  110. expect(searchBar.find('textarea').props().value).toEqual(
  111. 'device:"this \\" is \\" filled \\" with \\" quotes" '
  112. );
  113. });
  114. it('does not preventDefault when there are no search items and is loading and enter is pressed', async function () {
  115. jest.useRealTimers();
  116. const getTagValuesMock = jest.fn().mockImplementation(() => {
  117. return new Promise(() => {});
  118. });
  119. const onSearch = jest.fn();
  120. const props = {
  121. orgId: 'org-slug',
  122. projectId: '0',
  123. query: '',
  124. location,
  125. organization,
  126. supportedTags,
  127. onGetTagValues: getTagValuesMock,
  128. onSearch,
  129. };
  130. const searchBar = mountWithTheme(
  131. <SmartSearchBar {...props} api={new Client()} />,
  132. options
  133. );
  134. searchBar.find('textarea').simulate('focus');
  135. searchBar.find('textarea').simulate('change', {target: {value: 'browser:'}});
  136. await tick();
  137. // press enter
  138. const preventDefault = jest.fn();
  139. searchBar.find('textarea').simulate('keyDown', {key: 'Enter', preventDefault});
  140. expect(onSearch).not.toHaveBeenCalled();
  141. expect(preventDefault).not.toHaveBeenCalled();
  142. });
  143. it('calls preventDefault when there are existing search items and is loading and enter is pressed', async function () {
  144. jest.useRealTimers();
  145. const getTagValuesMock = jest.fn().mockImplementation(() => {
  146. return new Promise(() => {});
  147. });
  148. const onSearch = jest.fn();
  149. const props = {
  150. orgId: 'org-slug',
  151. projectId: '0',
  152. query: '',
  153. location,
  154. organization,
  155. supportedTags,
  156. onGetTagValues: getTagValuesMock,
  157. onSearch,
  158. };
  159. const searchBar = mountWithTheme(
  160. <SmartSearchBar {...props} api={new Client()} />,
  161. options
  162. );
  163. searchBar.find('textarea').simulate('focus');
  164. searchBar.find('textarea').simulate('change', {target: {value: 'bro'}});
  165. await tick();
  166. // Can't select with tab
  167. searchBar.find('textarea').simulate('keyDown', {key: 'ArrowDown'});
  168. searchBar.find('textarea').simulate('keyDown', {key: 'Tab'});
  169. expect(onSearch).not.toHaveBeenCalled();
  170. searchBar.find('textarea').simulate('change', {target: {value: 'browser:'}});
  171. await tick();
  172. // press enter
  173. const preventDefault = jest.fn();
  174. searchBar.find('textarea').simulate('keyDown', {key: 'Enter', preventDefault});
  175. expect(onSearch).not.toHaveBeenCalled();
  176. // Prevent default since we need to select an item
  177. expect(preventDefault).toHaveBeenCalled();
  178. });
  179. describe('componentWillReceiveProps()', function () {
  180. it('should add a space when setting state.query', function () {
  181. const searchBar = mountWithTheme(
  182. <SmartSearchBar
  183. organization={organization}
  184. location={location}
  185. supportedTags={supportedTags}
  186. query="one"
  187. />,
  188. options
  189. );
  190. expect(searchBar.state().query).toEqual('one ');
  191. });
  192. it('should update state.query if props.query is updated from outside', function () {
  193. const searchBar = mountWithTheme(
  194. <SmartSearchBar
  195. organization={organization}
  196. location={location}
  197. supportedTags={supportedTags}
  198. query="one"
  199. />,
  200. options
  201. );
  202. searchBar.setProps({query: 'two'});
  203. expect(searchBar.state().query).toEqual('two ');
  204. });
  205. it('should update state.query if props.query is updated to null/undefined from outside', function () {
  206. const searchBar = mountWithTheme(
  207. <SmartSearchBar
  208. organization={organization}
  209. location={location}
  210. supportedTags={supportedTags}
  211. query="one"
  212. />,
  213. options
  214. );
  215. searchBar.setProps({query: null});
  216. expect(searchBar.state().query).toEqual('');
  217. });
  218. it('should not reset user textarea if a noop props change happens', function () {
  219. const searchBar = mountWithTheme(
  220. <SmartSearchBar
  221. organization={organization}
  222. location={location}
  223. supportedTags={supportedTags}
  224. query="one"
  225. />,
  226. options
  227. );
  228. searchBar.setState({query: 'two'});
  229. searchBar.setProps({query: 'one'});
  230. expect(searchBar.state().query).toEqual('two');
  231. });
  232. it('should reset user textarea if a meaningful props change happens', function () {
  233. const searchBar = mountWithTheme(
  234. <SmartSearchBar
  235. organization={organization}
  236. location={location}
  237. supportedTags={supportedTags}
  238. query="one"
  239. />,
  240. options
  241. );
  242. searchBar.setState({query: 'two'});
  243. searchBar.setProps({query: 'three'});
  244. expect(searchBar.state().query).toEqual('three ');
  245. });
  246. });
  247. describe('clearSearch()', function () {
  248. it('clears the query', function () {
  249. const props = {
  250. organization,
  251. location,
  252. query: 'is:unresolved ruby',
  253. defaultQuery: 'is:unresolved',
  254. supportedTags,
  255. };
  256. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  257. searchBar.clearSearch();
  258. expect(searchBar.state.query).toEqual('');
  259. });
  260. it('calls onSearch()', async function () {
  261. const props = {
  262. organization,
  263. location,
  264. query: 'is:unresolved ruby',
  265. defaultQuery: 'is:unresolved',
  266. supportedTags,
  267. onSearch: jest.fn(),
  268. };
  269. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  270. await searchBar.clearSearch();
  271. expect(props.onSearch).toHaveBeenCalledWith('');
  272. });
  273. });
  274. describe('onQueryFocus()', function () {
  275. it('displays the drop down', function () {
  276. const searchBar = mountWithTheme(
  277. <SmartSearchBar
  278. organization={organization}
  279. location={location}
  280. supportedTags={supportedTags}
  281. onGetTagValues={tagValuesMock}
  282. />,
  283. options
  284. ).instance();
  285. expect(searchBar.state.inputHasFocus).toBe(false);
  286. searchBar.onQueryFocus();
  287. expect(searchBar.state.inputHasFocus).toBe(true);
  288. });
  289. it('displays dropdown in hasPinnedSearch mode', function () {
  290. const searchBar = mountWithTheme(
  291. <SmartSearchBar
  292. organization={organization}
  293. location={location}
  294. supportedTags={supportedTags}
  295. onGetTagValues={tagValuesMock}
  296. hasPinnedSearch
  297. />,
  298. options
  299. ).instance();
  300. expect(searchBar.state.inputHasFocus).toBe(false);
  301. searchBar.onQueryFocus();
  302. expect(searchBar.state.inputHasFocus).toBe(true);
  303. });
  304. });
  305. describe('onQueryBlur()', function () {
  306. it('hides the drop down', function () {
  307. const searchBar = mountWithTheme(
  308. <SmartSearchBar
  309. organization={organization}
  310. location={location}
  311. supportedTags={supportedTags}
  312. />,
  313. options
  314. ).instance();
  315. searchBar.state.inputHasFocus = true;
  316. jest.useFakeTimers();
  317. searchBar.onQueryBlur({target: {value: 'test'}});
  318. jest.advanceTimersByTime(201); // doesn't close until 200ms
  319. expect(searchBar.state.inputHasFocus).toBe(false);
  320. });
  321. });
  322. describe('onKeyUp()', function () {
  323. describe('escape', function () {
  324. it('blurs the textarea', function () {
  325. const wrapper = mountWithTheme(
  326. <SmartSearchBar
  327. organization={organization}
  328. location={location}
  329. supportedTags={supportedTags}
  330. />,
  331. options
  332. );
  333. wrapper.setState({inputHasFocus: true});
  334. const instance = wrapper.instance();
  335. jest.spyOn(instance, 'blur');
  336. wrapper.find('textarea').simulate('keyup', {key: 'Escape'});
  337. expect(instance.blur).toHaveBeenCalledTimes(1);
  338. });
  339. });
  340. });
  341. describe('render()', function () {
  342. it('invokes onSearch() when submitting the form', function () {
  343. const stubbedOnSearch = jest.fn();
  344. const wrapper = mountWithTheme(
  345. <SmartSearchBar
  346. onSearch={stubbedOnSearch}
  347. organization={organization}
  348. location={location}
  349. query="is:unresolved"
  350. supportedTags={supportedTags}
  351. />,
  352. options
  353. );
  354. wrapper.find('form').simulate('submit', {
  355. preventDefault() {},
  356. });
  357. expect(stubbedOnSearch).toHaveBeenCalledWith('is:unresolved');
  358. });
  359. it('invokes onSearch() when search is cleared', async function () {
  360. jest.useRealTimers();
  361. const props = {
  362. organization,
  363. location,
  364. query: 'is:unresolved',
  365. supportedTags,
  366. onSearch: jest.fn(),
  367. };
  368. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  369. wrapper.find('button[aria-label="Clear search"]').simulate('click');
  370. await tick();
  371. expect(props.onSearch).toHaveBeenCalledWith('');
  372. });
  373. it('invokes onSearch() on submit in hasPinnedSearch mode', function () {
  374. const stubbedOnSearch = jest.fn();
  375. const wrapper = mountWithTheme(
  376. <SmartSearchBar
  377. onSearch={stubbedOnSearch}
  378. organization={organization}
  379. query="is:unresolved"
  380. location={location}
  381. supportedTags={supportedTags}
  382. hasPinnedSearch
  383. />,
  384. options
  385. );
  386. wrapper.find('form').simulate('submit');
  387. expect(stubbedOnSearch).toHaveBeenCalledWith('is:unresolved');
  388. });
  389. });
  390. it('handles an empty query', function () {
  391. const props = {
  392. query: '',
  393. defaultQuery: 'is:unresolved',
  394. organization,
  395. location,
  396. supportedTags,
  397. };
  398. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  399. expect(wrapper.state('query')).toEqual('');
  400. });
  401. describe('updateAutoCompleteItems()', function () {
  402. beforeEach(function () {
  403. jest.useFakeTimers();
  404. });
  405. it('sets state when empty', function () {
  406. const props = {
  407. query: '',
  408. organization,
  409. location,
  410. supportedTags,
  411. };
  412. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  413. searchBar.updateAutoCompleteItems();
  414. expect(searchBar.state.searchTerm).toEqual('');
  415. expect(searchBar.state.searchGroups).toEqual([]);
  416. expect(searchBar.state.activeSearchItem).toEqual(-1);
  417. });
  418. it('sets state when incomplete tag', async function () {
  419. const props = {
  420. query: 'fu',
  421. organization,
  422. location,
  423. supportedTags,
  424. };
  425. jest.useRealTimers();
  426. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  427. const searchBar = wrapper.instance();
  428. wrapper.find('textarea').simulate('focus');
  429. searchBar.updateAutoCompleteItems();
  430. await tick();
  431. wrapper.update();
  432. expect(searchBar.state.searchTerm).toEqual('fu');
  433. expect(searchBar.state.searchGroups).toEqual([
  434. expect.objectContaining({children: []}),
  435. ]);
  436. expect(searchBar.state.activeSearchItem).toEqual(-1);
  437. });
  438. it('sets state when incomplete tag has negation operator', async function () {
  439. const props = {
  440. query: '!fu',
  441. organization,
  442. location,
  443. supportedTags,
  444. };
  445. jest.useRealTimers();
  446. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  447. const searchBar = wrapper.instance();
  448. wrapper.find('textarea').simulate('focus');
  449. searchBar.updateAutoCompleteItems();
  450. await tick();
  451. wrapper.update();
  452. expect(searchBar.state.searchTerm).toEqual('fu');
  453. expect(searchBar.state.searchGroups).toEqual([
  454. expect.objectContaining({children: []}),
  455. ]);
  456. expect(searchBar.state.activeSearchItem).toEqual(-1);
  457. });
  458. it('sets state when incomplete tag as second textarea', async function () {
  459. const props = {
  460. query: 'is:unresolved fu',
  461. organization,
  462. location,
  463. supportedTags,
  464. };
  465. jest.useRealTimers();
  466. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  467. const searchBar = wrapper.instance();
  468. // Cursor is at end of line
  469. mockCursorPosition(searchBar, 15);
  470. searchBar.updateAutoCompleteItems();
  471. await tick();
  472. wrapper.update();
  473. expect(searchBar.state.searchTerm).toEqual('fu');
  474. // 1 items because of headers ("Tags")
  475. expect(searchBar.state.searchGroups).toHaveLength(1);
  476. expect(searchBar.state.activeSearchItem).toEqual(-1);
  477. });
  478. it('does not request values when tag is environments', function () {
  479. const props = {
  480. query: 'environment:production',
  481. excludeEnvironment: true,
  482. location,
  483. organization,
  484. supportedTags,
  485. };
  486. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  487. searchBar.updateAutoCompleteItems();
  488. jest.advanceTimersByTime(301);
  489. expect(environmentTagValuesMock).not.toHaveBeenCalled();
  490. });
  491. it('does not request values when tag is `timesSeen`', function () {
  492. // This should never get called
  493. const mock = MockApiClient.addMockResponse({
  494. url: '/projects/123/456/tags/timesSeen/values/',
  495. body: [],
  496. });
  497. const props = {
  498. query: 'timesSeen:',
  499. organization,
  500. supportedTags,
  501. };
  502. const searchBar = mountWithTheme(
  503. <SmartSearchBar {...props} api={new Client()} />,
  504. options
  505. ).instance();
  506. searchBar.updateAutoCompleteItems();
  507. jest.advanceTimersByTime(301);
  508. expect(mock).not.toHaveBeenCalled();
  509. });
  510. it('requests values when tag is `firstRelease`', function () {
  511. const mock = MockApiClient.addMockResponse({
  512. url: '/organizations/org-slug/releases/',
  513. body: [],
  514. });
  515. const props = {
  516. orgId: 'org-slug',
  517. projectId: '0',
  518. query: 'firstRelease:',
  519. location,
  520. organization,
  521. supportedTags,
  522. };
  523. const searchBar = mountWithTheme(
  524. <SmartSearchBar {...props} api={new Client()} />,
  525. options
  526. ).instance();
  527. mockCursorPosition(searchBar, 13);
  528. searchBar.updateAutoCompleteItems();
  529. jest.advanceTimersByTime(301);
  530. expect(mock).toHaveBeenCalledWith(
  531. '/organizations/org-slug/releases/',
  532. expect.objectContaining({
  533. method: 'GET',
  534. query: {
  535. project: '0',
  536. per_page: 5, // Limit results to 5 for autocomplete
  537. },
  538. })
  539. );
  540. });
  541. it('shows operator autocompletion', async function () {
  542. const props = {
  543. query: 'is:unresolved',
  544. organization,
  545. location,
  546. supportedTags,
  547. };
  548. jest.useRealTimers();
  549. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  550. const searchBar = wrapper.instance();
  551. // Cursor is on ':'
  552. mockCursorPosition(searchBar, 3);
  553. searchBar.updateAutoCompleteItems();
  554. await tick();
  555. wrapper.update();
  556. // two search groups because of operator suggestions
  557. expect(searchBar.state.searchGroups).toHaveLength(2);
  558. expect(searchBar.state.activeSearchItem).toEqual(-1);
  559. });
  560. it('responds to cursor changes', async function () {
  561. const props = {
  562. query: 'is:unresolved',
  563. organization,
  564. location,
  565. supportedTags,
  566. };
  567. jest.useRealTimers();
  568. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  569. const searchBar = wrapper.instance();
  570. // Cursor is on ':'
  571. mockCursorPosition(searchBar, 3);
  572. searchBar.updateAutoCompleteItems();
  573. await tick();
  574. wrapper.update();
  575. // two search groups tags and values
  576. expect(searchBar.state.searchGroups).toHaveLength(2);
  577. expect(searchBar.state.activeSearchItem).toEqual(-1);
  578. mockCursorPosition(searchBar, 1);
  579. searchBar.updateAutoCompleteItems();
  580. await tick();
  581. wrapper.update();
  582. // one search group because only showing tags now
  583. expect(searchBar.state.searchGroups).toHaveLength(1);
  584. expect(searchBar.state.activeSearchItem).toEqual(-1);
  585. });
  586. it('shows errors on incorrect tokens', async function () {
  587. const props = {
  588. query: 'tag: ',
  589. organization,
  590. location,
  591. supportedTags,
  592. };
  593. jest.useRealTimers();
  594. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  595. expect(wrapper.find('Filter').prop('invalid')).toBe(true);
  596. });
  597. });
  598. describe('onAutoComplete()', function () {
  599. it('completes terms from the list', function () {
  600. const props = {
  601. query: 'event.type:error ',
  602. organization,
  603. location,
  604. supportedTags,
  605. };
  606. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  607. searchBar.onAutoComplete('myTag:', {type: 'tag'});
  608. expect(searchBar.state.query).toEqual('event.type:error myTag:');
  609. });
  610. it('completes values if cursor is not at the end', function () {
  611. const props = {
  612. query: 'id: event.type:error ',
  613. organization,
  614. location,
  615. supportedTags,
  616. };
  617. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  618. mockCursorPosition(searchBar, 3);
  619. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  620. expect(searchBar.state.query).toEqual('id:12345 event.type:error ');
  621. });
  622. it('completes values if cursor is at the end', function () {
  623. const props = {
  624. query: 'event.type:error id:',
  625. organization,
  626. location,
  627. supportedTags,
  628. };
  629. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  630. mockCursorPosition(searchBar, 20);
  631. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  632. expect(searchBar.state.query).toEqual('event.type:error id:12345 ');
  633. });
  634. it('triggers onChange', function () {
  635. const onChange = jest.fn();
  636. const props = {
  637. query: 'event.type:error id:',
  638. organization,
  639. location,
  640. supportedTags,
  641. };
  642. const searchBar = mountWithTheme(
  643. <SmartSearchBar {...props} onChange={onChange} />,
  644. options
  645. ).instance();
  646. mockCursorPosition(searchBar, 20);
  647. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  648. expect(onChange).toHaveBeenCalledWith(
  649. 'event.type:error id:12345 ',
  650. expect.anything()
  651. );
  652. });
  653. it('keeps the negation operator is present', function () {
  654. const props = {
  655. query: '',
  656. organization,
  657. location,
  658. supportedTags,
  659. };
  660. const smartSearchBar = mountWithTheme(<SmartSearchBar {...props} />, options);
  661. const searchBar = smartSearchBar.instance();
  662. const textarea = smartSearchBar.find('textarea');
  663. // start typing part of the tag prefixed by the negation operator!
  664. textarea.simulate('change', {target: {value: 'event.type:error !ti'}});
  665. mockCursorPosition(searchBar, 20);
  666. // use autocompletion to do the rest
  667. searchBar.onAutoComplete('title:', {});
  668. expect(searchBar.state.query).toEqual('event.type:error !title:');
  669. });
  670. it('handles special case for user tag', function () {
  671. const props = {
  672. query: '',
  673. organization,
  674. location,
  675. supportedTags,
  676. };
  677. const smartSearchBar = mountWithTheme(<SmartSearchBar {...props} />, options);
  678. const searchBar = smartSearchBar.instance();
  679. const textarea = smartSearchBar.find('textarea');
  680. textarea.simulate('change', {target: {value: 'user:'}});
  681. mockCursorPosition(searchBar, 5);
  682. searchBar.onAutoComplete('id:1', {});
  683. expect(searchBar.state.query).toEqual('user:"id:1" ');
  684. });
  685. });
  686. });