github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/ServiceLane/DotsMenu.test.tsx (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 import { act, render } from '@testing-library/react'; 17 import { DotsMenu, DotsMenuProps } from './DotsMenu'; 18 import { elementQuerySelectorSafe } from '../../../setupTests'; 19 20 describe('DotsMenu Rendering', () => { 21 const getNode = (overrides: DotsMenuProps) => <DotsMenu {...overrides} />; 22 const getWrapper = (overrides: DotsMenuProps) => render(getNode(overrides)); 23 24 const mySpy = jest.fn(); 25 26 type TestData = { 27 name: string; 28 input: DotsMenuProps; 29 expectedNumItems: number; 30 }; 31 32 const data: TestData[] = [ 33 { 34 name: 'renders empty list', 35 input: { buttons: [] }, 36 expectedNumItems: 0, 37 }, 38 { 39 name: 'renders one button', 40 input: { 41 buttons: [ 42 { 43 label: 'test label', 44 onClick: mySpy, 45 }, 46 ], 47 }, 48 expectedNumItems: 1, 49 }, 50 { 51 name: 'renders three button', 52 input: { 53 buttons: [ 54 { 55 label: 'test label A', 56 onClick: mySpy, 57 }, 58 { 59 label: 'test label B', 60 onClick: mySpy, 61 }, 62 { 63 label: 'test label C', 64 onClick: mySpy, 65 }, 66 ], 67 }, 68 expectedNumItems: 3, 69 }, 70 ]; 71 72 describe.each(data)('DotsMenu Test', (testcase) => { 73 it(testcase.name, () => { 74 mySpy.mockReset(); 75 expect(mySpy).toHaveBeenCalledTimes(0); 76 77 const { container } = getWrapper(testcase.input); 78 79 expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1); 80 const result = elementQuerySelectorSafe(container, '.dots-menu-hidden .mdc-button--unelevated'); 81 act(() => { 82 result.click(); 83 }); 84 expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(0); 85 expect(document.querySelectorAll('li.item').length).toEqual(testcase.expectedNumItems); 86 87 if (testcase.expectedNumItems > 0) { 88 expect(mySpy).toHaveBeenCalledTimes(0); 89 const result = elementQuerySelectorSafe(container, 'li .mdc-button--unelevated'); 90 act(() => { 91 result.click(); 92 }); 93 expect(mySpy).toHaveBeenCalledTimes(1); 94 } 95 }); 96 }); 97 }); 98 99 const addSibling = (wrappee: JSX.Element) => ( 100 <> 101 {wrappee} 102 <div id="sibling-outside"></div> 103 </> 104 ); 105 106 describe('DotsMenu Close', () => { 107 const getNode = (overrides: DotsMenuProps) => <DotsMenu {...overrides} />; 108 const getWrapper = (overrides: DotsMenuProps) => render(addSibling(getNode(overrides))); 109 110 type propMod = (props: Partial<DotsMenuProps>) => void; 111 const fixtureProps = (...mods: propMod[]) => { 112 const props = { 113 buttons: [ 114 { 115 label: 'test label A', 116 onClick: () => {}, 117 }, 118 { 119 label: 'test label B', 120 onClick: () => {}, 121 }, 122 { 123 label: 'test label C', 124 onClick: () => {}, 125 }, 126 ], 127 }; 128 mods.forEach((m) => m(props)); 129 return props; 130 }; 131 132 type TestData = { 133 name: string; 134 props: DotsMenuProps; 135 sendKey: string; 136 clickViaEvent: string[]; 137 expectClosed: boolean; 138 }; 139 const fixtureTestData = () => ({ 140 props: fixtureProps(), 141 sendKey: '', 142 clickViaEvent: [], 143 expectClosed: false, 144 }); 145 146 const data: TestData[] = [ 147 { 148 name: 'visible if not clicking', 149 ...fixtureTestData(), 150 expectClosed: false, 151 }, 152 { 153 name: 'close if escape is pressed', 154 ...fixtureTestData(), 155 sendKey: 'Escape', 156 expectClosed: true, 157 }, 158 { 159 name: 'visible if any other button is pressed', 160 ...fixtureTestData(), 161 sendKey: 'a', 162 expectClosed: false, 163 }, 164 { 165 name: 'close if sibling is clicked', 166 ...fixtureTestData(), 167 clickViaEvent: ['#sibling-outside'], 168 expectClosed: true, 169 }, 170 { 171 name: 'visible if elements inside is clicked', 172 ...fixtureTestData(), 173 clickViaEvent: ['.item', '.dots-menu-open'], 174 expectClosed: false, 175 }, 176 ]; 177 178 describe.each(data)('DotsMenu Closing', (testcase) => { 179 it(testcase.name, () => { 180 const { container } = getWrapper(testcase.props); 181 182 // open menu 183 expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1); 184 const result = elementQuerySelectorSafe(container, '.dots-menu-hidden .mdc-button--unelevated'); 185 act(() => { 186 result.click(); 187 }); 188 expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(0); 189 190 testcase.clickViaEvent.forEach((selector) => { 191 act(() => { 192 const elem = elementQuerySelectorSafe(container, selector); 193 ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup'].forEach((mouseEventType) => { 194 elem.dispatchEvent( 195 new MouseEvent(mouseEventType, { 196 view: window, 197 bubbles: true, 198 buttons: 1, 199 }) 200 ); 201 }); 202 }); 203 }); 204 205 if (testcase.sendKey !== '') { 206 ['keydown', 'keypress', 'keyup'].forEach((keyEventType) => 207 act(() => 208 document.dispatchEvent( 209 new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true }) 210 ) 211 ) 212 ); 213 } 214 215 if (testcase.expectClosed) { 216 expect(document.querySelectorAll('.dots-menu-hidden .mdc-button--unelevated').length).toEqual(1); 217 } else { 218 expect(document.querySelectorAll('.dots-menu-open').length).toEqual(1); 219 } 220 }); 221 }); 222 });