dropdownLink.spec.jsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import DropdownLink from 'sentry/components/dropdownLink';
  3. import {MENU_CLOSE_DELAY} from 'sentry/constants';
  4. jest.useFakeTimers();
  5. describe('DropdownLink', function () {
  6. const INPUT_1 = {
  7. title: 'test',
  8. onOpen: () => {},
  9. onClose: () => {},
  10. topLevelClasses: 'top-level-class',
  11. alwaysRenderMenu: true,
  12. menuClasses: '',
  13. };
  14. describe('renders', function () {
  15. it('and anchors to left by default', function () {
  16. const component = mountWithTheme(
  17. <DropdownLink {...INPUT_1}>
  18. <div>1</div>
  19. <div>2</div>
  20. </DropdownLink>
  21. );
  22. expect(component).toSnapshot();
  23. });
  24. it('and anchors to right', function () {
  25. const component = mountWithTheme(
  26. <DropdownLink {...INPUT_1} anchorRight>
  27. <div>1</div>
  28. <div>2</div>
  29. </DropdownLink>
  30. );
  31. expect(component).toSnapshot();
  32. });
  33. });
  34. describe('Uncontrolled', function () {
  35. let wrapper;
  36. beforeEach(function () {
  37. if (wrapper) {
  38. wrapper.unmount();
  39. }
  40. wrapper = mountWithTheme(
  41. <DropdownLink alwaysRenderMenu={false} title="test">
  42. <li>hi</li>
  43. </DropdownLink>
  44. );
  45. });
  46. describe('While Closed', function () {
  47. it('displays dropdown menu when dropdown actor button clicked', function () {
  48. expect(wrapper.find('li')).toHaveLength(0);
  49. // open
  50. wrapper.find('a').simulate('click');
  51. expect(wrapper.find('li')).toHaveLength(1);
  52. });
  53. });
  54. describe('While Opened', function () {
  55. beforeEach(function () {
  56. // Opens dropdown menu
  57. wrapper.find('a').simulate('click');
  58. });
  59. it('closes when clicked outside', async function () {
  60. const evt = document.createEvent('HTMLEvents');
  61. evt.initEvent('click', false, true);
  62. document.body.dispatchEvent(evt);
  63. jest.runAllTimers();
  64. await Promise.resolve();
  65. wrapper.update();
  66. expect(wrapper.find('li')).toHaveLength(0);
  67. });
  68. it('closes when dropdown actor button is clicked', function () {
  69. wrapper.find('a').simulate('click');
  70. expect(wrapper.find('li')).toHaveLength(0);
  71. });
  72. it('closes when dropdown menu item is clicked', function () {
  73. wrapper.find('li').simulate('click');
  74. expect(wrapper.find('li')).toHaveLength(0);
  75. });
  76. it('does not close when menu is clicked and `keepMenuOpen` is on', function () {
  77. wrapper = mountWithTheme(
  78. <DropdownLink title="test" alwaysRenderMenu={false} keepMenuOpen>
  79. <li>hi</li>
  80. </DropdownLink>
  81. );
  82. wrapper.find('a').simulate('click');
  83. wrapper.find('li').simulate('click');
  84. expect(wrapper.find('li')).toHaveLength(1);
  85. wrapper.unmount();
  86. });
  87. });
  88. });
  89. describe('Controlled', function () {
  90. let wrapper;
  91. beforeEach(function () {
  92. if (wrapper) {
  93. wrapper.unmount();
  94. }
  95. });
  96. describe('Opened', function () {
  97. beforeEach(function () {
  98. wrapper = mountWithTheme(
  99. <DropdownLink isOpen alwaysRenderMenu={false} title="test">
  100. <li>hi</li>
  101. </DropdownLink>
  102. );
  103. });
  104. it('does not close when menu is clicked', function () {
  105. // open
  106. wrapper.find('li').simulate('click');
  107. // State does not change
  108. expect(wrapper.find('.dropdown-menu')).toHaveLength(1);
  109. });
  110. it('does not close when document is clicked', function () {
  111. const evt = document.createEvent('HTMLEvents');
  112. evt.initEvent('click', false, true);
  113. document.body.dispatchEvent(evt);
  114. // State does not change
  115. expect(wrapper.find('.dropdown-menu')).toHaveLength(1);
  116. });
  117. it('does not close when dropdown actor is clicked', function () {
  118. wrapper.find('a').simulate('click');
  119. // State does not change
  120. expect(wrapper.find('.dropdown-menu')).toHaveLength(1);
  121. });
  122. });
  123. describe('Closed', function () {
  124. beforeEach(function () {
  125. wrapper = mountWithTheme(
  126. <DropdownLink isOpen={false} alwaysRenderMenu={false} title="test">
  127. <li>hi</li>
  128. </DropdownLink>
  129. );
  130. });
  131. it('does not open when dropdown actor is clicked', function () {
  132. wrapper.find('a').simulate('click');
  133. // State does not change
  134. expect(wrapper.find('.dropdown-menu')).toHaveLength(0);
  135. });
  136. });
  137. });
  138. describe('Nested Dropdown', function () {
  139. let wrapper;
  140. beforeEach(function () {
  141. if (wrapper) {
  142. wrapper.unmount();
  143. }
  144. wrapper = mountWithTheme(
  145. <DropdownLink title="parent" alwaysRenderMenu={false}>
  146. <li id="nested-actor">
  147. <DropdownLink
  148. className="nested-menu"
  149. alwaysRenderMenu={false}
  150. title="nested"
  151. isNestedDropdown
  152. >
  153. <li id="nested-actor-2">
  154. <DropdownLink
  155. className="nested-menu-2"
  156. alwaysRenderMenu={false}
  157. title="nested #2"
  158. isNestedDropdown
  159. >
  160. <li id="nested-actor-3">Hello</li>
  161. </DropdownLink>
  162. </li>
  163. </DropdownLink>
  164. </li>
  165. <li id="no-nest">Item 2</li>
  166. </DropdownLink>
  167. );
  168. // Start when menu open
  169. wrapper.find('a').simulate('click');
  170. });
  171. it('closes when top-level actor is clicked', function () {
  172. wrapper.find('a').first().simulate('click');
  173. expect(wrapper.find('.dropdown-menu')).toHaveLength(0);
  174. });
  175. it('Opens / closes on mouse enter and leave', function () {
  176. // Nested menus have delay on open
  177. wrapper.find('.dropdown-menu a').simulate('mouseEnter');
  178. jest.runAllTimers();
  179. wrapper.update();
  180. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  181. // Leaving Nested Menu
  182. wrapper.find('a.nested-menu').simulate('mouseLeave');
  183. // Nested menus have close delay
  184. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  185. jest.advanceTimersByTime(MENU_CLOSE_DELAY - 1);
  186. wrapper.update();
  187. // Re-entering nested menu will cancel close
  188. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  189. wrapper.find('a.nested-menu').simulate('mouseEnter');
  190. jest.advanceTimersByTime(2);
  191. wrapper.update();
  192. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  193. // Re-entering an actor will also cancel close
  194. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  195. jest.advanceTimersByTime(MENU_CLOSE_DELAY - 1);
  196. wrapper.update();
  197. wrapper.find('.dropdown-menu a').first().simulate('mouseEnter');
  198. jest.advanceTimersByTime(2);
  199. wrapper.update();
  200. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  201. // Leave menu
  202. wrapper.find('a.nested-menu').simulate('mouseLeave');
  203. jest.runAllTimers();
  204. wrapper.update();
  205. expect(wrapper.find('.dropdown-menu')).toHaveLength(1);
  206. });
  207. it('closes when first level nested actor is clicked', function () {
  208. wrapper.find('#nested-actor').simulate('click');
  209. expect(wrapper.find('.dropdown-menu')).toHaveLength(0);
  210. });
  211. it('does not close when second level nested actor is clicked', function () {
  212. wrapper.find('a.nested-menu').simulate('mouseEnter');
  213. jest.runAllTimers();
  214. wrapper.update();
  215. wrapper.find('a.nested-menu-2').simulate('click');
  216. expect(wrapper.find('.dropdown-menu')).toHaveLength(2);
  217. });
  218. it('closes when third level nested actor is clicked', function () {
  219. wrapper.find('a.nested-menu').simulate('mouseEnter');
  220. jest.runAllTimers();
  221. wrapper.update();
  222. wrapper.find('a.nested-menu-2').simulate('mouseEnter');
  223. jest.runAllTimers();
  224. wrapper.update();
  225. wrapper.find('#nested-actor-3').simulate('click');
  226. expect(wrapper.find('.dropdown-menu')).toHaveLength(0);
  227. });
  228. });
  229. });