autoComplete.spec.jsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. import {useEffect} from 'react';
  2. import {fireEvent, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  3. import AutoComplete from 'sentry/components/autoComplete';
  4. const items = [
  5. {
  6. name: 'Apple',
  7. },
  8. {
  9. name: 'Pineapple',
  10. },
  11. {
  12. name: 'Orange',
  13. },
  14. ];
  15. /**
  16. * For every render, we push all injected params into `autoCompleteState`, we probably want to
  17. * assert against those instead of the wrapper's state since component state will be different if we have
  18. * "controlled" props where <AutoComplete> does not handle state
  19. */
  20. describe('AutoComplete', function () {
  21. let input;
  22. let autoCompleteState = [];
  23. const mocks = {
  24. onSelect: jest.fn(),
  25. onClose: jest.fn(),
  26. onOpen: jest.fn(),
  27. };
  28. afterEach(() => {
  29. jest.resetAllMocks();
  30. autoCompleteState = [];
  31. });
  32. const List = ({registerItemCount, itemCount, ...props}) => {
  33. useEffect(() => void registerItemCount(itemCount), [itemCount, registerItemCount]);
  34. return <ul {...props} />;
  35. };
  36. const Item = ({registerVisibleItem, item, index, ...props}) => {
  37. useEffect(() => registerVisibleItem(index, item), [registerVisibleItem, index, item]);
  38. return <li {...props} />;
  39. };
  40. const createComponent = props => (
  41. <AutoComplete {...mocks} itemToString={item => item.name} {...props}>
  42. {injectedProps => {
  43. const {
  44. getRootProps,
  45. getInputProps,
  46. getMenuProps,
  47. getItemProps,
  48. inputValue,
  49. isOpen,
  50. registerItemCount,
  51. registerVisibleItem,
  52. highlightedIndex,
  53. } = injectedProps;
  54. // This is purely for testing
  55. autoCompleteState.push(injectedProps);
  56. const filteredItems = items.filter(item =>
  57. item.name.toLowerCase().includes(inputValue.toLowerCase())
  58. );
  59. return (
  60. <div {...getRootProps()}>
  61. <input placeholder="autocomplete" {...getInputProps()} />
  62. {isOpen && (
  63. <div {...getMenuProps()} data-test-id="test-autocomplete">
  64. <List
  65. registerItemCount={registerItemCount}
  66. itemCount={filteredItems.length}
  67. >
  68. {filteredItems.map((item, index) => (
  69. <Item
  70. key={item.name}
  71. registerVisibleItem={registerVisibleItem}
  72. index={index}
  73. item={item}
  74. aria-selected={highlightedIndex === index}
  75. {...getItemProps({item, index})}
  76. >
  77. {item.name}
  78. </Item>
  79. ))}
  80. </List>
  81. </div>
  82. )}
  83. </div>
  84. );
  85. }}
  86. </AutoComplete>
  87. );
  88. const createWrapper = props => {
  89. const wrapper = render(createComponent(props));
  90. input = screen.getByPlaceholderText('autocomplete');
  91. return wrapper;
  92. };
  93. describe('Uncontrolled', function () {
  94. it('shows dropdown menu when input has focus', function () {
  95. createWrapper();
  96. fireEvent.focus(input);
  97. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  98. for (const item of items) {
  99. expect(screen.getByText(item.name)).toBeInTheDocument();
  100. }
  101. });
  102. it('only tries to close once if input is blurred and click outside occurs', async function () {
  103. createWrapper();
  104. jest.useFakeTimers();
  105. fireEvent.focus(input);
  106. fireEvent.blur(input);
  107. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  108. // Click outside dropdown
  109. fireEvent.click(document.body);
  110. jest.runAllTimers();
  111. await waitFor(() => expect(mocks.onClose).toHaveBeenCalledTimes(1));
  112. });
  113. it('only calls onClose dropdown menu when input is blurred', function () {
  114. createWrapper();
  115. jest.useFakeTimers();
  116. fireEvent.focus(input);
  117. fireEvent.blur(input);
  118. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  119. jest.runAllTimers();
  120. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  121. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  122. });
  123. it('can close dropdown menu when Escape is pressed', function () {
  124. createWrapper();
  125. fireEvent.focus(input);
  126. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  127. fireEvent.keyDown(input, {key: 'Escape', charCode: 27});
  128. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  129. });
  130. it('can open and close dropdown menu using injected actions', function () {
  131. createWrapper();
  132. const [injectedProps] = autoCompleteState;
  133. injectedProps.actions.open();
  134. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  135. expect(mocks.onOpen).toHaveBeenCalledTimes(1);
  136. injectedProps.actions.close();
  137. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  138. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  139. });
  140. it('reopens dropdown menu after Escape is pressed and input is changed', function () {
  141. createWrapper();
  142. fireEvent.focus(input);
  143. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  144. fireEvent.keyDown(input, {key: 'Escape', charCode: 27});
  145. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  146. fireEvent.change(input, {target: {value: 'a', charCode: 65}});
  147. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  148. expect(screen.getAllByRole('option')).toHaveLength(3);
  149. });
  150. it('reopens dropdown menu after item is selected and then input is changed', function () {
  151. createWrapper();
  152. fireEvent.focus(input);
  153. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  154. fireEvent.change(input, {target: {value: 'eapp'}});
  155. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  156. expect(screen.getByRole('option')).toBeInTheDocument();
  157. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  158. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  159. fireEvent.change(input, {target: {value: 'app'}});
  160. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  161. expect(screen.getAllByRole('option')).toHaveLength(2);
  162. });
  163. it('selects dropdown item by clicking and sets input to selected value', function () {
  164. createWrapper();
  165. fireEvent.focus(input);
  166. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  167. expect(screen.getAllByRole('option')).toHaveLength(3);
  168. fireEvent.click(screen.getByText(items[1].name));
  169. expect(mocks.onSelect).toHaveBeenCalledWith(
  170. items[1],
  171. expect.objectContaining({inputValue: '', highlightedIndex: 0}),
  172. expect.anything()
  173. );
  174. expect(input).toHaveValue('Pineapple');
  175. expect(screen.queryByRole('option')).not.toBeInTheDocument();
  176. });
  177. it('can navigate dropdown items with keyboard and select with "Enter" keypress', function () {
  178. createWrapper();
  179. fireEvent.focus(input);
  180. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  181. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  182. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  183. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  184. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  185. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  186. expect(screen.getAllByRole('option')).toHaveLength(3);
  187. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  188. expect(mocks.onSelect).toHaveBeenCalledWith(
  189. items[2],
  190. expect.objectContaining({inputValue: '', highlightedIndex: 2}),
  191. expect.anything()
  192. );
  193. expect(screen.queryByRole('option')).not.toBeInTheDocument();
  194. expect(input).toHaveValue('Orange');
  195. });
  196. it('respects list bounds when navigating filtered items with arrow keys', function () {
  197. createWrapper();
  198. fireEvent.focus(input);
  199. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  200. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  201. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  202. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  203. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  204. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  205. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  206. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  207. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  208. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  209. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  210. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  211. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  212. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  213. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  214. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  215. expect(screen.getAllByRole('option')).toHaveLength(3);
  216. });
  217. it('can filter items and then navigate with keyboard', function () {
  218. createWrapper();
  219. fireEvent.focus(input);
  220. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  221. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  222. expect(screen.getAllByRole('option')).toHaveLength(3);
  223. fireEvent.keyDown(input, {target: {value: 'a', charCode: 65}});
  224. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  225. expect(input).toHaveValue('a');
  226. // Apple, pineapple, orange
  227. expect(screen.getAllByRole('option')).toHaveLength(3);
  228. fireEvent.change(input, {target: {value: 'ap'}});
  229. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  230. expect(input).toHaveValue('ap');
  231. expect(autoCompleteState[autoCompleteState.length - 1].inputValue).toBe('ap');
  232. // Apple, pineapple
  233. expect(screen.getAllByRole('option')).toHaveLength(2);
  234. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  235. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  236. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  237. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  238. expect(screen.getAllByRole('option')).toHaveLength(2);
  239. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  240. expect(mocks.onSelect).toHaveBeenCalledWith(
  241. items[1],
  242. expect.objectContaining({inputValue: 'ap', highlightedIndex: 1}),
  243. expect.anything()
  244. );
  245. expect(screen.queryByRole('option')).not.toBeInTheDocument();
  246. expect(input).toHaveValue('Pineapple');
  247. });
  248. it('can reset input when menu closes', function () {
  249. const wrapper = createWrapper();
  250. jest.useFakeTimers();
  251. wrapper.rerender(createComponent({resetInputOnClose: true}));
  252. fireEvent.focus(input);
  253. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  254. fireEvent.keyDown(input, {target: {value: 'a', charCode: 65}});
  255. expect(input).toHaveValue('a');
  256. fireEvent.blur(input);
  257. jest.runAllTimers();
  258. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  259. expect(input).toHaveValue('');
  260. });
  261. });
  262. describe('isOpen controlled', function () {
  263. it('has dropdown menu initially open', function () {
  264. createWrapper({isOpen: true});
  265. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  266. expect(screen.getAllByRole('option')).toHaveLength(3);
  267. });
  268. it('closes when props change', function () {
  269. const wrapper = createWrapper({isOpen: true});
  270. wrapper.rerender(createComponent({isOpen: false}));
  271. // Menu should be closed
  272. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  273. expect(screen.queryByRole('option')).not.toBeInTheDocument();
  274. });
  275. it('remains closed when input is focused, but calls `onOpen`', function () {
  276. createWrapper({isOpen: false});
  277. jest.useFakeTimers();
  278. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  279. fireEvent.focus(input);
  280. jest.runAllTimers();
  281. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  282. expect(screen.queryByRole('option')).not.toBeInTheDocument();
  283. expect(mocks.onOpen).toHaveBeenCalledTimes(1);
  284. });
  285. it('remains open when input focus/blur events occur, but calls `onClose`', function () {
  286. createWrapper({isOpen: true});
  287. jest.useFakeTimers();
  288. fireEvent.focus(input);
  289. fireEvent.blur(input);
  290. jest.runAllTimers();
  291. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  292. // This still gets called even though menu is open
  293. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  294. });
  295. it('calls onClose when Escape is pressed', function () {
  296. createWrapper({isOpen: true});
  297. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  298. fireEvent.keyDown(input, {key: 'Escape', charCode: 27});
  299. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  300. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  301. });
  302. it('does not open and close dropdown menu using injected actions', function () {
  303. createWrapper({isOpen: true});
  304. const [injectedProps] = autoCompleteState;
  305. injectedProps.actions.open();
  306. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  307. expect(mocks.onOpen).toHaveBeenCalledTimes(1);
  308. injectedProps.actions.close();
  309. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  310. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  311. });
  312. it('onClose is called after item is selected', function () {
  313. createWrapper({isOpen: true});
  314. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  315. fireEvent.change(input, {target: {value: 'eapp'}});
  316. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  317. expect(screen.getByRole('option')).toBeInTheDocument();
  318. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  319. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  320. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  321. });
  322. it('selects dropdown item by clicking and sets input to selected value', function () {
  323. createWrapper({isOpen: true});
  324. expect(screen.getAllByRole('option')).toHaveLength(3);
  325. fireEvent.click(screen.getByText(items[1].name));
  326. expect(mocks.onSelect).toHaveBeenCalledWith(
  327. items[1],
  328. expect.objectContaining({inputValue: '', highlightedIndex: 0}),
  329. expect.anything()
  330. );
  331. expect(input).toHaveValue('Pineapple');
  332. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  333. });
  334. it('can navigate dropdown items with keyboard and select with "Enter" keypress', function () {
  335. createWrapper({isOpen: true});
  336. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  337. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  338. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  339. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  340. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  341. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  342. expect(screen.getAllByRole('option')).toHaveLength(3);
  343. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  344. expect(mocks.onSelect).toHaveBeenCalledWith(
  345. items[2],
  346. expect.objectContaining({inputValue: '', highlightedIndex: 2}),
  347. expect.anything()
  348. );
  349. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  350. expect(input).toHaveValue('Orange');
  351. });
  352. it('respects list bounds when navigating filtered items with arrow keys', function () {
  353. createWrapper({isOpen: true});
  354. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  355. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  356. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  357. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  358. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  359. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  360. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  361. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  362. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  363. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  364. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  365. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  366. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  367. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  368. fireEvent.keyDown(input, {key: 'ArrowUp', charCode: 38});
  369. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  370. expect(screen.getAllByRole('option')).toHaveLength(3);
  371. });
  372. it('can filter items and then navigate with keyboard', function () {
  373. createWrapper({isOpen: true});
  374. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  375. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  376. expect(screen.getAllByRole('option')).toHaveLength(3);
  377. fireEvent.keyDown(input, {target: {value: 'a', charCode: 65}});
  378. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  379. expect(input).toHaveValue('a');
  380. // Apple, pineapple, orange
  381. expect(screen.getAllByRole('option')).toHaveLength(3);
  382. fireEvent.change(input, {target: {value: 'ap'}});
  383. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  384. expect(input).toHaveValue('ap');
  385. expect(autoCompleteState[autoCompleteState.length - 1].inputValue).toBe('ap');
  386. // Apple, pineapple
  387. expect(screen.getAllByRole('option')).toHaveLength(2);
  388. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  389. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  390. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  391. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  392. expect(screen.getAllByRole('option')).toHaveLength(2);
  393. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  394. expect(mocks.onSelect).toHaveBeenCalledWith(
  395. items[1],
  396. expect.objectContaining({inputValue: 'ap', highlightedIndex: 1}),
  397. expect.anything()
  398. );
  399. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  400. expect(input).toHaveValue('Pineapple');
  401. });
  402. it('only scrolls highlighted item into view on keyboard events', function () {
  403. const scrollIntoViewMock = jest.fn();
  404. Element.prototype.scrollIntoView = scrollIntoViewMock;
  405. createWrapper({isOpen: true});
  406. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  407. fireEvent.mouseEnter(screen.getByText('Pineapple'));
  408. expect(scrollIntoViewMock).not.toHaveBeenCalled();
  409. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  410. expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
  411. });
  412. it('can reset input value when menu closes', function () {
  413. const wrapper = createWrapper({isOpen: true});
  414. jest.useFakeTimers();
  415. wrapper.rerender(createComponent({resetInputOnClose: true}));
  416. fireEvent.focus(input);
  417. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  418. fireEvent.keyDown(input, {target: {value: 'a', charCode: 65}});
  419. expect(input).toHaveValue('a');
  420. fireEvent.blur(input);
  421. jest.runAllTimers();
  422. expect(input).toHaveValue('');
  423. });
  424. });
  425. describe('inputValue controlled', () => {
  426. it('follows the inputValue prop', () => {
  427. const wrapper = createWrapper({inputValue: 'initial value'});
  428. expect(input).toHaveValue('initial value');
  429. wrapper.rerender(createComponent({inputValue: 'new value'}));
  430. expect(input).toHaveValue('new value');
  431. });
  432. it('calls onInputValueChange on input', () => {
  433. const wrapper = createWrapper({inputValue: 'initial value'});
  434. const onInputValueChange = jest.fn();
  435. wrapper.rerender(createComponent({onInputValueChange}));
  436. fireEvent.focus(input);
  437. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  438. fireEvent.change(input, {target: {value: 'a'}});
  439. expect(onInputValueChange).toHaveBeenCalledWith('a');
  440. });
  441. it('input value does not change when typed into', () => {
  442. createWrapper({inputValue: 'initial value'});
  443. fireEvent.focus(input);
  444. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  445. fireEvent.change(input, {target: {value: 'a'}});
  446. expect(input).toHaveValue('initial value');
  447. });
  448. it('input value does not change when blurred', () => {
  449. createWrapper({inputValue: 'initial value'});
  450. fireEvent.focus(input);
  451. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  452. fireEvent.blur(input);
  453. expect(input).toHaveValue('initial value');
  454. });
  455. it('can search for and select an item without changing the input value', () => {
  456. const wrapper = createWrapper({inputValue: 'initial value'});
  457. fireEvent.focus(input);
  458. wrapper.rerender(createComponent({inputValue: 'apple'}));
  459. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  460. expect(screen.getAllByRole('option')).toHaveLength(2);
  461. fireEvent.click(screen.getByText('Apple'));
  462. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  463. expect(input).toHaveValue('apple');
  464. expect(mocks.onSelect).toHaveBeenCalledWith(
  465. {name: 'Apple'},
  466. expect.anything(),
  467. expect.anything()
  468. );
  469. });
  470. it('can filter and navigate dropdown items with keyboard and select with "Enter" keypress without changing input value', function () {
  471. const wrapper = createWrapper({inputValue: 'initial value'});
  472. wrapper.rerender(createComponent({inputValue: 'apple'}));
  473. fireEvent.focus(input);
  474. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  475. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  476. expect(screen.getAllByRole('option')).toHaveLength(2);
  477. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  478. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  479. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  480. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  481. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  482. expect(mocks.onSelect).toHaveBeenCalledWith(
  483. items[1],
  484. expect.objectContaining({highlightedIndex: 1}),
  485. expect.anything()
  486. );
  487. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  488. expect(input).toHaveValue('apple');
  489. });
  490. });
  491. it('selects using enter key', function () {
  492. const wrapper = createWrapper({isOpen: true, shouldSelectWithEnter: false});
  493. fireEvent.change(input, {target: {value: 'pine'}});
  494. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  495. expect(mocks.onSelect).not.toHaveBeenCalled();
  496. wrapper.unmount();
  497. createWrapper({isOpen: true, shouldSelectWithEnter: true});
  498. fireEvent.change(input, {target: {value: 'pine'}});
  499. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  500. expect(mocks.onSelect).toHaveBeenCalledWith(
  501. items[1],
  502. expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
  503. expect.anything()
  504. );
  505. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  506. expect(input).toHaveValue('Pineapple');
  507. });
  508. it('selects using tab key', function () {
  509. let wrapper = createWrapper({isOpen: true, shouldSelectWithTab: false});
  510. fireEvent.change(input, {target: {value: 'pine'}});
  511. fireEvent.keyDown(input, {key: 'Tab', charCode: 9});
  512. expect(mocks.onSelect).not.toHaveBeenCalled();
  513. wrapper.unmount();
  514. wrapper = createWrapper({isOpen: true, shouldSelectWithTab: true});
  515. fireEvent.change(input, {target: {value: 'pine'}});
  516. fireEvent.keyDown(input, {key: 'Tab'});
  517. expect(mocks.onSelect).toHaveBeenCalledWith(
  518. items[1],
  519. expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
  520. expect.anything()
  521. );
  522. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  523. expect(input).toHaveValue('Pineapple');
  524. });
  525. it('does not reset highlight state if `closeOnSelect` is false and we select a new item', function () {
  526. createWrapper({closeOnSelect: false});
  527. jest.useFakeTimers();
  528. fireEvent.focus(input);
  529. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  530. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  531. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  532. // Select item
  533. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  534. // Should still remain open with same highlightedIndex
  535. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  536. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  537. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  538. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  539. // Select item
  540. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  541. // Should still remain open with same highlightedIndex
  542. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  543. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  544. });
  545. });