1 |
|
2 | import React from 'react';
|
3 | import { render, screen } from '@testing-library/react';
|
4 | import user from '@testing-library/user-event';
|
5 | import '@testing-library/jest-dom';
|
6 | import { Modal, ModalBody, ModalHeader, ModalFooter, Button } from '..';
|
7 |
|
8 | describe('Modal', () => {
|
9 | const toggle = () => {};
|
10 |
|
11 | beforeEach(() => {
|
12 | jest.useFakeTimers();
|
13 | });
|
14 |
|
15 | afterEach(() => {
|
16 | jest.clearAllTimers();
|
17 |
|
18 | document.body.removeAttribute('style');
|
19 | document.body.removeAttribute('class');
|
20 | });
|
21 |
|
22 | it('should render modal portal into DOM', () => {
|
23 | render(
|
24 | <Modal isOpen toggle={toggle}>
|
25 | Yo!
|
26 | </Modal>,
|
27 | );
|
28 |
|
29 | expect(screen.getByText(/yo/i)).toBeInTheDocument();
|
30 | });
|
31 |
|
32 | it('should render with the class "modal-dialog"', () => {
|
33 | render(
|
34 | <Modal isOpen toggle={toggle}>
|
35 | Yo!
|
36 | </Modal>,
|
37 | );
|
38 |
|
39 | expect(screen.getByText(/yo/i).parentElement).toHaveClass('modal-dialog');
|
40 | });
|
41 |
|
42 | it('should render external content when present', () => {
|
43 | render(
|
44 | <Modal
|
45 | isOpen
|
46 | toggle={toggle}
|
47 | external={<button className="cool-close-button">crazy button</button>}
|
48 | >
|
49 | Yo!
|
50 | </Modal>,
|
51 | );
|
52 |
|
53 | expect(screen.getByText(/crazy button/i)).toBeInTheDocument();
|
54 | expect(
|
55 | screen
|
56 | .getByText(/crazy button/i)
|
57 | .nextElementSibling.className.split(' ')
|
58 | .indexOf('modal-dialog') > -1,
|
59 | ).toBe(true);
|
60 | });
|
61 |
|
62 | it('should render with the backdrop with the class "modal-backdrop" by default', () => {
|
63 | render(
|
64 | <Modal isOpen toggle={toggle}>
|
65 | Yo!
|
66 | </Modal>,
|
67 | );
|
68 |
|
69 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
|
70 | });
|
71 |
|
72 | it('should render with the backdrop with the class "modal-backdrop" when backdrop is "static"', () => {
|
73 | render(
|
74 | <Modal isOpen toggle={toggle} backdrop="static">
|
75 | Yo!
|
76 | </Modal>,
|
77 | );
|
78 |
|
79 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
|
80 | });
|
81 |
|
82 | it('should not render with the backdrop with the class "modal-backdrop" when backdrop is "false"', () => {
|
83 | render(
|
84 | <Modal isOpen toggle={toggle} backdrop={false}>
|
85 | Yo!
|
86 | </Modal>,
|
87 | );
|
88 |
|
89 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
90 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(0);
|
91 | });
|
92 |
|
93 | it('should render with the class "modal-dialog-scrollable" when scrollable is "true"', () => {
|
94 | render(
|
95 | <Modal isOpen toggle={toggle} scrollable>
|
96 | Yo!
|
97 | </Modal>,
|
98 | );
|
99 |
|
100 | expect(
|
101 | document.getElementsByClassName('modal-dialog-scrollable').length,
|
102 | ).toBe(1);
|
103 | });
|
104 |
|
105 | it('should render with class "modal-dialog" and have custom class name if provided', () => {
|
106 | render(
|
107 | <Modal isOpen toggle={toggle} className="my-custom-modal">
|
108 | Yo!
|
109 | </Modal>,
|
110 | );
|
111 |
|
112 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
113 | expect(document.getElementsByClassName('my-custom-modal').length).toBe(1);
|
114 | });
|
115 |
|
116 | it('should render with class "modal-dialog" w/o centered class if not provided', () => {
|
117 | render(
|
118 | <Modal isOpen toggle={toggle}>
|
119 | Yo!
|
120 | </Modal>,
|
121 | );
|
122 |
|
123 | jest.advanceTimersByTime(300);
|
124 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
125 | expect(
|
126 | document.getElementsByClassName('modal-dialog-centered').length,
|
127 | ).toBe(0);
|
128 | });
|
129 |
|
130 | it('should render with class "modal-dialog" and centered class if provided', () => {
|
131 | render(
|
132 | <Modal isOpen toggle={toggle} centered>
|
133 | Yo!
|
134 | </Modal>,
|
135 | );
|
136 |
|
137 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
138 | expect(
|
139 | document.getElementsByClassName('modal-dialog-centered').length,
|
140 | ).toBe(1);
|
141 | });
|
142 |
|
143 | describe('fullscreen', () => {
|
144 | it('should render non fullscreen by default', () => {
|
145 | render(
|
146 | <Modal isOpen toggle={toggle}>
|
147 | Yo!
|
148 | </Modal>,
|
149 | );
|
150 |
|
151 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
152 | expect(document.getElementsByClassName('modal-fullscreen').length).toBe(
|
153 | 0,
|
154 | );
|
155 | });
|
156 |
|
157 | it('should always render fullscreen if true', () => {
|
158 | render(
|
159 | <Modal isOpen toggle={toggle} fullscreen>
|
160 | Yo!
|
161 | </Modal>,
|
162 | );
|
163 |
|
164 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
165 | expect(document.getElementsByClassName('modal-fullscreen').length).toBe(
|
166 | 1,
|
167 | );
|
168 | });
|
169 |
|
170 | it('should render fullscreen below breakpoint if breakpoint is provided', () => {
|
171 | render(
|
172 | <Modal isOpen toggle={toggle} fullscreen="lg">
|
173 | Yo!
|
174 | </Modal>,
|
175 | );
|
176 |
|
177 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
178 | expect(document.getElementsByClassName('modal-fullscreen').length).toBe(
|
179 | 0,
|
180 | );
|
181 | expect(
|
182 | document.getElementsByClassName('modal-fullscreen-lg-down').length,
|
183 | ).toBe(1);
|
184 | });
|
185 | });
|
186 |
|
187 | it('should render with additional props if provided', () => {
|
188 | render(
|
189 | <Modal isOpen toggle={toggle} style={{ maxWidth: '95%' }}>
|
190 | Yo!
|
191 | </Modal>,
|
192 | );
|
193 |
|
194 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
195 | expect(
|
196 | document.getElementsByClassName('modal-dialog')[0].style.maxWidth,
|
197 | ).toBe('95%');
|
198 | });
|
199 |
|
200 | it('should render without fade transition if provided with fade={false}', () => {
|
201 | render(
|
202 | <Modal
|
203 | isOpen
|
204 | toggle={toggle}
|
205 | fade={false}
|
206 | modalClassName="fadeless-modal"
|
207 | >
|
208 | Howdy!
|
209 | </Modal>,
|
210 | );
|
211 |
|
212 | const matchedModals = document.getElementsByClassName('fadeless-modal');
|
213 | const matchedModal = matchedModals[0];
|
214 |
|
215 | expect(matchedModals.length).toBe(1);
|
216 |
|
217 | expect(matchedModal.className.split(' ').indexOf('fade') < 0).toBe(true);
|
218 | });
|
219 |
|
220 | it('should render when expected when passed modalTransition and backdropTransition props', () => {
|
221 | render(
|
222 | <Modal
|
223 | isOpen
|
224 | toggle={toggle}
|
225 | modalTransition={{ timeout: 2 }}
|
226 | backdropTransition={{ timeout: 10 }}
|
227 | modalClassName="custom-timeout-modal"
|
228 | >
|
229 | Hello, world!
|
230 | </Modal>,
|
231 | );
|
232 |
|
233 | expect(document.getElementsByClassName('custom-timeout-modal').length).toBe(
|
234 | 1,
|
235 | );
|
236 | });
|
237 |
|
238 | it('should render with class "modal" and have custom class name if provided with modalClassName', () => {
|
239 | render(
|
240 | <Modal isOpen toggle={toggle} modalClassName="my-custom-modal">
|
241 | Yo!
|
242 | </Modal>,
|
243 | );
|
244 |
|
245 | expect(document.querySelectorAll('.modal.my-custom-modal').length).toBe(1);
|
246 | });
|
247 |
|
248 | it('should render with custom class name if provided with wrapClassName', () => {
|
249 | render(
|
250 | <Modal isOpen toggle={toggle} wrapClassName="my-custom-modal">
|
251 | Yo!
|
252 | </Modal>,
|
253 | );
|
254 |
|
255 | expect(document.getElementsByClassName('my-custom-modal').length).toBe(1);
|
256 | });
|
257 |
|
258 | it('should render with class "modal-content" and have custom class name if provided with contentClassName', () => {
|
259 | render(
|
260 | <Modal isOpen toggle={toggle} contentClassName="my-custom-modal">
|
261 | Yo!
|
262 | </Modal>,
|
263 | );
|
264 |
|
265 | expect(
|
266 | document.querySelectorAll('.modal-content.my-custom-modal').length,
|
267 | ).toBe(1);
|
268 | });
|
269 |
|
270 | it('should render with class "modal-backdrop" and have custom class name if provided with backdropClassName', () => {
|
271 | render(
|
272 | <Modal isOpen toggle={toggle} backdropClassName="my-custom-modal">
|
273 | Yo!
|
274 | </Modal>,
|
275 | );
|
276 |
|
277 | expect(
|
278 | document.querySelectorAll('.modal-backdrop.my-custom-modal').length,
|
279 | ).toBe(1);
|
280 | });
|
281 |
|
282 | it('should render with the class "modal-${size}" when size is passed', () => {
|
283 | render(
|
284 | <Modal isOpen toggle={toggle} size="crazy">
|
285 | Yo!
|
286 | </Modal>,
|
287 | );
|
288 |
|
289 | expect(document.getElementsByClassName('modal-dialog').length).toBe(1);
|
290 | expect(document.getElementsByClassName('modal-crazy').length).toBe(1);
|
291 | });
|
292 |
|
293 | it('should render modal when isOpen is true', () => {
|
294 | render(
|
295 | <Modal isOpen toggle={toggle}>
|
296 | Yo!
|
297 | </Modal>,
|
298 | );
|
299 |
|
300 | expect(document.getElementsByClassName('modal').length).toBe(1);
|
301 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
|
302 | });
|
303 |
|
304 | it('should render modal with default role of "dialog"', () => {
|
305 | render(
|
306 | <Modal isOpen toggle={toggle}>
|
307 | Yo!
|
308 | </Modal>,
|
309 | );
|
310 |
|
311 | expect(
|
312 | document.getElementsByClassName('modal')[0].getAttribute('role'),
|
313 | ).toBe('dialog');
|
314 | });
|
315 |
|
316 | it('should render modal with provided role', () => {
|
317 | render(
|
318 | <Modal isOpen toggle={toggle} role="alert">
|
319 | Yo!
|
320 | </Modal>,
|
321 | );
|
322 |
|
323 | expect(
|
324 | document.getElementsByClassName('modal')[0].getAttribute('role'),
|
325 | ).toBe('alert');
|
326 | });
|
327 |
|
328 | it('should render modal with aria-labelledby provided labelledBy', () => {
|
329 | render(
|
330 | <Modal isOpen toggle={toggle} labelledBy="myModalTitle">
|
331 | Yo!
|
332 | </Modal>,
|
333 | );
|
334 |
|
335 | expect(
|
336 | document
|
337 | .getElementsByClassName('modal')[0]
|
338 | .getAttribute('aria-labelledby'),
|
339 | ).toBe('myModalTitle');
|
340 | });
|
341 |
|
342 | it('should not render modal when isOpen is false', () => {
|
343 | render(
|
344 | <Modal isOpen={false} toggle={toggle}>
|
345 | Yo!
|
346 | </Modal>,
|
347 | );
|
348 |
|
349 | expect(document.getElementsByClassName('modal').length).toBe(0);
|
350 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(0);
|
351 | });
|
352 |
|
353 | it('should toggle modal', () => {
|
354 | const { rerender } = render(
|
355 | <Modal isOpen={false} toggle={toggle}>
|
356 | Yo!
|
357 | </Modal>,
|
358 | );
|
359 |
|
360 | expect(document.getElementsByClassName('modal').length).toBe(0);
|
361 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(0);
|
362 |
|
363 | rerender(
|
364 | <Modal isOpen toggle={toggle}>
|
365 | Yo!
|
366 | </Modal>,
|
367 | );
|
368 |
|
369 | expect(document.getElementsByClassName('modal').length).toBe(1);
|
370 | expect(document.getElementsByClassName('modal-backdrop').length).toBe(1);
|
371 | });
|
372 |
|
373 | it('should call onClosed & onOpened', () => {
|
374 | const onOpened = jest.fn();
|
375 | const onClosed = jest.fn();
|
376 | const { rerender } = render(
|
377 | <Modal
|
378 | isOpen={false}
|
379 | onOpened={onOpened}
|
380 | onClosed={onClosed}
|
381 | toggle={toggle}
|
382 | >
|
383 | Yo!
|
384 | </Modal>,
|
385 | );
|
386 |
|
387 | expect(onOpened).not.toHaveBeenCalled();
|
388 | expect(onClosed).not.toHaveBeenCalled();
|
389 | rerender(
|
390 | <Modal isOpen onOpened={onOpened} onClosed={onClosed} toggle={toggle}>
|
391 | Yo!
|
392 | </Modal>,
|
393 | );
|
394 | jest.advanceTimersByTime(300);
|
395 | expect(onOpened).toHaveBeenCalledTimes(1);
|
396 |
|
397 | rerender(
|
398 | <Modal
|
399 | isOpen={false}
|
400 | onOpened={onOpened}
|
401 | onClosed={onClosed}
|
402 | toggle={toggle}
|
403 | >
|
404 | Yo!
|
405 | </Modal>,
|
406 | );
|
407 |
|
408 | jest.advanceTimersByTime(300);
|
409 | expect(onOpened).toHaveBeenCalledTimes(1);
|
410 | expect(onClosed).toHaveBeenCalledTimes(1);
|
411 | });
|
412 |
|
413 | it('should call onClosed & onOpened when fade={false}', () => {
|
414 | const onOpened = jest.fn();
|
415 | const onClosed = jest.fn();
|
416 | const { rerender } = render(
|
417 | <Modal
|
418 | isOpen={false}
|
419 | onOpened={onOpened}
|
420 | onClosed={onClosed}
|
421 | toggle={toggle}
|
422 | fade={false}
|
423 | >
|
424 | Yo!
|
425 | </Modal>,
|
426 | );
|
427 |
|
428 | expect(onOpened).not.toHaveBeenCalled();
|
429 | expect(onClosed).not.toHaveBeenCalled();
|
430 |
|
431 | rerender(
|
432 | <Modal
|
433 | isOpen
|
434 | onOpened={onOpened}
|
435 | onClosed={onClosed}
|
436 | toggle={toggle}
|
437 | fade={false}
|
438 | >
|
439 | Yo!
|
440 | </Modal>,
|
441 | );
|
442 | jest.advanceTimersByTime(1);
|
443 |
|
444 | expect(onOpened).toHaveBeenCalledTimes(1);
|
445 | expect(onClosed).not.toHaveBeenCalled();
|
446 |
|
447 | rerender(
|
448 | <Modal
|
449 | isOpen={false}
|
450 | onOpened={onOpened}
|
451 | onClosed={onClosed}
|
452 | toggle={toggle}
|
453 | fade={false}
|
454 | >
|
455 | Yo!
|
456 | </Modal>,
|
457 | );
|
458 | jest.advanceTimersByTime(1);
|
459 |
|
460 | expect(onClosed).toHaveBeenCalledTimes(1);
|
461 | expect(onOpened).toHaveBeenCalledTimes(1);
|
462 | });
|
463 |
|
464 | it('should call toggle when escape key pressed and not for enter key', () => {
|
465 | const toggle = jest.fn();
|
466 | render(
|
467 | <Modal isOpen toggle={toggle}>
|
468 | Yo!
|
469 | </Modal>,
|
470 | );
|
471 |
|
472 | user.keyboard('{enter}');
|
473 | expect(toggle).not.toHaveBeenCalled();
|
474 |
|
475 | user.keyboard('{esc}');
|
476 | expect(toggle).toHaveBeenCalled();
|
477 | });
|
478 |
|
479 | it('should not call toggle when escape key pressed when keyboard is false', () => {
|
480 | const toggle = jest.fn();
|
481 | render(
|
482 | <Modal isOpen toggle={toggle} keyboard={false}>
|
483 | Yo!
|
484 | </Modal>,
|
485 | );
|
486 |
|
487 | user.keyboard('{esc}');
|
488 | expect(toggle).not.toHaveBeenCalled();
|
489 | });
|
490 |
|
491 | it('should call toggle when clicking backdrop', () => {
|
492 | const toggle = jest.fn();
|
493 | render(
|
494 | <Modal isOpen toggle={toggle}>
|
495 | <button id="clicker">Does Nothing</button>
|
496 | </Modal>,
|
497 | );
|
498 |
|
499 | user.click(screen.getByText(/does nothing/i));
|
500 | expect(toggle).not.toHaveBeenCalled();
|
501 |
|
502 | user.click(document.body.getElementsByClassName('modal')[0]);
|
503 | expect(toggle).toHaveBeenCalled();
|
504 | });
|
505 |
|
506 | it('should not call toggle when clicking backdrop and backdrop is "static"', () => {
|
507 | const toggle = jest.fn();
|
508 | render(
|
509 | <Modal isOpen toggle={toggle} backdrop="static">
|
510 | <button id="clicker">Does Nothing</button>
|
511 | </Modal>,
|
512 | );
|
513 |
|
514 | user.click(document.getElementsByClassName('modal-backdrop')[0]);
|
515 | expect(toggle).not.toHaveBeenCalled();
|
516 | });
|
517 |
|
518 | it('should not call toggle when escape key pressed and backdrop is "static" and keyboard=false', () => {
|
519 | const toggle = jest.fn();
|
520 | render(
|
521 | <Modal isOpen toggle={toggle} backdrop="static" keyboard={false}>
|
522 | Yo!
|
523 | </Modal>,
|
524 | );
|
525 |
|
526 | user.keyboard('{esc}');
|
527 | expect(toggle).not.toHaveBeenCalled();
|
528 | });
|
529 |
|
530 | it('should call toggle when escape key pressed and backdrop is "static" and keyboard=true', () => {
|
531 | const toggle = jest.fn();
|
532 | render(
|
533 | <Modal isOpen toggle={toggle} backdrop="static" keyboard>
|
534 | <button id="clicker">Does Nothing</button>
|
535 | </Modal>,
|
536 | );
|
537 |
|
538 | user.keyboard('{esc}');
|
539 | expect(toggle).toHaveBeenCalled();
|
540 | });
|
541 |
|
542 | it('should animate when backdrop is "static" and escape key pressed and keyboard=false', () => {
|
543 | render(
|
544 | <Modal
|
545 | isOpen
|
546 | toggle={toggle}
|
547 | backdrop="static"
|
548 | keyboard={false}
|
549 | data-testid="mandalorian"
|
550 | >
|
551 | <button id="clicker">Does Nothing</button>
|
552 | </Modal>,
|
553 | );
|
554 |
|
555 | user.keyboard('{esc}');
|
556 |
|
557 | expect(screen.getByTestId('mandalorian').parentElement).toHaveClass(
|
558 | 'modal-static',
|
559 | );
|
560 |
|
561 | jest.advanceTimersByTime(300);
|
562 |
|
563 | expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass(
|
564 | 'modal-static',
|
565 | );
|
566 | });
|
567 |
|
568 | it('should animate when backdrop is "static" and backdrop is clicked', () => {
|
569 | render(
|
570 | <Modal isOpen toggle={toggle} backdrop="static" data-testid="mandalorian">
|
571 | <button id="clicker">Does Nothing</button>
|
572 | </Modal>,
|
573 | );
|
574 |
|
575 | user.click(document.getElementsByClassName('modal')[0]);
|
576 |
|
577 | expect(screen.getByTestId('mandalorian').parentElement).toHaveClass(
|
578 | 'modal-static',
|
579 | );
|
580 |
|
581 | jest.advanceTimersByTime(300);
|
582 |
|
583 | expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass(
|
584 | 'modal-static',
|
585 | );
|
586 | });
|
587 |
|
588 | it('should not animate when backdrop is "static" and modal is clicked', () => {
|
589 | render(
|
590 | <Modal isOpen toggle={toggle} backdrop="static" data-testid="mandalorian">
|
591 | <button id="clicker">Does Nothing</button>
|
592 | </Modal>,
|
593 | );
|
594 |
|
595 | user.click(document.getElementsByClassName('modal-dialog')[0]);
|
596 |
|
597 | expect(screen.getByTestId('mandalorian').parentElement).not.toHaveClass(
|
598 | 'modal-static',
|
599 | );
|
600 | });
|
601 |
|
602 | it('should destroy this._element', () => {
|
603 | const { rerender } = render(
|
604 | <Modal isOpen toggle={toggle} wrapClassName="weird-class">
|
605 | <button id="clicker">Does Nothing</button>
|
606 | </Modal>,
|
607 | );
|
608 |
|
609 | const element =
|
610 | document.getElementsByClassName('weird-class')[0].parentElement;
|
611 | expect(element).toBeInTheDocument();
|
612 |
|
613 | rerender(
|
614 | <Modal isOpen={false} toggle={toggle} wrapClassName="weird-class">
|
615 | <button id="clicker">Does Nothing</button>
|
616 | </Modal>,
|
617 | );
|
618 | jest.advanceTimersByTime(300);
|
619 | expect(element).not.toBeInTheDocument();
|
620 | });
|
621 |
|
622 | it('should destroy this._element when unmountOnClose prop set to true', () => {
|
623 | const { rerender } = render(
|
624 | <Modal isOpen toggle={toggle} unmountOnClose wrapClassName="weird-class">
|
625 | <button id="clicker">Does Nothing</button>
|
626 | </Modal>,
|
627 | );
|
628 |
|
629 | const element =
|
630 | document.getElementsByClassName('weird-class')[0].parentElement;
|
631 | expect(element).toBeInTheDocument();
|
632 |
|
633 | rerender(
|
634 | <Modal
|
635 | isOpen={false}
|
636 | toggle={toggle}
|
637 | unmountOnClose
|
638 | wrapClassName="weird-class"
|
639 | >
|
640 | <button id="clicker">Does Nothing</button>
|
641 | </Modal>,
|
642 | );
|
643 | jest.advanceTimersByTime(300);
|
644 | expect(element).not.toBeInTheDocument();
|
645 | });
|
646 |
|
647 | it('should not destroy this._element when unmountOnClose prop set to false', () => {
|
648 | const { rerender } = render(
|
649 | <Modal
|
650 | isOpen
|
651 | toggle={toggle}
|
652 | unmountOnClose={false}
|
653 | wrapClassName="weird-class"
|
654 | >
|
655 | <button id="clicker">Does Nothing</button>
|
656 | </Modal>,
|
657 | );
|
658 |
|
659 | const element =
|
660 | document.getElementsByClassName('weird-class')[0].parentElement;
|
661 | expect(element).toBeInTheDocument();
|
662 |
|
663 | rerender(
|
664 | <Modal
|
665 | isOpen={false}
|
666 | toggle={toggle}
|
667 | unmountOnClose={false}
|
668 | wrapClassName="weird-class"
|
669 | >
|
670 | <button id="clicker">Does Nothing</button>
|
671 | </Modal>,
|
672 | );
|
673 | expect(element).toBeInTheDocument();
|
674 | });
|
675 |
|
676 | it('should destroy this._element on unmount', () => {
|
677 | const { unmount } = render(
|
678 | <Modal isOpen toggle={toggle} wrapClassName="weird-class">
|
679 | <button id="clicker">Does Nothing</button>
|
680 | </Modal>,
|
681 | );
|
682 | unmount();
|
683 | jest.advanceTimersByTime(300);
|
684 | expect(document.getElementsByClassName('modal').length).toBe(0);
|
685 | });
|
686 |
|
687 | it('should render nested modals', () => {
|
688 | const { unmount } = render(
|
689 | <Modal isOpen toggle={toggle}>
|
690 | <ModalBody>
|
691 | <Modal isOpen toggle={() => {}}>
|
692 | Yo!
|
693 | </Modal>
|
694 | </ModalBody>
|
695 | </Modal>,
|
696 | );
|
697 |
|
698 | expect(document.getElementsByClassName('modal-dialog').length).toBe(2);
|
699 | expect(document.body.className).toBe('modal-open');
|
700 |
|
701 | unmount();
|
702 |
|
703 | expect(document.getElementsByClassName('modal-dialog').length).toBe(0);
|
704 | });
|
705 |
|
706 | it('should remove exactly modal-open class from body', () => {
|
707 |
|
708 | document.body.className = 'my-modal-opened';
|
709 |
|
710 | const { rerender } = render(
|
711 | <Modal isOpen={false} toggle={toggle}>
|
712 | Yo!
|
713 | </Modal>,
|
714 | );
|
715 |
|
716 | expect(document.body.className).toBe('my-modal-opened');
|
717 |
|
718 | rerender(
|
719 | <Modal isOpen toggle={toggle}>
|
720 | Yo!
|
721 | </Modal>,
|
722 | );
|
723 |
|
724 | expect(document.body.className).toBe('my-modal-opened modal-open');
|
725 |
|
726 |
|
727 | document.body.className += ' modal-opened';
|
728 | expect(document.body.className).toBe(
|
729 | 'my-modal-opened modal-open modal-opened',
|
730 | );
|
731 |
|
732 | rerender(
|
733 | <Modal isOpen={false} toggle={toggle}>
|
734 | Yo!
|
735 | </Modal>,
|
736 | );
|
737 |
|
738 | jest.advanceTimersByTime(300);
|
739 | expect(document.body.className).toBe('my-modal-opened modal-opened');
|
740 | });
|
741 |
|
742 | it('should call onEnter & onExit props if provided', () => {
|
743 | const onEnter = jest.fn();
|
744 | const onExit = jest.fn();
|
745 | const { rerender, unmount } = render(
|
746 | <Modal isOpen={false} onEnter={onEnter} onExit={onExit} toggle={toggle}>
|
747 | Yo!
|
748 | </Modal>,
|
749 | );
|
750 |
|
751 | expect(onEnter).toHaveBeenCalled();
|
752 | expect(onExit).not.toHaveBeenCalled();
|
753 |
|
754 | onEnter.mockReset();
|
755 | onExit.mockReset();
|
756 |
|
757 | rerender(
|
758 | <Modal isOpen onEnter={onEnter} onExit={onExit} toggle={toggle}>
|
759 | Yo!
|
760 | </Modal>,
|
761 | );
|
762 | expect(onEnter).not.toHaveBeenCalled();
|
763 | expect(onExit).not.toHaveBeenCalled();
|
764 |
|
765 | onEnter.mockReset();
|
766 | onExit.mockReset();
|
767 |
|
768 | rerender(
|
769 | <Modal isOpen={false} onEnter={onEnter} onExit={onExit} toggle={toggle}>
|
770 | Yo!
|
771 | </Modal>,
|
772 | );
|
773 | unmount();
|
774 | expect(onEnter).not.toHaveBeenCalled();
|
775 | expect(onExit).toHaveBeenCalled();
|
776 | });
|
777 |
|
778 | it('should update element z index when prop changes', () => {
|
779 | const { rerender } = render(
|
780 | <Modal isOpen zIndex={0} wrapClassName="sandman">
|
781 | Yo!
|
782 | </Modal>,
|
783 | );
|
784 |
|
785 | expect(
|
786 | document.getElementsByClassName('sandman')[0].parentElement.style.zIndex,
|
787 | ).toBe('0');
|
788 |
|
789 | rerender(
|
790 | <Modal isOpen zIndex={1} wrapClassName="sandman">
|
791 | Yo!
|
792 | </Modal>,
|
793 | );
|
794 |
|
795 | expect(
|
796 | document.getElementsByClassName('sandman')[0].parentElement.style.zIndex,
|
797 | ).toBe('1');
|
798 | });
|
799 |
|
800 | it('should allow focus on only focusable elements and tab through them', () => {
|
801 | render(
|
802 | <Modal isOpen toggle={toggle}>
|
803 | <ModalHeader toggle={toggle}>Modal title</ModalHeader>
|
804 | <ModalBody>
|
805 | <a alt="test" href="/">
|
806 | First Test
|
807 | </a>
|
808 | <map name="test">
|
809 | <area alt="test" href="/" coords="200,5,200,30" />
|
810 | </map>
|
811 | <input type="text" aria-label="test text input" />
|
812 | <input type="hidden" />
|
813 | <input type="text" disabled value="Test" />
|
814 | <select name="test" id="select_test">
|
815 | <option>Second item</option>
|
816 | </select>
|
817 | <select name="test" id="select_test_disabled" disabled>
|
818 | <option>Third item</option>
|
819 | </select>
|
820 | <textarea
|
821 | name="textarea_test"
|
822 | id="textarea_test"
|
823 | cols="30"
|
824 | rows="10"
|
825 | aria-label="test text area"
|
826 | />
|
827 | <textarea
|
828 | name="textarea_test_disabled"
|
829 | id="textarea_test_disabled"
|
830 | cols="30"
|
831 | rows="10"
|
832 | disabled
|
833 | />
|
834 | <object>Test</object>
|
835 | <span tabIndex="0">test tab index</span>
|
836 | </ModalBody>
|
837 | <ModalFooter>
|
838 | <Button disabled color="primary" onClick={toggle}>
|
839 | Do Something
|
840 | </Button>{' '}
|
841 | <Button color="secondary" onClick={toggle}>
|
842 | Cancel
|
843 | </Button>
|
844 | </ModalFooter>
|
845 | </Modal>,
|
846 | );
|
847 |
|
848 | user.tab();
|
849 | expect(screen.getByLabelText(/close/i)).toHaveFocus();
|
850 | user.tab();
|
851 | expect(screen.getByText(/first test/i)).toHaveFocus();
|
852 | user.tab();
|
853 | expect(screen.getByLabelText(/test text input/i)).toHaveFocus();
|
854 | user.tab();
|
855 | expect(screen.getByText(/second item/i).parentElement).toHaveFocus();
|
856 | user.tab();
|
857 | expect(screen.getByLabelText(/test text area/i)).toHaveFocus();
|
858 | user.tab();
|
859 | expect(screen.getByText(/test tab index/i)).toHaveFocus();
|
860 | user.tab();
|
861 | expect(screen.getByText(/cancel/i)).toHaveFocus();
|
862 | user.tab();
|
863 | expect(screen.getByLabelText(/close/i)).toHaveFocus();
|
864 | });
|
865 |
|
866 | it('should return the focus to the last focused element before the modal has opened', () => {
|
867 | const { rerender } = render(
|
868 | <>
|
869 | <button className="focus">Focused</button>
|
870 | <Modal isOpen={false}>
|
871 | <ModalBody>Whatever</ModalBody>
|
872 | </Modal>
|
873 | </>,
|
874 | );
|
875 |
|
876 | user.tab();
|
877 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
878 |
|
879 | rerender(
|
880 | <>
|
881 | <button className="focus">Focused</button>
|
882 | <Modal isOpen>
|
883 | <ModalBody>Whatever</ModalBody>
|
884 | </Modal>
|
885 | </>,
|
886 | );
|
887 |
|
888 | rerender(
|
889 | <>
|
890 | <button className="focus">Focused</button>
|
891 | <Modal isOpen={false}>
|
892 | <ModalBody>Whatever</ModalBody>
|
893 | </Modal>
|
894 | </>,
|
895 | );
|
896 |
|
897 | jest.runAllTimers();
|
898 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
899 | });
|
900 |
|
901 | it('should not return the focus to the last focused element before the modal has opened when "returnFocusAfterClose" is false', () => {
|
902 | const { rerender } = render(
|
903 | <>
|
904 | <button className="focus">Focused</button>
|
905 | <Modal returnFocusAfterClose={false} isOpen={false}>
|
906 | <ModalBody>Whatever</ModalBody>
|
907 | </Modal>
|
908 | </>,
|
909 | );
|
910 |
|
911 | user.tab();
|
912 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
913 |
|
914 | rerender(
|
915 | <>
|
916 | <button className="focus">Focused</button>
|
917 | <Modal returnFocusAfterClose={false} isOpen>
|
918 | <ModalBody>Whatever</ModalBody>
|
919 | </Modal>
|
920 | </>,
|
921 | );
|
922 |
|
923 | rerender(
|
924 | <>
|
925 | <button className="focus">Focused</button>
|
926 | <Modal returnFocusAfterClose={false} isOpen={false}>
|
927 | <ModalBody>Whatever</ModalBody>
|
928 | </Modal>
|
929 | </>,
|
930 | );
|
931 |
|
932 | jest.runAllTimers();
|
933 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
934 | });
|
935 |
|
936 | it('should return the focus to the last focused element before the modal has opened when "unmountOnClose" is false', () => {
|
937 | const { rerender } = render(
|
938 | <>
|
939 | <button className="focus">Focused</button>
|
940 | <Modal unmountOnClose={false} isOpen={false}>
|
941 | <ModalBody>Whatever</ModalBody>
|
942 | </Modal>
|
943 | </>,
|
944 | );
|
945 |
|
946 | user.tab();
|
947 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
948 |
|
949 | rerender(
|
950 | <>
|
951 | <button className="focus">Focused</button>
|
952 | <Modal unmountOnClose={false} isOpen>
|
953 | <ModalBody>Whatever</ModalBody>
|
954 | </Modal>
|
955 | </>,
|
956 | );
|
957 |
|
958 | rerender(
|
959 | <>
|
960 | <button className="focus">Focused</button>
|
961 | <Modal unmountOnClose={false} isOpen={false}>
|
962 | <ModalBody>Whatever</ModalBody>
|
963 | </Modal>
|
964 | </>,
|
965 | );
|
966 |
|
967 | jest.runAllTimers();
|
968 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
969 | });
|
970 |
|
971 | it('should not return the focus to the last focused element before the modal has opened when "returnFocusAfterClose" is false and "unmountOnClose" is false', () => {
|
972 | const { rerender } = render(
|
973 | <>
|
974 | <button className="focus">Focused</button>
|
975 | <Modal
|
976 | unmountOnClose={false}
|
977 | returnFocusAfterClose={false}
|
978 | isOpen={false}
|
979 | >
|
980 | <ModalBody>Whatever</ModalBody>
|
981 | </Modal>
|
982 | </>,
|
983 | );
|
984 |
|
985 | user.tab();
|
986 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
987 |
|
988 | rerender(
|
989 | <>
|
990 | <button className="focus">Focused</button>
|
991 | <Modal unmountOnClose={false} returnFocusAfterClose={false} isOpen>
|
992 | <ModalBody>Whatever</ModalBody>
|
993 | </Modal>
|
994 | </>,
|
995 | );
|
996 |
|
997 | rerender(
|
998 | <>
|
999 | <button className="focus">Focused</button>
|
1000 | <Modal
|
1001 | unmountOnClose={false}
|
1002 | returnFocusAfterClose={false}
|
1003 | isOpen={false}
|
1004 | >
|
1005 | <ModalBody>Whatever</ModalBody>
|
1006 | </Modal>
|
1007 | </>,
|
1008 | );
|
1009 |
|
1010 | jest.runAllTimers();
|
1011 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
1012 | });
|
1013 |
|
1014 | it('should attach/detach trapFocus for dialogs', () => {
|
1015 | const addEventListener = jest.spyOn(document, 'addEventListener');
|
1016 | const removeEventListener = jest.spyOn(document, 'removeEventListener');
|
1017 |
|
1018 | const { unmount } = render(
|
1019 | <Modal isOpen>
|
1020 | <ModalBody>
|
1021 | <Button className="focus">focusable element</Button>
|
1022 | </ModalBody>
|
1023 | </Modal>,
|
1024 | );
|
1025 |
|
1026 | expect(addEventListener).toHaveBeenCalledTimes(1);
|
1027 | expect(addEventListener).toHaveBeenCalledWith(
|
1028 | 'focus',
|
1029 | expect.any(Function),
|
1030 | true,
|
1031 | );
|
1032 |
|
1033 | unmount();
|
1034 |
|
1035 | expect(removeEventListener).toHaveBeenCalledTimes(1);
|
1036 | expect(removeEventListener).toHaveBeenCalledWith(
|
1037 | 'focus',
|
1038 | expect.any(Function),
|
1039 | true,
|
1040 | );
|
1041 |
|
1042 | addEventListener.mockRestore();
|
1043 | removeEventListener.mockRestore();
|
1044 | });
|
1045 |
|
1046 | it('should trap focus inside the open dialog', () => {
|
1047 | const { rerender } = render(
|
1048 | <>
|
1049 | <Button className="first">Focused</Button>
|
1050 | <Modal isOpen={false} trapFocus>
|
1051 | <ModalBody>
|
1052 | Something else to see
|
1053 | <Button className="focus">focusable element</Button>
|
1054 | </ModalBody>
|
1055 | </Modal>
|
1056 | </>,
|
1057 | );
|
1058 |
|
1059 | screen.getByText(/focused/i).focus();
|
1060 |
|
1061 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
1062 |
|
1063 | rerender(
|
1064 | <>
|
1065 | <Button className="first">Focused</Button>
|
1066 | <Modal isOpen trapFocus data-testid="modal">
|
1067 | <ModalBody>
|
1068 | Something else to see
|
1069 | <Button className="focus">focusable element</Button>
|
1070 | </ModalBody>
|
1071 | </Modal>
|
1072 | </>,
|
1073 | );
|
1074 |
|
1075 | jest.runAllTimers();
|
1076 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
1077 |
|
1078 | expect(screen.getByTestId('modal').parentElement).toHaveFocus();
|
1079 |
|
1080 | user.tab();
|
1081 | expect(screen.getByText(/focusable element/i)).toHaveFocus();
|
1082 | user.tab();
|
1083 | expect(screen.getByText(/focusable element/i)).toHaveFocus();
|
1084 | });
|
1085 |
|
1086 | it('tab should focus on inside modal children for nested modal', () => {
|
1087 | render(
|
1088 | <Modal isOpen toggle={toggle}>
|
1089 | <ModalBody>
|
1090 | <Button className="b0" onClick={toggle}>
|
1091 | Cancel
|
1092 | </Button>
|
1093 | <Modal isOpen>
|
1094 | <ModalBody>
|
1095 | <Button className="b1">Click 1</Button>
|
1096 | </ModalBody>
|
1097 | </Modal>
|
1098 | </ModalBody>
|
1099 | </Modal>,
|
1100 | );
|
1101 |
|
1102 | user.tab();
|
1103 | expect(screen.getByText(/click 1/i)).toHaveFocus();
|
1104 |
|
1105 | user.tab();
|
1106 | expect(screen.getByText(/click 1/i)).toHaveFocus();
|
1107 | });
|
1108 |
|
1109 | it('works with strict mode', () => {
|
1110 | const spy = jest.spyOn(console, 'error');
|
1111 | render(
|
1112 | <React.StrictMode>
|
1113 | <Modal isOpen>Hello</Modal>
|
1114 | </React.StrictMode>,
|
1115 | );
|
1116 | expect(spy).not.toHaveBeenCalled();
|
1117 | });
|
1118 | });
|