autoComplete.test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. import { Icon, AutoComplete } from '../../index';
  2. import { noop } from 'lodash';
  3. import sinon from 'sinon';
  4. import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
  5. import keyCode from '@douyinfe/semi-foundation/utils/keyCode';
  6. function getAc(props, needAttachTo) {
  7. return mount(<AutoComplete {...props} />, { attachTo: document.getElementById('container') });
  8. }
  9. let stringData = ['semi', 'ies', 'design', 'platform'];
  10. let objectData = [
  11. { email: '[email protected]', value: 'abc' },
  12. { email: '[email protected]', value: 'bytedance' },
  13. { email: '[email protected]', value: 'vigo' },
  14. ];
  15. let commonProps = {
  16. // AutoComplete use Popup Layer to show candidate option,
  17. // but all Popup Layer which extends from Tooltip (eg Popover, Dropdown) have animation and delay.
  18. // Turn off animation and delay during testing, to avoid wating (something like setTimeOut/balabala...) in the test code
  19. motion: false,
  20. mouseEnterDelay: 0,
  21. mouseLeaveDelay: 0,
  22. };
  23. describe('AutoComplete', () => {
  24. beforeEach(() => {
  25. // Avoid `attachTo: document.body` Warning
  26. // document.body.innerHTML = '';
  27. const div = document.createElement('div');
  28. div.setAttribute('id', 'container');
  29. document.body.appendChild(div);
  30. });
  31. afterEach(() => {
  32. const div = document.getElementById('container');
  33. if (div) {
  34. document.body.removeChild(div);
  35. }
  36. document.body.innerHTML = '';
  37. });
  38. it('【style & className】custom className & style', () => {
  39. let props = {
  40. className: 'test',
  41. style: {
  42. color: 'red',
  43. },
  44. };
  45. const wrapper = getAc(props);
  46. expect(wrapper.hasClass('test')).toEqual(true);
  47. expect(wrapper.find('div.test')).toHaveStyle('color', 'red');
  48. wrapper.unmount();
  49. });
  50. it('【placeholder】with placeholder', () => {
  51. const props = { placeholder: 'semi' };
  52. const ac = getAc(props);
  53. expect(ac.find('input').instance().placeholder).toEqual('semi');
  54. });
  55. it('【size】different size', () => {
  56. const props = { size: 'small' };
  57. const ac = getAc(props);
  58. expect(ac.exists(`.${BASE_CLASS_PREFIX}-input-small`)).toEqual(true);
  59. ac.setProps({ size: 'large' });
  60. expect(ac.exists(`.${BASE_CLASS_PREFIX}-input-large`)).toEqual(true);
  61. });
  62. it('【disabled】disabled component when disabled is true', () => {
  63. const props = { disabled: true };
  64. const ac = getAc(props);
  65. expect(ac.exists(`.${BASE_CLASS_PREFIX}-autocomplete-disabled`)).toEqual(true);
  66. });
  67. it('【prefix & suffix】custom prefix & suffix', () => {
  68. let prefix = <div className="prefix">prefix content</div>;
  69. let suffix = <div className="suffix">suffix content</div>;
  70. const props = {
  71. prefix,
  72. suffix,
  73. };
  74. let ac = getAc(props);
  75. expect(ac.contains(prefix)).toEqual(true);
  76. expect(ac.contains(suffix)).toEqual(true);
  77. });
  78. it('【dropdownClassName & dropdownStyle】custom dropdownClassName & dropdownStyle', () => {
  79. let props = {
  80. dropdownClassName: 'ddc',
  81. dropdownStyle: {
  82. color: 'red',
  83. },
  84. defaultOpen: true,
  85. ...commonProps,
  86. };
  87. let ac = getAc(props);
  88. expect(ac.exists(`.${BASE_CLASS_PREFIX}-autocomplete-option-list.ddc`)).toEqual(true);
  89. expect(ac.find(`.${BASE_CLASS_PREFIX}-autocomplete-option-list.ddc`)).toHaveStyle('color', 'red');
  90. });
  91. it('【position】different position', () => {
  92. let props = {
  93. position: 'top',
  94. data: stringData,
  95. defaultOpen: true,
  96. ...commonProps,
  97. };
  98. let ac = getAc(props);
  99. expect(
  100. ac
  101. .find(`.${BASE_CLASS_PREFIX}-popover-wrapper`)
  102. .instance()
  103. .getAttribute('x-placement')
  104. ).toEqual('top');
  105. });
  106. it('【defaultValue】with defaultValue(not candidate in data)', () => {
  107. let props = {
  108. defaultValue: 'semi',
  109. data: [],
  110. ...commonProps,
  111. };
  112. let ac = getAc(props);
  113. expect(ac.find('input').instance().value).toEqual('semi');
  114. });
  115. it('【defaultValue】with defaultValue(can match in data)', () => {
  116. let props = {
  117. defaultValue: 'semi',
  118. data: stringData,
  119. ...commonProps,
  120. };
  121. let ac = getAc(props);
  122. expect(ac.find('input').instance().value).toEqual('semi');
  123. });
  124. it('【onSearch】trigger onSearch when input change', () => {
  125. let onSearch = value => {};
  126. let spyOnSearch = sinon.spy(onSearch);
  127. let props = {
  128. onSearch: spyOnSearch,
  129. };
  130. let ac = getAc(props);
  131. let inputValue = 'semi';
  132. let event = { target: { value: inputValue } };
  133. ac.find('input').simulate('change', event);
  134. expect(spyOnSearch.calledOnce).toBe(true);
  135. expect(spyOnSearch.calledWithMatch(inputValue)).toBe(true);
  136. });
  137. it('optionList should show when defaultOpen is true & data not empty', () => {
  138. let props = {
  139. defaultOpen: true,
  140. data: stringData,
  141. ...commonProps,
  142. };
  143. let ac = getAc(props);
  144. expect(ac.state().visible).toEqual(true);
  145. let candidate = ac.find(`.${BASE_CLASS_PREFIX}-autocomplete-option-list`).children();
  146. expect(candidate.length).toEqual(4);
  147. expect(candidate.at(0).getDOMNode().textContent).toEqual('semi');
  148. expect(candidate.at(1).getDOMNode().textContent).toEqual('ies');
  149. });
  150. it('【data】updateOptionList when data change', () => {
  151. let props = {
  152. defaultOpen: true,
  153. data: ['semi'],
  154. ...commonProps,
  155. };
  156. let ac = getAc(props);
  157. let candidate = ac.find(`.${BASE_CLASS_PREFIX}-autocomplete-option-list`).children();
  158. expect(candidate.length).toEqual(1);
  159. expect(candidate.at(0).getDOMNode().textContent).toEqual('semi');
  160. ac.setProps({ data: ['ies', 'design'] });
  161. ac.update();
  162. candidate = ac.find(`.${BASE_CLASS_PREFIX}-autocomplete-option-list`).children();
  163. expect(candidate.length).toEqual(2);
  164. expect(candidate.at(0).getDOMNode().textContent).toEqual('ies');
  165. expect(candidate.at(1).getDOMNode().textContent).toEqual('design');
  166. });
  167. it('【loading】hide optionList & show loading when loading is true', () => {
  168. let props = {
  169. loading: true,
  170. defaultOpen: true,
  171. data: stringData,
  172. ...commonProps,
  173. };
  174. let ac = getAc(props);
  175. expect(ac.exists(`.${BASE_CLASS_PREFIX}-autocomplete-loading-wrapper`)).toEqual(true);
  176. expect(ac.exists(`.${BASE_CLASS_PREFIX}-autoComplete-option`)).toEqual(false);
  177. });
  178. it('【onSelect】trigger onSelect when click candidate option', () => {
  179. let onSelect = () => {};
  180. let spyOnSelect = sinon.spy(onSelect);
  181. let props = {
  182. defaultOpen: true,
  183. data: stringData,
  184. onSelect: spyOnSelect,
  185. ...commonProps,
  186. };
  187. let ac = getAc(props);
  188. let options = ac.find(`.${BASE_CLASS_PREFIX}-autoComplete-option`);
  189. let firstOption = options.at(0);
  190. const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
  191. firstOption.simulate('click', nativeEvent);
  192. expect(spyOnSelect.calledOnce).toBe(true);
  193. expect(spyOnSelect.calledWithMatch(`${BASE_CLASS_PREFIX}`)).toBe(true);
  194. });
  195. it('【onSelect】callback with object when onSelectWithObject is true', () => {
  196. let onSelect = v => {};
  197. let spyOnSelect = sinon.spy(onSelect);
  198. let props = {
  199. defaultOpen: true,
  200. data: objectData,
  201. onSelect: spyOnSelect,
  202. onSelectWithObject: true,
  203. ...commonProps,
  204. };
  205. let ac = getAc(props);
  206. let options = ac.find(`.${BASE_CLASS_PREFIX}-autoComplete-option`);
  207. let firstOption = options.at(0);
  208. const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
  209. firstOption.simulate('click', nativeEvent);
  210. expect(spyOnSelect.calledOnce).toBe(true);
  211. expect(spyOnSelect.calledWithMatch(objectData[0])).toBe(true);
  212. });
  213. it('show candidate option list after click AutoComplete(when data not empty)', () => {
  214. let props = {
  215. data: stringData,
  216. ...commonProps,
  217. };
  218. let ac = getAc(props);
  219. let acCls = `.${BASE_CLASS_PREFIX}-autocomplete`;
  220. ac.find(acCls).simulate('click', {});
  221. expect(ac.state().visible).toEqual(true);
  222. expect(ac.exists(`.${BASE_CLASS_PREFIX}-autocomplete-option-list`)).toEqual(true);
  223. });
  224. // TODO
  225. // it('show clear button when input has value and showClear is true', () => {
  226. // let props = {
  227. // data: stringData,
  228. // showClear: true,
  229. // ...commonProps
  230. // };
  231. // let ac = getAc(props);
  232. // // input Clear button only show when focus/hover
  233. // ac.find(`.${BASE_CLASS_PREFIX}-autocomplete`).simulate('click', {});
  234. // ac.find('input').simulate('change', { target: { value: '${BASE_CLASS_PREFIX}' }});
  235. // expect(ac.exists(`.${BASE_CLASS_PREFIX}-input-clearbtn`)).toEqual(true);
  236. // });
  237. // it('trigger onSearch when click clear button', () => {
  238. // });
  239. it('renderSelectedItem', () => {
  240. let props = {
  241. data: objectData,
  242. defaultOpen: true,
  243. ...commonProps,
  244. renderSelectedItem: option => option.email,
  245. };
  246. let ac = getAc(props);
  247. let options = ac.find(`.${BASE_CLASS_PREFIX}-autoComplete-option`);
  248. let firstOption = options.at(0);
  249. const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
  250. firstOption.simulate('click', nativeEvent);
  251. expect(ac.find('input').instance().value).toEqual(objectData[0].email);
  252. });
  253. it('renderItem in controlled mode', () => {
  254. const fakeOnChange = sinon.spy();
  255. let props = {
  256. data: [
  257. {
  258. name: '夏可漫',
  259. label: '夏可漫',
  260. value: '[email protected]',
  261. email: '[email protected]',
  262. abbr: 'XK',
  263. color: 'amber',
  264. },
  265. {
  266. name: '申悦',
  267. label: '申悦',
  268. value: '[email protected]',
  269. email: '[email protected]',
  270. abbr: 'SY',
  271. color: 'indigo',
  272. },
  273. ],
  274. motion: false,
  275. defaultOpen: true,
  276. mouseEnterDelay: 0,
  277. mouseLeaveDelay: 0,
  278. renderSelectedItem: option => option.abbr,
  279. onChange: fakeOnChange,
  280. value: '',
  281. };
  282. const ac = getAc(props);
  283. ac.find(`.${BASE_CLASS_PREFIX}-autoComplete-option`)
  284. .at(0)
  285. .simulate('click');
  286. expect(fakeOnChange.calledWith('XK')).toEqual(true);
  287. });
  288. it('【value】controlled mode', () => {
  289. let props = {
  290. data: [],
  291. ...commonProps,
  292. value: 'semi',
  293. };
  294. let ac = getAc(props);
  295. expect(ac.find('input').instance().value).toEqual('semi');
  296. ac.setProps({ value: 'ies' });
  297. ac.update();
  298. expect(ac.find('input').instance().value).toEqual('ies');
  299. });
  300. it('【autoFocus】works', () => {
  301. let onFocus = () => {};
  302. let spyOnFocus = sinon.spy(onFocus);
  303. const props = {
  304. autoFocus: true,
  305. onFocus: spyOnFocus,
  306. };
  307. const ac = getAc(props);
  308. expect(spyOnFocus.calledOnce).toBe(true);
  309. expect(ac.exists(`.${BASE_CLASS_PREFIX}-input-wrapper-focus`)).toBe(true);
  310. });
  311. it('【emptyContent】shows when data is empty', () => {
  312. const props = {
  313. defaultOpen: true,
  314. data: [],
  315. emptyContent: <div className="custom-empty-content">emptycontent</div>,
  316. ...commonProps,
  317. };
  318. const ac = getAc(props);
  319. expect(ac.exists('.custom-empty-content')).toBe(true);
  320. });
  321. it('【emptyContent】not show when data is not empty', () => {
  322. const props = {
  323. defaultOpen: true,
  324. data: [1, 2, 3],
  325. emptyContent: <div className="custom-empty-content">emptycontent</div>,
  326. ...commonProps,
  327. };
  328. const ac = getAc(props);
  329. expect(ac.exists('.custom-empty-content')).toBe(false);
  330. });
  331. it('【onChange should be triggered in right occasion】', () => {
  332. const spyOnChange = sinon.spy();
  333. const props = {
  334. defaultOpen: true,
  335. onChange: spyOnChange,
  336. data: ['hello', 'bytedance', 'semi'],
  337. showClear: true,
  338. motion: false,
  339. mouseEnterDelay: 0,
  340. mouseLeaveDelay: 0,
  341. };
  342. const component = getAc(props);
  343. let event = { target: { value: 'abc' } };
  344. component.find('input').simulate('change', event);
  345. expect(spyOnChange.calledWith('abc')).toEqual(true);
  346. let options = component.find(`.${BASE_CLASS_PREFIX}-autoComplete-option`);
  347. let firstOption = options.at(0);
  348. const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
  349. firstOption.simulate('click', nativeEvent);
  350. expect(spyOnChange.calledWith('hello')).toEqual(true);
  351. });
  352. it('maxHeight', () => {
  353. let props = {
  354. maxHeight: 400,
  355. data: ['a', 'b', 'c'],
  356. defaultOpen: true,
  357. ...commonProps
  358. };
  359. let ac = getAc(props);
  360. let dom = document.querySelector('.semi-autocomplete-option-list');
  361. expect(dom.style.maxHeight).toEqual('400px');
  362. });
  363. it('zIndex', () => {
  364. const props = {
  365. defaultOpen: true,
  366. data: ['a', 'b', 'c'],
  367. zIndex: 998,
  368. ...commonProps
  369. };
  370. const ac = getAc(props);
  371. let popupDom = document.querySelector('.semi-portal');
  372. expect(popupDom.style.zIndex).toEqual('998');
  373. });
  374. it('set trigger width', () => {
  375. let numberWidth = {
  376. defaultOpen: true,
  377. style: {
  378. width: 200
  379. },
  380. ...commonProps
  381. };
  382. let numberAc = getAc(numberWidth);
  383. let dom = document.querySelector('.semi-autocomplete-option-list');
  384. expect(dom.style.minWidth).toEqual('200px');
  385. let stringWidth = {
  386. defaultOpen: true,
  387. style: {
  388. width: '80px'
  389. },
  390. ...commonProps
  391. };
  392. let stringAc = getAc(stringWidth);
  393. let stringDom = document.querySelector('.semi-autocomplete-option-list');
  394. expect(stringDom.style.minWidth).toEqual('80px');
  395. })
  396. // it('onBlur', () => {
  397. // });
  398. // it('keyboard event', () => {
  399. // let numberWidth = {
  400. // defaultOpen: true,
  401. // style: {
  402. // width: 200
  403. // },
  404. // defaultActiveFirstOption: true,
  405. // data: ['a', 'b', 'c', 'd'],
  406. // ...commonProps
  407. // };
  408. // let numberAc = getAc(numberWidth);
  409. // numberAc.simulate('keydown', { keyCode: keyCode['ENTER'] });
  410. // numberAc.simulate('keydown', { keyCode: keyCode['UP'] });
  411. // numberAc.simulate('keydown', { keyCode: keyCode['DOWN'] });
  412. // numberAc.simulate('keydown', { keyCode: keyCode['ESC'] });
  413. // });
  414. // it('triggerRender', () => {
  415. // });
  416. });