autoComplete.spec.jsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  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('can reset input value when menu closes', function () {
  403. const wrapper = createWrapper({isOpen: true});
  404. jest.useFakeTimers();
  405. wrapper.rerender(createComponent({resetInputOnClose: true}));
  406. fireEvent.focus(input);
  407. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  408. fireEvent.keyDown(input, {target: {value: 'a', charCode: 65}});
  409. expect(input).toHaveValue('a');
  410. fireEvent.blur(input);
  411. jest.runAllTimers();
  412. expect(input).toHaveValue('');
  413. });
  414. });
  415. describe('inputValue controlled', () => {
  416. it('follows the inputValue prop', () => {
  417. const wrapper = createWrapper({inputValue: 'initial value'});
  418. expect(input).toHaveValue('initial value');
  419. wrapper.rerender(createComponent({inputValue: 'new value'}));
  420. expect(input).toHaveValue('new value');
  421. });
  422. it('calls onInputValueChange on input', () => {
  423. const wrapper = createWrapper({inputValue: 'initial value'});
  424. const onInputValueChange = jest.fn();
  425. wrapper.rerender(createComponent({onInputValueChange}));
  426. fireEvent.focus(input);
  427. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  428. fireEvent.change(input, {target: {value: 'a'}});
  429. expect(onInputValueChange).toHaveBeenCalledWith('a');
  430. });
  431. it('input value does not change when typed into', () => {
  432. createWrapper({inputValue: 'initial value'});
  433. fireEvent.focus(input);
  434. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  435. fireEvent.change(input, {target: {value: 'a'}});
  436. expect(input).toHaveValue('initial value');
  437. });
  438. it('input value does not change when blurred', () => {
  439. createWrapper({inputValue: 'initial value'});
  440. fireEvent.focus(input);
  441. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  442. fireEvent.blur(input);
  443. expect(input).toHaveValue('initial value');
  444. });
  445. it('can search for and select an item without changing the input value', () => {
  446. const wrapper = createWrapper({inputValue: 'initial value'});
  447. fireEvent.focus(input);
  448. wrapper.rerender(createComponent({inputValue: 'apple'}));
  449. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  450. expect(screen.getAllByRole('option')).toHaveLength(2);
  451. fireEvent.click(screen.getByText('Apple'));
  452. expect(screen.queryByTestId('test-autocomplete')).not.toBeInTheDocument();
  453. expect(input).toHaveValue('apple');
  454. expect(mocks.onSelect).toHaveBeenCalledWith(
  455. {name: 'Apple'},
  456. expect.anything(),
  457. expect.anything()
  458. );
  459. });
  460. it('can filter and navigate dropdown items with keyboard and select with "Enter" keypress without changing input value', function () {
  461. const wrapper = createWrapper({inputValue: 'initial value'});
  462. wrapper.rerender(createComponent({inputValue: 'apple'}));
  463. fireEvent.focus(input);
  464. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  465. expect(screen.getByText('Apple')).toHaveAttribute('aria-selected', 'true');
  466. expect(screen.getAllByRole('option')).toHaveLength(2);
  467. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  468. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  469. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  470. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  471. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  472. expect(mocks.onSelect).toHaveBeenCalledWith(
  473. items[1],
  474. expect.objectContaining({highlightedIndex: 1}),
  475. expect.anything()
  476. );
  477. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  478. expect(input).toHaveValue('apple');
  479. });
  480. });
  481. it('selects using enter key', function () {
  482. const wrapper = createWrapper({isOpen: true, shouldSelectWithEnter: false});
  483. fireEvent.change(input, {target: {value: 'pine'}});
  484. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  485. expect(mocks.onSelect).not.toHaveBeenCalled();
  486. wrapper.unmount();
  487. createWrapper({isOpen: true, shouldSelectWithEnter: true});
  488. fireEvent.change(input, {target: {value: 'pine'}});
  489. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  490. expect(mocks.onSelect).toHaveBeenCalledWith(
  491. items[1],
  492. expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
  493. expect.anything()
  494. );
  495. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  496. expect(input).toHaveValue('Pineapple');
  497. });
  498. it('selects using tab key', function () {
  499. let wrapper = createWrapper({isOpen: true, shouldSelectWithTab: false});
  500. fireEvent.change(input, {target: {value: 'pine'}});
  501. fireEvent.keyDown(input, {key: 'Tab', charCode: 9});
  502. expect(mocks.onSelect).not.toHaveBeenCalled();
  503. wrapper.unmount();
  504. wrapper = createWrapper({isOpen: true, shouldSelectWithTab: true});
  505. fireEvent.change(input, {target: {value: 'pine'}});
  506. fireEvent.keyDown(input, {key: 'Tab'});
  507. expect(mocks.onSelect).toHaveBeenCalledWith(
  508. items[1],
  509. expect.objectContaining({inputValue: 'pine', highlightedIndex: 0}),
  510. expect.anything()
  511. );
  512. expect(mocks.onClose).toHaveBeenCalledTimes(1);
  513. expect(input).toHaveValue('Pineapple');
  514. });
  515. it('does not reset highlight state if `closeOnSelect` is false and we select a new item', function () {
  516. createWrapper({closeOnSelect: false});
  517. jest.useFakeTimers();
  518. fireEvent.focus(input);
  519. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  520. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  521. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  522. // Select item
  523. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  524. // Should still remain open with same highlightedIndex
  525. expect(screen.getByText('Pineapple')).toHaveAttribute('aria-selected', 'true');
  526. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  527. fireEvent.keyDown(input, {key: 'ArrowDown', charCode: 40});
  528. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  529. // Select item
  530. fireEvent.keyDown(input, {key: 'Enter', charCode: 13});
  531. // Should still remain open with same highlightedIndex
  532. expect(screen.getByText('Orange')).toHaveAttribute('aria-selected', 'true');
  533. expect(screen.getByTestId('test-autocomplete')).toBeInTheDocument();
  534. });
  535. });