index.spec.jsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {Client} from 'sentry/api';
  3. import {SmartSearchBar} from 'sentry/components/smartSearchBar';
  4. import TagStore from 'sentry/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'});
  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('onPaste()', function () {
  323. it('trims pasted content', function () {
  324. const onChange = jest.fn();
  325. const wrapper = mountWithTheme(
  326. <SmartSearchBar
  327. organization={organization}
  328. location={location}
  329. supportedTags={supportedTags}
  330. onChange={onChange}
  331. />,
  332. options
  333. );
  334. wrapper.setState({inputHasFocus: true});
  335. const input = ' something ';
  336. wrapper
  337. .find('textarea')
  338. .simulate('paste', {clipboardData: {getData: () => input, value: input}});
  339. wrapper.update();
  340. expect(onChange).toHaveBeenCalledWith('something', expect.anything());
  341. });
  342. });
  343. describe('onKeyUp()', function () {
  344. describe('escape', function () {
  345. it('blurs the textarea', function () {
  346. const wrapper = mountWithTheme(
  347. <SmartSearchBar
  348. organization={organization}
  349. location={location}
  350. supportedTags={supportedTags}
  351. />,
  352. options
  353. );
  354. wrapper.setState({inputHasFocus: true});
  355. const instance = wrapper.instance();
  356. jest.spyOn(instance, 'blur');
  357. wrapper.find('textarea').simulate('keyup', {key: 'Escape'});
  358. expect(instance.blur).toHaveBeenCalledTimes(1);
  359. });
  360. });
  361. });
  362. describe('render()', function () {
  363. it('invokes onSearch() when submitting the form', function () {
  364. const stubbedOnSearch = jest.fn();
  365. const wrapper = mountWithTheme(
  366. <SmartSearchBar
  367. onSearch={stubbedOnSearch}
  368. organization={organization}
  369. location={location}
  370. query="is:unresolved"
  371. supportedTags={supportedTags}
  372. />,
  373. options
  374. );
  375. wrapper.find('form').simulate('submit', {
  376. preventDefault() {},
  377. });
  378. expect(stubbedOnSearch).toHaveBeenCalledWith('is:unresolved');
  379. });
  380. it('invokes onSearch() when search is cleared', async function () {
  381. jest.useRealTimers();
  382. const props = {
  383. organization,
  384. location,
  385. query: 'is:unresolved',
  386. supportedTags,
  387. onSearch: jest.fn(),
  388. };
  389. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  390. wrapper.find('button[aria-label="Clear search"]').simulate('click');
  391. await tick();
  392. expect(props.onSearch).toHaveBeenCalledWith('');
  393. });
  394. it('invokes onSearch() on submit in hasPinnedSearch mode', function () {
  395. const stubbedOnSearch = jest.fn();
  396. const wrapper = mountWithTheme(
  397. <SmartSearchBar
  398. onSearch={stubbedOnSearch}
  399. organization={organization}
  400. query="is:unresolved"
  401. location={location}
  402. supportedTags={supportedTags}
  403. hasPinnedSearch
  404. />,
  405. options
  406. );
  407. wrapper.find('form').simulate('submit');
  408. expect(stubbedOnSearch).toHaveBeenCalledWith('is:unresolved');
  409. });
  410. });
  411. it('handles an empty query', function () {
  412. const props = {
  413. query: '',
  414. defaultQuery: 'is:unresolved',
  415. organization,
  416. location,
  417. supportedTags,
  418. };
  419. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  420. expect(wrapper.state('query')).toEqual('');
  421. });
  422. describe('updateAutoCompleteItems()', function () {
  423. beforeEach(function () {
  424. jest.useFakeTimers();
  425. });
  426. it('sets state when empty', function () {
  427. const props = {
  428. query: '',
  429. organization,
  430. location,
  431. supportedTags,
  432. };
  433. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  434. searchBar.updateAutoCompleteItems();
  435. expect(searchBar.state.searchTerm).toEqual('');
  436. expect(searchBar.state.searchGroups).toEqual([]);
  437. expect(searchBar.state.activeSearchItem).toEqual(-1);
  438. });
  439. it('sets state when incomplete tag', async function () {
  440. const props = {
  441. query: 'fu',
  442. organization,
  443. location,
  444. supportedTags,
  445. };
  446. jest.useRealTimers();
  447. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  448. const searchBar = wrapper.instance();
  449. wrapper.find('textarea').simulate('focus');
  450. searchBar.updateAutoCompleteItems();
  451. await tick();
  452. wrapper.update();
  453. expect(searchBar.state.searchTerm).toEqual('fu');
  454. expect(searchBar.state.searchGroups).toEqual([
  455. expect.objectContaining({children: []}),
  456. ]);
  457. expect(searchBar.state.activeSearchItem).toEqual(-1);
  458. });
  459. it('sets state when incomplete tag has negation operator', async function () {
  460. const props = {
  461. query: '!fu',
  462. organization,
  463. location,
  464. supportedTags,
  465. };
  466. jest.useRealTimers();
  467. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  468. const searchBar = wrapper.instance();
  469. wrapper.find('textarea').simulate('focus');
  470. searchBar.updateAutoCompleteItems();
  471. await tick();
  472. wrapper.update();
  473. expect(searchBar.state.searchTerm).toEqual('fu');
  474. expect(searchBar.state.searchGroups).toEqual([
  475. expect.objectContaining({children: []}),
  476. ]);
  477. expect(searchBar.state.activeSearchItem).toEqual(-1);
  478. });
  479. it('sets state when incomplete tag as second textarea', async function () {
  480. const props = {
  481. query: 'is:unresolved fu',
  482. organization,
  483. location,
  484. supportedTags,
  485. };
  486. jest.useRealTimers();
  487. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  488. const searchBar = wrapper.instance();
  489. // Cursor is at end of line
  490. mockCursorPosition(searchBar, 15);
  491. searchBar.updateAutoCompleteItems();
  492. await tick();
  493. wrapper.update();
  494. expect(searchBar.state.searchTerm).toEqual('fu');
  495. // 1 items because of headers ("Tags")
  496. expect(searchBar.state.searchGroups).toHaveLength(1);
  497. expect(searchBar.state.activeSearchItem).toEqual(-1);
  498. });
  499. it('does not request values when tag is environments', function () {
  500. const props = {
  501. query: 'environment:production',
  502. excludeEnvironment: true,
  503. location,
  504. organization,
  505. supportedTags,
  506. };
  507. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  508. searchBar.updateAutoCompleteItems();
  509. jest.advanceTimersByTime(301);
  510. expect(environmentTagValuesMock).not.toHaveBeenCalled();
  511. });
  512. it('does not request values when tag is `timesSeen`', function () {
  513. // This should never get called
  514. const mock = MockApiClient.addMockResponse({
  515. url: '/projects/123/456/tags/timesSeen/values/',
  516. body: [],
  517. });
  518. const props = {
  519. query: 'timesSeen:',
  520. organization,
  521. supportedTags,
  522. };
  523. const searchBar = mountWithTheme(
  524. <SmartSearchBar {...props} api={new Client()} />,
  525. options
  526. ).instance();
  527. searchBar.updateAutoCompleteItems();
  528. jest.advanceTimersByTime(301);
  529. expect(mock).not.toHaveBeenCalled();
  530. });
  531. it('requests values when tag is `firstRelease`', function () {
  532. const mock = MockApiClient.addMockResponse({
  533. url: '/organizations/org-slug/releases/',
  534. body: [],
  535. });
  536. const props = {
  537. orgId: 'org-slug',
  538. projectId: '0',
  539. query: 'firstRelease:',
  540. location,
  541. organization,
  542. supportedTags,
  543. };
  544. const searchBar = mountWithTheme(
  545. <SmartSearchBar {...props} api={new Client()} />,
  546. options
  547. ).instance();
  548. mockCursorPosition(searchBar, 13);
  549. searchBar.updateAutoCompleteItems();
  550. jest.advanceTimersByTime(301);
  551. expect(mock).toHaveBeenCalledWith(
  552. '/organizations/org-slug/releases/',
  553. expect.objectContaining({
  554. method: 'GET',
  555. query: {
  556. project: '0',
  557. per_page: 5, // Limit results to 5 for autocomplete
  558. },
  559. })
  560. );
  561. });
  562. it('shows operator autocompletion', async function () {
  563. const props = {
  564. query: 'is:unresolved',
  565. organization,
  566. location,
  567. supportedTags,
  568. };
  569. jest.useRealTimers();
  570. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  571. const searchBar = wrapper.instance();
  572. // Cursor is on ':'
  573. mockCursorPosition(searchBar, 3);
  574. searchBar.updateAutoCompleteItems();
  575. await tick();
  576. wrapper.update();
  577. // two search groups because of operator suggestions
  578. expect(searchBar.state.searchGroups).toHaveLength(2);
  579. expect(searchBar.state.activeSearchItem).toEqual(-1);
  580. });
  581. it('responds to cursor changes', async function () {
  582. const props = {
  583. query: 'is:unresolved',
  584. organization,
  585. location,
  586. supportedTags,
  587. };
  588. jest.useRealTimers();
  589. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  590. const searchBar = wrapper.instance();
  591. // Cursor is on ':'
  592. mockCursorPosition(searchBar, 3);
  593. searchBar.updateAutoCompleteItems();
  594. await tick();
  595. wrapper.update();
  596. // two search groups tags and values
  597. expect(searchBar.state.searchGroups).toHaveLength(2);
  598. expect(searchBar.state.activeSearchItem).toEqual(-1);
  599. mockCursorPosition(searchBar, 1);
  600. searchBar.updateAutoCompleteItems();
  601. await tick();
  602. wrapper.update();
  603. // one search group because only showing tags now
  604. expect(searchBar.state.searchGroups).toHaveLength(1);
  605. expect(searchBar.state.activeSearchItem).toEqual(-1);
  606. });
  607. it('shows errors on incorrect tokens', async function () {
  608. const props = {
  609. query: 'tag: is: has: ',
  610. organization,
  611. location,
  612. supportedTags,
  613. };
  614. jest.useRealTimers();
  615. const wrapper = mountWithTheme(<SmartSearchBar {...props} />, options);
  616. wrapper.find('Filter').forEach(filter => {
  617. expect(filter.prop('invalid')).toBe(true);
  618. });
  619. });
  620. });
  621. describe('onAutoComplete()', function () {
  622. it('completes terms from the list', function () {
  623. const props = {
  624. query: 'event.type:error ',
  625. organization,
  626. location,
  627. supportedTags,
  628. };
  629. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  630. searchBar.onAutoComplete('myTag:', {type: 'tag'});
  631. expect(searchBar.state.query).toEqual('event.type:error myTag:');
  632. });
  633. it('completes values if cursor is not at the end', function () {
  634. const props = {
  635. query: 'id: event.type:error ',
  636. organization,
  637. location,
  638. supportedTags,
  639. };
  640. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  641. mockCursorPosition(searchBar, 3);
  642. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  643. expect(searchBar.state.query).toEqual('id:12345 event.type:error ');
  644. });
  645. it('completes values if cursor is at the end', function () {
  646. const props = {
  647. query: 'event.type:error id:',
  648. organization,
  649. location,
  650. supportedTags,
  651. };
  652. const searchBar = mountWithTheme(<SmartSearchBar {...props} />, options).instance();
  653. mockCursorPosition(searchBar, 20);
  654. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  655. expect(searchBar.state.query).toEqual('event.type:error id:12345 ');
  656. });
  657. it('triggers onChange', function () {
  658. const onChange = jest.fn();
  659. const props = {
  660. query: 'event.type:error id:',
  661. organization,
  662. location,
  663. supportedTags,
  664. };
  665. const searchBar = mountWithTheme(
  666. <SmartSearchBar {...props} onChange={onChange} />,
  667. options
  668. ).instance();
  669. mockCursorPosition(searchBar, 20);
  670. searchBar.onAutoComplete('12345', {type: 'tag-value'});
  671. expect(onChange).toHaveBeenCalledWith(
  672. 'event.type:error id:12345 ',
  673. expect.anything()
  674. );
  675. });
  676. it('keeps the negation operator is present', function () {
  677. const props = {
  678. query: '',
  679. organization,
  680. location,
  681. supportedTags,
  682. };
  683. const smartSearchBar = mountWithTheme(<SmartSearchBar {...props} />, options);
  684. const searchBar = smartSearchBar.instance();
  685. const textarea = smartSearchBar.find('textarea');
  686. // start typing part of the tag prefixed by the negation operator!
  687. textarea.simulate('change', {target: {value: 'event.type:error !ti'}});
  688. mockCursorPosition(searchBar, 20);
  689. // use autocompletion to do the rest
  690. searchBar.onAutoComplete('title:', {});
  691. expect(searchBar.state.query).toEqual('event.type:error !title:');
  692. });
  693. it('handles special case for user tag', function () {
  694. const props = {
  695. query: '',
  696. organization,
  697. location,
  698. supportedTags,
  699. };
  700. const smartSearchBar = mountWithTheme(<SmartSearchBar {...props} />, options);
  701. const searchBar = smartSearchBar.instance();
  702. const textarea = smartSearchBar.find('textarea');
  703. textarea.simulate('change', {target: {value: 'user:'}});
  704. mockCursorPosition(searchBar, 5);
  705. searchBar.onAutoComplete('id:1', {});
  706. expect(searchBar.state.query).toEqual('user:"id:1" ');
  707. });
  708. });
  709. });