index.spec.jsx 22 KB

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