dropdown.test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import { Icon, Dropdown, Tag } from '../../index';
  2. import { string } from 'prop-types';
  3. import { noop, drop } from 'lodash';
  4. import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
  5. import {sleep} from "../../_test_/utils";
  6. const defaultItems = [{ children: 'Menu Item 1' }, { children: 'Menu Item 2' }, { children: 'Menu Item 3' }];
  7. function getSubMenu(items = defaultItems) {
  8. let dropdownItems = items.map(item => <Dropdown.Item {...item} />);
  9. return <Dropdown.Menu>{dropdownItems}</Dropdown.Menu>;
  10. }
  11. function getDD(props) {
  12. let bcProps = {
  13. // Dropdown use Popup Layer to show candidate option,
  14. // but all Popup Layer which extends from Tooltip (eg Popover, Dropdown) have animation and delay.
  15. // Turn off animation and delay during testing, to avoid wating (something like setTimeOut/balabala...) in the test code
  16. motion: false,
  17. mouseEnterDelay: 0,
  18. mouseLeaveDelay: 0,
  19. render: getSubMenu(),
  20. children: <Tag>semi dropdown</Tag>,
  21. ...props,
  22. };
  23. return mount(<Dropdown {...bcProps}></Dropdown>, {
  24. attachTo: document.getElementById('container'),
  25. });
  26. }
  27. let el_portal_inner = `.${BASE_CLASS_PREFIX}-portal-inner`;
  28. let el_item = `.${BASE_CLASS_PREFIX}-dropdown-item`;
  29. describe('Dropdown', () => {
  30. beforeEach(() => {
  31. document.body.innerHTML = '';
  32. // Avoid `attachTo: document.body` Warning
  33. const div = document.createElement('div');
  34. div.setAttribute('id', 'container');
  35. document.body.appendChild(div);
  36. });
  37. afterEach(() => {
  38. const div = document.getElementById('container');
  39. if (div) {
  40. document.body.removeChild(div);
  41. }
  42. });
  43. it('Dropdown-custom className & style', () => {
  44. let props = {
  45. visible: true,
  46. trigger: 'custom',
  47. className: 'test',
  48. style: {
  49. color: 'red',
  50. },
  51. };
  52. const dropdown = getDD(props);
  53. expect(dropdown.exists(`.${BASE_CLASS_PREFIX}-dropdown-wrapper.test`)).toEqual(true);
  54. expect(dropdown.find(`.${BASE_CLASS_PREFIX}-dropdown`)).toHaveStyle('color', 'red');
  55. });
  56. // Dropdown can't find `.${BASE_CLASS_PREFIX}-portal` (can confirm it't existence through documenty.body.innerHTML)
  57. // but find `.${BASE_CLASS_PREFIX}-portal-inner`
  58. // 由于.${BASE_CLASS_PREFIX}-portal 不是有dropdown或其子元素的render函数渲染出来的dom,而是portal在constructore阶段通过createElement/appendChild 插入的,所以无法使用find来找
  59. // 只能通过document.querySelector来获取
  60. it('Dropdown-zIndex', () => {
  61. let zIndex = 2000;
  62. let props = {
  63. visible: true,
  64. trigger: 'custom',
  65. zIndex: zIndex,
  66. };
  67. const dropdown = getDD(props);
  68. expect(Number(document.querySelector(`.${BASE_CLASS_PREFIX}-portal`).style.zIndex)).toEqual(zIndex);
  69. });
  70. it('Dropdown-trigger-hover', async () => {
  71. let props = {
  72. trigger: 'hover',
  73. };
  74. const dropdown = getDD(props);
  75. // Before hover, dropdown is not displayed
  76. expect(dropdown.exists(el_portal_inner)).toEqual(false);
  77. // After trigger, dropdown content will show
  78. dropdown.find(`.${BASE_CLASS_PREFIX}-tag`).simulate('mouseEnter', {});
  79. expect(dropdown.exists(el_portal_inner)).toEqual(true);
  80. expect(dropdown.find(el_item)).toHaveLength(3);
  81. await sleep(1000);
  82. // auto hide
  83. dropdown.find(`.${BASE_CLASS_PREFIX}-tag`).simulate('mouseLeave', {});
  84. await sleep(1000);
  85. expect(dropdown.exists(el_portal_inner)).toEqual(false);
  86. expect(dropdown.find(el_item)).toHaveLength(0);
  87. });
  88. it('Dropdown-trigger-click', () => {
  89. let props = {
  90. trigger: 'click',
  91. };
  92. const dropdown = getDD(props);
  93. // Before click
  94. expect(dropdown.exists(el_portal_inner)).toEqual(false);
  95. expect(dropdown.exists(el_item)).toEqual(false);
  96. // After click
  97. dropdown.find(`.${BASE_CLASS_PREFIX}-tag`).simulate('click', {});
  98. expect(dropdown.exists(el_portal_inner)).toEqual(true);
  99. expect(dropdown.find(el_item)).toHaveLength(3);
  100. });
  101. it('Dropdown-contentClassName', () => {
  102. let props = {
  103. contentClassName: 'test',
  104. trigger: 'custom',
  105. visible: true,
  106. };
  107. const dd = getDD(props);
  108. expect(dd.exists(`.${BASE_CLASS_PREFIX}-dropdown.test`)).toEqual(true);
  109. });
  110. // TODO ??? visibleChange在Jest中没有被触发,实际上代码是work的
  111. // it('Dropdown-onVisibleChange', () => {
  112. // let onVisibleChange = visible => {
  113. // debugger;
  114. // };
  115. // // let spyVisibleChange = sinon.spy(onVisibleChange);
  116. // let props = {
  117. // trigger: 'hover',
  118. // onVisibleChange: onVisibleChange,
  119. // };
  120. // const dropdown = getDD(props);
  121. // dropdown.find(`.${BASE_CLASS_PREFIX}-tag`).simulate('mouseEnter', {});
  122. // expect(spyVisibleChange.calledOnce).toBe(true);
  123. // expect(spyVisibleChange.calledWithMatch(true)).toBe(true);
  124. // dropdown.find(`.${BASE_CLASS_PREFIX}-tag`).simulate('mouseLeave', {});
  125. // expect(spyVisibleChange.calledWithMatch(false)).toBe(true);
  126. // });
  127. // it('Dropdown-clickToHide', () => {
  128. // let props = {
  129. // clickToHide: true,
  130. // };
  131. // });
  132. it('Dropdown-showTick', () => {
  133. let items = [{ children: 'Item 1' }, { active: true, children: 'Item 2' }, { children: 'Item 3' }];
  134. let props = {
  135. showTick: true,
  136. render: getSubMenu(items),
  137. visible: true,
  138. trigger: 'custom',
  139. };
  140. let DD = getDD(props);
  141. expect(DD.find(`.${BASE_CLASS_PREFIX}-dropdown-item-withTick.${BASE_CLASS_PREFIX}-dropdown-item-active`).text()).toEqual('Item 2');
  142. });
  143. it('Dropdown.Item active', () => {
  144. let items = [{ children: 'Item 1' }, { active: true, children: 'Item 2' }, { children: 'Item 3' }];
  145. let props = {
  146. render: getSubMenu(items),
  147. visible: true,
  148. trigger: 'custom',
  149. };
  150. let DD = getDD(props);
  151. expect(DD.find(`.${BASE_CLASS_PREFIX}-dropdown-item-active`).text()).toEqual('Item 2');
  152. });
  153. it('Dropdown.Item type', () => {
  154. let types = ['primary', 'secondary', 'tertiary', 'warning', 'danger'];
  155. let items = types.map(type => {
  156. return { type, children: `${type}Item` };
  157. });
  158. let props = {
  159. render: getSubMenu(items),
  160. trigger: 'custom',
  161. visible: true,
  162. };
  163. let DD = getDD(props);
  164. items.forEach(item => {
  165. expect(DD.find(`.${BASE_CLASS_PREFIX}-dropdown-item-${item.type}`).text()).toEqual(`${item.children}`);
  166. });
  167. });
  168. it('Dropdown.Item className & style', () => {
  169. let items = [
  170. { type: 'primary', children: 'primaryItem', className: 'primary-test', style: { color: 'red' } },
  171. { type: 'secondary', children: 'secondaryItem' },
  172. ];
  173. let props = {
  174. render: getSubMenu(items),
  175. trigger: 'custom',
  176. visible: true,
  177. };
  178. let DD = getDD(props);
  179. expect(DD.find('li.primary-test')).toHaveStyle('color', 'red');
  180. });
  181. it('Dropdown.Item disabled', () => {
  182. let items = [
  183. { disabled: true, children: 'Item 1' },
  184. { disabled: false, children: 'Item 2' },
  185. ];
  186. let props = {
  187. render: getSubMenu(items),
  188. trigger: 'custom',
  189. visible: true,
  190. };
  191. let DD = getDD(props);
  192. expect(DD.find(`.${BASE_CLASS_PREFIX}-dropdown-item-disabled`).text()).toEqual('Item 1');
  193. });
  194. it('Dropdown.Item onClick', () => {
  195. let onClick = event => {};
  196. let spyItemCLick = sinon.spy(onClick);
  197. let items = [{ children: 'A' }, { children: 'B', onClick: spyItemCLick, className: 'test' }];
  198. let props = {
  199. render: getSubMenu(items),
  200. trigger: 'custom',
  201. visible: true,
  202. };
  203. let DD = getDD(props);
  204. let targetItem = DD.find('li.test');
  205. let event = {
  206. button:0,
  207. target: {
  208. value: 'B1',
  209. },
  210. };
  211. targetItem.simulate('click', event);
  212. expect(spyItemCLick.calledOnce).toEqual(true);
  213. expect(spyItemCLick.calledWithMatch(event)).toEqual(true);
  214. });
  215. it('Dropdown.Item onMouseEnter/onMouseLeave', () => {
  216. let onMouseEnter = e => {};
  217. let spyItemMouseEnter = sinon.spy(onMouseEnter);
  218. let spyItemMouseLeave = sinon.spy(e => {});
  219. let items = [
  220. { children: 'A' },
  221. { children: 'B', onMouseEnter: spyItemMouseEnter, onMouseLeave: spyItemMouseLeave, className: 'test' },
  222. ];
  223. let props = {
  224. render: getSubMenu(items),
  225. trigger: 'custom',
  226. visible: true,
  227. };
  228. let DD = getDD(props);
  229. let targetItem = DD.find('li.test');
  230. let event = {
  231. target: {
  232. value: 'B1',
  233. },
  234. };
  235. targetItem.simulate('mouseEnter', event);
  236. expect(spyItemMouseEnter.calledOnce).toEqual(true);
  237. expect(spyItemMouseEnter.calledWithMatch(event)).toEqual(true);
  238. targetItem.simulate('mouseLeave', event);
  239. expect(spyItemMouseLeave.calledOnce).toEqual(true);
  240. expect(spyItemMouseLeave.calledWithMatch(event)).toEqual(true);
  241. });
  242. it('Dropdown.Title className & style', () => {
  243. let props = {
  244. render: (
  245. <Dropdown.Menu>
  246. <Dropdown.Title className="test" style={{ margin: 5 }}>
  247. 分组1
  248. </Dropdown.Title>
  249. <Dropdown.Item>primary</Dropdown.Item>
  250. <Dropdown.Item type="secondary">secondary</Dropdown.Item>
  251. <Dropdown.Divider />
  252. <Dropdown.Title>分组2</Dropdown.Title>
  253. <Dropdown.Item type="danger">danger</Dropdown.Item>
  254. </Dropdown.Menu>
  255. ),
  256. trigger: 'custom',
  257. visible: true,
  258. };
  259. let DD = getDD(props);
  260. expect(DD.find('div.test')).toHaveStyle('margin', 5);
  261. });
  262. it('Dropdown array menu', () => {
  263. const menu = [
  264. { node: 'title', name: '分组1' },
  265. { node: 'item', name: 'primary1', type: 'primary', onClick: () => console.log('click primary') },
  266. { node: 'item', name: 'secondary', type: 'secondary' },
  267. { node: 'divider', },
  268. { node: 'title', name: '分组2' },
  269. { node: 'item', name: 'tertiary', type: 'tertiary' },
  270. { node: 'item', name: 'warning', type: 'warning', active: true },
  271. { node: 'item', name: 'danger', type: 'danger' },
  272. ];
  273. let DD = mount(<Dropdown menu={menu} trigger="custom" visible ></Dropdown>, {
  274. attachTo: document.getElementById('container'),
  275. });
  276. expect(DD.find('.semi-dropdown-menu').children().length).toEqual(menu.length);
  277. const menu2 = [
  278. { node: 'title', name: '分组1', iconType: 'menu' },
  279. { node: 'item', name: 'secondary', type: 'secondary' },
  280. { node: 'divider', },
  281. { node: 'title', name: '分组2' },
  282. { node: 'invalid node', name: '分组2' },
  283. ];
  284. DD.setProps({ menu: menu2 })
  285. DD.update()
  286. expect(DD.find('.semi-dropdown-menu').children().length).toEqual(menu2.length - 1);
  287. });
  288. });