github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/frontend-service/src/ui/components/dialog/ConfirmationDialog.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 17 import React from 'react'; 18 19 import { ConfirmationDialog, ConfirmationDialogProps, PlainDialog, PlainDialogProps } from './ConfirmationDialog'; 20 import { act, getByTestId, render } from '@testing-library/react'; 21 import { MemoryRouter } from 'react-router-dom'; 22 23 const getNodeConfirm = (overrides?: {}): JSX.Element | any => { 24 const props: any = { 25 children: null, 26 open: true, 27 ...overrides, 28 }; 29 // given 30 return ( 31 <div> 32 <MemoryRouter> 33 <ConfirmationDialog {...props} /> 34 </MemoryRouter> 35 </div> 36 ); 37 }; 38 39 const getNodePlain = (overrides?: {}): JSX.Element | any => { 40 const props: any = { 41 open: true, 42 center: false, 43 ...overrides, 44 }; 45 // given 46 return ( 47 <div> 48 <MemoryRouter> 49 <PlainDialog {...props} /> 50 </MemoryRouter> 51 </div> 52 ); 53 }; 54 55 const addSibling = (wrappee: JSX.Element) => ( 56 <> 57 {wrappee} 58 <div data-testid="sibling-outside"></div> 59 </> 60 ); 61 62 const getWrapperConfirm = (overrides?: Partial<ConfirmationDialogProps>) => 63 render(addSibling(getNodeConfirm(overrides))); 64 const getWrapperPlain = (overrides?: Partial<PlainDialogProps>) => render(addSibling(getNodePlain(overrides))); 65 66 const testIdRootRefParent = 'test-root-ref-parent'; 67 68 describe('ConfirmDialog', () => { 69 type TestData = { 70 name: string; 71 props: Partial<ConfirmationDialogProps>; 72 // if we click these elements 73 clickViaEvent: string[]; 74 // click button 75 clickButton: string[]; 76 sendKey: string; 77 // then we expect dialog to be 78 expectClose: boolean; 79 expectRootRefClasses: string[]; 80 }; 81 82 type propMod = (props: Partial<ConfirmationDialogProps>) => void; 83 const fixtureProps = (...mods: propMod[]) => { 84 const props = { 85 testIdRootRefParent: testIdRootRefParent, 86 headerLabel: 'Header label', 87 confirmLabel: 'Confirm label', 88 }; 89 mods.forEach((m: propMod) => m(props)); 90 return props; 91 }; 92 const fixtureTestData = () => ({ 93 props: fixtureProps(), 94 clickViaEvent: [], 95 clickButton: [], 96 sendKey: '', 97 expectClose: false, 98 expectRootRefClasses: ['confirmation-dialog-container', 'confirmation-dialog-container-open'], 99 }); 100 101 const data: TestData[] = [ 102 { 103 name: 'visible if not clicked', 104 ...fixtureTestData(), 105 }, 106 { 107 name: 'close if clicked outside', 108 ...fixtureTestData(), 109 clickViaEvent: ['sibling-outside'], 110 expectClose: true, 111 }, 112 { 113 name: 'close if escape pressed', 114 ...fixtureTestData(), 115 clickViaEvent: ['sibling-outside'], 116 sendKey: 'Escape', 117 expectClose: true, 118 }, 119 { 120 name: 'close if cancel button pressed', 121 ...fixtureTestData(), 122 clickButton: ['test-confirm-button-cancel'], 123 expectClose: true, 124 }, 125 { 126 name: 'do not close on random key press', 127 ...fixtureTestData(), 128 sendKey: 'a', 129 expectClose: false, 130 }, 131 ]; 132 133 const mouseClickEvents = ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup']; 134 135 describe.each(data)('Closes confirm dialog on user interaction', (testcase) => { 136 it(testcase.name, () => { 137 let calledClose = false; 138 const onClose = () => { 139 calledClose = true; 140 }; 141 const { container } = getWrapperConfirm({ 142 ...testcase.props, 143 onCancel: onClose, 144 children: ( 145 <> 146 <div data-testid="test-content"></div> 147 </> 148 ), 149 }); 150 151 testcase.clickViaEvent.forEach((id) => { 152 const elem = getByTestId(container, id); 153 mouseClickEvents.forEach((mouseEventType) => { 154 act(() => 155 elem.dispatchEvent( 156 new MouseEvent(mouseEventType, { 157 view: window, 158 bubbles: true, 159 // cancelable: true, 160 buttons: 1, 161 }) 162 ) 163 ); 164 }); 165 }); 166 testcase.clickButton.forEach((id) => { 167 act(() => getByTestId(container, id).click()); 168 }); 169 170 if (testcase.sendKey !== '') { 171 ['keydown', 'keypress', 'keyup'].forEach((keyEventType) => 172 act(() => 173 document.dispatchEvent( 174 new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true }) 175 ) 176 ) 177 ); 178 } 179 180 expect(calledClose).toBe(testcase.expectClose); 181 182 if (!testcase.expectClose) { 183 const header = container.getElementsByClassName('confirmation-dialog-header'); 184 expect(header).toHaveLength(1); 185 expect(header[0]).toHaveTextContent(testcase.props.headerLabel ?? ''); 186 187 const confirm = getByTestId(container, 'test-confirm-button-confirm'); 188 expect(confirm).toHaveTextContent(testcase.props.confirmLabel ?? ''); 189 expect(getByTestId(container, 'test-confirm-button-cancel')).toBeVisible(); 190 191 testcase.expectRootRefClasses.forEach((clas) => 192 expect(getByTestId(container, testIdRootRefParent)).toHaveClass(clas) 193 ); 194 } 195 }); 196 }); 197 }); 198 199 describe('PlainDialog Closing', () => { 200 type TestData = { 201 name: string; 202 props: Partial<PlainDialogProps>; 203 // if we click these elements 204 clickViaEvent: string[]; 205 sendKey: string; 206 // then we expect dialog to be 207 expectClose: boolean; 208 expectRootRefClasses: string[]; 209 }; 210 211 type propMod = (props: Partial<PlainDialogProps>) => void; 212 const fixtureProps = (...mods: propMod[]) => { 213 const props = { 214 testIdRootRefParent: testIdRootRefParent, 215 disableBackground: false, 216 center: false, 217 children: ( 218 <> 219 <div data-testid="test-content-inside"></div> 220 </> 221 ), 222 }; 223 mods.forEach((m: propMod) => m(props)); 224 return props; 225 }; 226 const fixtureTestData = () => ({ 227 props: fixtureProps(), 228 clickViaEvent: [], 229 sendKey: '', 230 expectClose: false, 231 expectRootRefClasses: ['plain-dialog-container'], 232 }); 233 234 const data: TestData[] = [ 235 { 236 name: 'visible if not clicked', 237 ...fixtureTestData(), 238 }, 239 { 240 name: 'close if clicked outside', 241 ...fixtureTestData(), 242 clickViaEvent: ['sibling-outside'], 243 expectClose: true, 244 }, 245 { 246 name: 'still visible if clicked on content', 247 ...fixtureTestData(), 248 clickViaEvent: ['content-inside'], 249 expectClose: false, 250 }, 251 { 252 name: 'still visible if clicked on content while centered', 253 ...fixtureTestData(), 254 props: fixtureProps((props) => { 255 props.center = true; 256 }), 257 clickViaEvent: ['content-inside'], 258 expectClose: false, 259 expectRootRefClasses: ['confirmation-dialog-container'], 260 }, 261 { 262 name: 'still visible if clicked on content while centered with disabled background', 263 ...fixtureTestData(), 264 props: fixtureProps((props) => { 265 props.center = true; 266 props.disableBackground = true; 267 }), 268 clickViaEvent: ['content-inside'], 269 expectClose: false, 270 expectRootRefClasses: ['confirmation-dialog-container', 'confirmation-dialog-container-open'], 271 }, 272 { 273 name: 'close if clicked outside while centered', 274 ...fixtureTestData(), 275 props: fixtureProps((props) => { 276 props.center = true; 277 }), 278 clickViaEvent: ['sibling-outside'], 279 expectClose: true, 280 }, 281 { 282 name: 'different dialog class if centered', 283 ...fixtureTestData(), 284 props: fixtureProps((props) => { 285 props.center = true; 286 }), 287 expectClose: false, 288 expectRootRefClasses: ['confirmation-dialog-container'], 289 }, 290 { 291 name: 'close if escape pressed', 292 ...fixtureTestData(), 293 clickViaEvent: ['sibling-outside'], 294 sendKey: 'Escape', 295 expectClose: true, 296 }, 297 { 298 name: 'do not close on random key press', 299 ...fixtureTestData(), 300 sendKey: 'a', 301 expectClose: false, 302 }, 303 ]; 304 305 const mouseClickEvents = ['pointerdown', 'mousedown', 'click', 'mouseup', 'pointerup']; 306 307 describe.each(data)('Closes plain dialog on user interaction', (testcase) => { 308 it(testcase.name, () => { 309 let calledClose = false; 310 const onClose = () => { 311 calledClose = true; 312 }; 313 const { container } = getWrapperPlain({ 314 ...testcase.props, 315 onClose: onClose, 316 children: ( 317 <> 318 <div data-testid="content-inside"></div> 319 </> 320 ), 321 }); 322 323 testcase.clickViaEvent.forEach((id) => { 324 const elem = getByTestId(container, id); 325 mouseClickEvents.forEach((mouseEventType) => { 326 elem.dispatchEvent( 327 new MouseEvent(mouseEventType, { 328 view: window, 329 bubbles: true, 330 buttons: 1, 331 }) 332 ); 333 }); 334 }); 335 336 if (testcase.sendKey !== '') { 337 ['keydown', 'keypress', 'keyup'].forEach((keyEventType) => 338 document.dispatchEvent(new KeyboardEvent(keyEventType, { key: testcase.sendKey, bubbles: true })) 339 ); 340 } 341 342 expect(calledClose).toBe(testcase.expectClose); 343 344 if (!testcase.expectClose) { 345 testcase.expectRootRefClasses.forEach((clas) => 346 expect(getByTestId(container, testIdRootRefParent)).toHaveClass(clas) 347 ); 348 } 349 }); 350 }); 351 });