dropdown.test.js 11 KB

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