dropdownMenu.spec.jsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import React from 'react';
  2. import {mount} from 'sentry-test/enzyme';
  3. import DropdownMenu from 'app/components/dropdownMenu';
  4. jest.useFakeTimers();
  5. describe('DropdownMenu', function() {
  6. let wrapper;
  7. beforeEach(function() {
  8. wrapper = mount(
  9. <DropdownMenu>
  10. {({getRootProps, getActorProps, getMenuProps, isOpen}) => (
  11. <span {...getRootProps({})}>
  12. <button {...getActorProps({})}>Open Dropdown</button>
  13. {isOpen && (
  14. <ul {...getMenuProps({})}>
  15. <li>Dropdown Menu Item 1</li>
  16. </ul>
  17. )}
  18. </span>
  19. )}
  20. </DropdownMenu>
  21. );
  22. });
  23. it('renders', function() {
  24. expect(wrapper).toSnapshot();
  25. });
  26. it('can toggle dropdown menu with actor', function() {
  27. wrapper.find('button').simulate('click');
  28. expect(wrapper.state('isOpen')).toBe(true);
  29. expect(wrapper.find('ul')).toHaveLength(1);
  30. wrapper.find('button').simulate('click');
  31. expect(wrapper.state('isOpen')).toBe(false);
  32. expect(wrapper.find('ul')).toHaveLength(0);
  33. });
  34. it('closes dropdown when clicking on anything in menu', function() {
  35. wrapper.find('button').simulate('click');
  36. wrapper.find('li').simulate('click');
  37. expect(wrapper.state('isOpen')).toBe(false);
  38. expect(wrapper.find('ul')).toHaveLength(0);
  39. });
  40. it('closes dropdown when clicking outside of menu', async function() {
  41. wrapper.find('button').simulate('click');
  42. // Simulate click on document
  43. const evt = document.createEvent('HTMLEvents');
  44. evt.initEvent('click', false, true);
  45. document.body.dispatchEvent(evt);
  46. jest.runAllTimers();
  47. await Promise.resolve();
  48. wrapper.update();
  49. expect(wrapper.find('ul')).toHaveLength(0);
  50. });
  51. it('closes dropdown when pressing escape', function() {
  52. wrapper.find('button').simulate('click');
  53. expect(wrapper.state('isOpen')).toBe(true);
  54. wrapper.simulate('keyDown', {key: 'Escape'});
  55. wrapper.find('button').simulate('keyDown', {key: 'Escape'});
  56. expect(wrapper.state('isOpen')).toBe(false);
  57. expect(wrapper.find('ul')).toHaveLength(0);
  58. });
  59. it('ignores "Escape" key if `closeOnEscape` is false', function() {
  60. wrapper = mount(
  61. <DropdownMenu closeOnEscape={false}>
  62. {({getRootProps, getActorProps, getMenuProps, isOpen}) => (
  63. <span {...getRootProps({})}>
  64. <button {...getActorProps({})}>Open Dropdown</button>
  65. {isOpen && (
  66. <ul {...getMenuProps({})}>
  67. <li>Dropdown Menu Item 1</li>
  68. </ul>
  69. )}
  70. </span>
  71. )}
  72. </DropdownMenu>
  73. );
  74. wrapper.find('button').simulate('click');
  75. expect(wrapper.state('isOpen')).toBe(true);
  76. wrapper.find('button').simulate('keyDown', {key: 'Escape'});
  77. expect(wrapper.find('ul')).toHaveLength(1);
  78. expect(wrapper.state('isOpen')).toBe(true);
  79. });
  80. it('keeps dropdown open when clicking on anything in menu with `keepMenuOpen` prop', function() {
  81. wrapper = mount(
  82. <DropdownMenu keepMenuOpen>
  83. {({getRootProps, getActorProps, getMenuProps, isOpen}) => (
  84. <span {...getRootProps({})}>
  85. <button {...getActorProps({})}>Open Dropdown</button>
  86. {isOpen && (
  87. <ul {...getMenuProps({})}>
  88. <li>Dropdown Menu Item 1</li>
  89. </ul>
  90. )}
  91. </span>
  92. )}
  93. </DropdownMenu>
  94. );
  95. wrapper.find('button').simulate('click');
  96. wrapper.find('li').simulate('click');
  97. expect(wrapper.state('isOpen')).toBe(true);
  98. expect(wrapper.find('ul')).toHaveLength(1);
  99. });
  100. it('render prop getters all extend props and call original onClick handlers', function() {
  101. const rootClick = jest.fn();
  102. const actorClick = jest.fn();
  103. const menuClick = jest.fn();
  104. const addSpy = jest.spyOn(document, 'addEventListener');
  105. const removeSpy = jest.spyOn(document, 'removeEventListener');
  106. wrapper = mount(
  107. <DropdownMenu keepMenuOpen>
  108. {({getRootProps, getActorProps, getMenuProps, isOpen}) => (
  109. <span
  110. {...getRootProps({
  111. className: 'root',
  112. onClick: rootClick,
  113. })}
  114. >
  115. <button
  116. {...getActorProps({
  117. className: 'actor',
  118. onClick: actorClick,
  119. })}
  120. >
  121. Open Dropdown
  122. </button>
  123. {isOpen && (
  124. <ul
  125. {...getMenuProps({
  126. className: 'menu',
  127. onClick: menuClick,
  128. })}
  129. >
  130. <li>Dropdown Menu Item 1</li>
  131. </ul>
  132. )}
  133. </span>
  134. )}
  135. </DropdownMenu>
  136. );
  137. expect(wrapper.find('ul')).toHaveLength(0);
  138. wrapper.find('span').simulate('click');
  139. expect(rootClick).toHaveBeenCalled();
  140. wrapper.find('button').simulate('click');
  141. expect(actorClick).toHaveBeenCalled();
  142. wrapper.find('li').simulate('click');
  143. expect(menuClick).toHaveBeenCalled();
  144. // breaks in jest22
  145. // expect(wrapper).toSnapshot();
  146. expect(wrapper.find('ul')).toHaveLength(1);
  147. expect(document.addEventListener).toHaveBeenCalled();
  148. wrapper.unmount();
  149. expect(document.removeEventListener).toHaveBeenCalled();
  150. addSpy.mockRestore();
  151. removeSpy.mockRestore();
  152. });
  153. it('always rendered menus should attach document event listeners only when opened', function() {
  154. const addSpy = jest.spyOn(document, 'addEventListener');
  155. const removeSpy = jest.spyOn(document, 'removeEventListener');
  156. wrapper = mount(
  157. <DropdownMenu alwaysRenderMenu>
  158. {({getRootProps, getActorProps, getMenuProps}) => (
  159. <span
  160. {...getRootProps({
  161. className: 'root',
  162. })}
  163. >
  164. <button
  165. {...getActorProps({
  166. className: 'actor',
  167. })}
  168. >
  169. Open Dropdown
  170. </button>
  171. <ul
  172. {...getMenuProps({
  173. className: 'menu',
  174. })}
  175. >
  176. <li>Dropdown Menu Item 1</li>
  177. </ul>
  178. </span>
  179. )}
  180. </DropdownMenu>
  181. );
  182. // Make sure this is only called when menu is open
  183. expect(document.addEventListener).not.toHaveBeenCalled();
  184. wrapper.find('button').simulate('click');
  185. expect(wrapper.state('isOpen')).toBe(true);
  186. expect(document.addEventListener).toHaveBeenCalled();
  187. expect(document.removeEventListener).not.toHaveBeenCalled();
  188. wrapper.find('button').simulate('click');
  189. expect(wrapper.state('isOpen')).toBe(false);
  190. expect(document.removeEventListener).toHaveBeenCalled();
  191. addSpy.mockRestore();
  192. removeSpy.mockRestore();
  193. });
  194. it('does not close nested dropdown on actor clicks', function() {
  195. wrapper = mount(
  196. <DropdownMenu isNestedDropdown>
  197. {({getRootProps, getActorProps, getMenuProps}) => (
  198. <span {...getRootProps({})}>
  199. <button {...getActorProps({})}>Open Dropdown</button>
  200. {
  201. <ul {...getMenuProps({})}>
  202. <li data-test-id="menu-item">Dropdown Menu Item 1</li>
  203. </ul>
  204. }
  205. </span>
  206. )}
  207. </DropdownMenu>
  208. );
  209. wrapper.find('button').simulate('mouseEnter');
  210. expect(wrapper.find('[data-test-id="menu-item"]')).toHaveLength(1);
  211. wrapper.find('button').simulate('click');
  212. //Should still be visible.
  213. expect(wrapper.find('[data-test-id="menu-item"]')).toHaveLength(1);
  214. });
  215. });