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 { Offcanvas, OffcanvasBody, OffcanvasHeader, Button } from '..';
|
7 | import { testForCustomClass } from '../testUtils';
|
8 |
|
9 | describe('Offcanvas', () => {
|
10 | let toggle;
|
11 | beforeEach(() => {
|
12 | toggle = () => {};
|
13 | jest.useFakeTimers();
|
14 | });
|
15 |
|
16 | afterEach(() => {
|
17 | jest.clearAllTimers();
|
18 | document.body.removeAttribute('style');
|
19 | });
|
20 |
|
21 | it('should render offcanvas portal into DOM', () => {
|
22 | render(
|
23 | <Offcanvas isOpen toggle={toggle}>
|
24 | Yo!
|
25 | </Offcanvas>,
|
26 | );
|
27 |
|
28 | jest.advanceTimersByTime(300);
|
29 |
|
30 | expect(screen.getByText(/yo/i)).toBeInTheDocument();
|
31 | });
|
32 |
|
33 | it('should render with the class "offcanvas"', () => {
|
34 | render(
|
35 | <Offcanvas isOpen toggle={toggle}>
|
36 | Yo!
|
37 | </Offcanvas>,
|
38 | );
|
39 |
|
40 | expect(screen.getByText(/yo/i)).toHaveClass('offcanvas');
|
41 | });
|
42 |
|
43 | it('should render with the backdrop with the class "offcanvas-backdrop" by default', () => {
|
44 | render(
|
45 | <Offcanvas isOpen toggle={toggle}>
|
46 | Yo!
|
47 | </Offcanvas>,
|
48 | );
|
49 |
|
50 | expect(document.getElementsByClassName('offcanvas-backdrop')).toHaveLength(
|
51 | 1,
|
52 | );
|
53 | });
|
54 |
|
55 | it('should not render with the backdrop with the class "offcanvas-backdrop" when backdrop is "false"', () => {
|
56 | render(
|
57 | <Offcanvas isOpen toggle={toggle} backdrop={false}>
|
58 | Yo!
|
59 | </Offcanvas>,
|
60 | );
|
61 |
|
62 | expect(document.getElementsByClassName('offcanvas').length).toBe(1);
|
63 | expect(document.getElementsByClassName('offcanvas-backdrop').length).toBe(
|
64 | 0,
|
65 | );
|
66 | });
|
67 |
|
68 | it('should have custom class name if provided', () => {
|
69 | testForCustomClass(Offcanvas, { isOpen: true, toggle });
|
70 | });
|
71 |
|
72 | it('should render with additional props if provided', () => {
|
73 | render(
|
74 | <Offcanvas isOpen toggle={toggle} style={{ maxWidth: '95%' }}>
|
75 | Yo!
|
76 | </Offcanvas>,
|
77 | );
|
78 |
|
79 | expect(document.getElementsByClassName('offcanvas')[0].style.maxWidth).toBe(
|
80 | '95%',
|
81 | );
|
82 | });
|
83 |
|
84 | it('should render without fade transition if provided with fade={false}', () => {
|
85 | render(
|
86 | <Offcanvas
|
87 | isOpen
|
88 | toggle={toggle}
|
89 | fade={false}
|
90 | className="fadeless-offcanvas"
|
91 | >
|
92 | Howdy!
|
93 | </Offcanvas>,
|
94 | );
|
95 |
|
96 | expect(
|
97 | document.getElementsByClassName('fadeless-offcanvas')[0],
|
98 | ).not.toHaveClass('fade');
|
99 | });
|
100 |
|
101 | it('should render when expected when passed offcanvasTransition and backdropTransition props', () => {
|
102 | render(
|
103 | <Offcanvas
|
104 | isOpen
|
105 | toggle={toggle}
|
106 | offcanvasTransition={{ timeout: 2 }}
|
107 | backdropTransition={{ timeout: 10 }}
|
108 | className="custom-timeout-offcanvas"
|
109 | >
|
110 | Hello, world!
|
111 | </Offcanvas>,
|
112 | );
|
113 |
|
114 | expect(
|
115 | document.getElementsByClassName('custom-timeout-offcanvas')[0],
|
116 | ).toHaveClass('fade');
|
117 | expect(
|
118 | document.getElementsByClassName('custom-timeout-offcanvas')[0],
|
119 | ).not.toHaveClass('show');
|
120 | expect(
|
121 | document.getElementsByClassName('offcanvas-backdrop')[0],
|
122 | ).not.toHaveClass('show');
|
123 |
|
124 | jest.advanceTimersByTime(20);
|
125 | expect(
|
126 | document.getElementsByClassName('custom-timeout-offcanvas')[0],
|
127 | ).toHaveClass('show');
|
128 | expect(
|
129 | document.getElementsByClassName('offcanvas-backdrop')[0],
|
130 | ).toHaveClass('show');
|
131 | });
|
132 |
|
133 | it('should render with class "offcanvas-backdrop" and have custom class name if provided with backdropClassName', () => {
|
134 | render(
|
135 | <Offcanvas isOpen toggle={toggle} backdropClassName="my-custom-offcanvas">
|
136 | Yo!
|
137 | </Offcanvas>,
|
138 | );
|
139 |
|
140 | expect(
|
141 | document.getElementsByClassName(
|
142 | 'offcanvas-backdrop my-custom-offcanvas',
|
143 | )[0],
|
144 | ).toBeInTheDocument();
|
145 | });
|
146 |
|
147 | it('should render with the class "offcanvas-${direction}" when direction is passed', () => {
|
148 | render(
|
149 | <Offcanvas isOpen toggle={toggle} direction="top">
|
150 | Yo!
|
151 | </Offcanvas>,
|
152 | );
|
153 |
|
154 | expect(screen.getByText(/yo/i)).toHaveClass('offcanvas-top');
|
155 | });
|
156 |
|
157 | it('should render offcanvas when isOpen is true', () => {
|
158 | render(
|
159 | <Offcanvas isOpen toggle={toggle}>
|
160 | Yo!
|
161 | </Offcanvas>,
|
162 | );
|
163 |
|
164 | expect(screen.getByText(/yo/i)).toHaveClass('offcanvas');
|
165 | expect(document.getElementsByClassName('offcanvas-backdrop').length).toBe(
|
166 | 1,
|
167 | );
|
168 | });
|
169 |
|
170 | it('should render offcanvas with default role of "dialog"', () => {
|
171 | render(
|
172 | <Offcanvas isOpen toggle={toggle}>
|
173 | Yo!
|
174 | </Offcanvas>,
|
175 | );
|
176 | expect(screen.getByText(/yo/i).getAttribute('role')).toBe('dialog');
|
177 | });
|
178 |
|
179 | it('should render offcanvas with provided role', () => {
|
180 | render(
|
181 | <Offcanvas isOpen toggle={toggle} role="alert">
|
182 | Yo!
|
183 | </Offcanvas>,
|
184 | );
|
185 |
|
186 | expect(screen.getByText(/yo/i).getAttribute('role')).toBe('alert');
|
187 | });
|
188 |
|
189 | it('should render offcanvas with aria-labelledby provided labelledBy', () => {
|
190 | render(
|
191 | <Offcanvas isOpen toggle={toggle} labelledBy="myOffcanvasTitle">
|
192 | Yo!
|
193 | </Offcanvas>,
|
194 | );
|
195 |
|
196 | expect(screen.getByText(/yo/i).getAttribute('aria-labelledby')).toBe(
|
197 | 'myOffcanvasTitle',
|
198 | );
|
199 | });
|
200 |
|
201 | it('should not render offcanvas when isOpen is false', () => {
|
202 | render(
|
203 | <Offcanvas isOpen={false} toggle={toggle}>
|
204 | Yo!
|
205 | </Offcanvas>,
|
206 | );
|
207 |
|
208 | expect(screen.queryByText(/yo/i)).not.toBeInTheDocument();
|
209 | });
|
210 |
|
211 | it('should toggle offcanvas', () => {
|
212 | const { rerender } = render(
|
213 | <Offcanvas isOpen={false} toggle={toggle}>
|
214 | Yo!
|
215 | </Offcanvas>,
|
216 | );
|
217 |
|
218 | expect(screen.queryByText(/yo/i)).not.toBeInTheDocument();
|
219 |
|
220 | rerender(
|
221 | <Offcanvas isOpen toggle={toggle}>
|
222 | Yo!
|
223 | </Offcanvas>,
|
224 | );
|
225 |
|
226 | expect(screen.queryByText(/yo/i)).toBeInTheDocument();
|
227 |
|
228 | rerender(
|
229 | <Offcanvas isOpen toggle={toggle}>
|
230 | Yo!
|
231 | </Offcanvas>,
|
232 | );
|
233 |
|
234 | expect(screen.queryByText(/yo/i)).toBeInTheDocument();
|
235 | });
|
236 |
|
237 | it('should call onClosed & onOpened', () => {
|
238 | const onOpened = jest.fn();
|
239 | const onClosed = jest.fn();
|
240 | const { rerender } = render(
|
241 | <Offcanvas
|
242 | isOpen={false}
|
243 | onOpened={onOpened}
|
244 | onClosed={onClosed}
|
245 | toggle={toggle}
|
246 | >
|
247 | Yo!
|
248 | </Offcanvas>,
|
249 | );
|
250 |
|
251 | expect(onOpened).not.toHaveBeenCalled();
|
252 | expect(onClosed).not.toHaveBeenCalled();
|
253 |
|
254 | rerender(
|
255 | <Offcanvas isOpen onOpened={onOpened} onClosed={onClosed} toggle={toggle}>
|
256 | Yo!
|
257 | </Offcanvas>,
|
258 | );
|
259 |
|
260 | jest.advanceTimersByTime(300);
|
261 |
|
262 | expect(onOpened).toHaveBeenCalledTimes(1);
|
263 | expect(onClosed).not.toHaveBeenCalled();
|
264 |
|
265 | onOpened.mockClear();
|
266 | onClosed.mockClear();
|
267 |
|
268 | rerender(
|
269 | <Offcanvas
|
270 | isOpen={false}
|
271 | onOpened={onOpened}
|
272 | onClosed={onClosed}
|
273 | toggle={toggle}
|
274 | >
|
275 | Yo!
|
276 | </Offcanvas>,
|
277 | );
|
278 |
|
279 | jest.advanceTimersByTime(300);
|
280 |
|
281 | expect(onClosed).toHaveBeenCalledTimes(1);
|
282 | expect(onOpened).not.toHaveBeenCalled();
|
283 | });
|
284 |
|
285 | it('should call onClosed & onOpened when fade={false}', () => {
|
286 | const onOpened = jest.fn();
|
287 | const onClosed = jest.fn();
|
288 | const { rerender } = render(
|
289 | <Offcanvas
|
290 | isOpen={false}
|
291 | onOpened={onOpened}
|
292 | onClosed={onClosed}
|
293 | toggle={toggle}
|
294 | fade={false}
|
295 | >
|
296 | Yo!
|
297 | </Offcanvas>,
|
298 | );
|
299 |
|
300 | expect(onOpened).not.toHaveBeenCalled();
|
301 | expect(onClosed).not.toHaveBeenCalled();
|
302 |
|
303 | rerender(
|
304 | <Offcanvas
|
305 | isOpen
|
306 | onOpened={onOpened}
|
307 | onClosed={onClosed}
|
308 | toggle={toggle}
|
309 | fade={false}
|
310 | >
|
311 | Yo!
|
312 | </Offcanvas>,
|
313 | );
|
314 |
|
315 | jest.advanceTimersByTime(300);
|
316 |
|
317 | expect(onOpened).toHaveBeenCalledTimes(1);
|
318 | expect(onClosed).not.toHaveBeenCalled();
|
319 |
|
320 | onOpened.mockClear();
|
321 | onClosed.mockClear();
|
322 |
|
323 | rerender(
|
324 | <Offcanvas
|
325 | isOpen={false}
|
326 | onOpened={onOpened}
|
327 | onClosed={onClosed}
|
328 | toggle={toggle}
|
329 | fade={false}
|
330 | >
|
331 | Yo!
|
332 | </Offcanvas>,
|
333 | );
|
334 |
|
335 | jest.advanceTimersByTime(300);
|
336 |
|
337 | expect(onClosed).toHaveBeenCalledTimes(1);
|
338 | expect(onOpened).not.toHaveBeenCalled();
|
339 | });
|
340 |
|
341 | it('should call toggle when escape key pressed', () => {
|
342 | const toggle = jest.fn();
|
343 | render(
|
344 | <Offcanvas isOpen toggle={toggle}>
|
345 | Yo!
|
346 | </Offcanvas>,
|
347 | );
|
348 |
|
349 | user.keyboard('{esc}');
|
350 | expect(toggle).toHaveBeenCalled();
|
351 | });
|
352 |
|
353 | it('should not call toggle when escape key pressed when keyboard is false', () => {
|
354 | const toggle = jest.fn();
|
355 | render(
|
356 | <Offcanvas isOpen toggle={toggle} keyboard={false}>
|
357 | Yo!
|
358 | </Offcanvas>,
|
359 | );
|
360 |
|
361 | user.keyboard('{esc}');
|
362 | expect(toggle).not.toHaveBeenCalled();
|
363 | });
|
364 |
|
365 | it('should call toggle when clicking backdrop', () => {
|
366 | const toggle = jest.fn();
|
367 | render(
|
368 | <Offcanvas isOpen toggle={toggle}>
|
369 | <button id="clicker">Does Nothing</button>
|
370 | </Offcanvas>,
|
371 | );
|
372 |
|
373 | user.click(screen.getByText(/does nothing/i));
|
374 |
|
375 | expect(toggle).not.toHaveBeenCalled();
|
376 |
|
377 | user.click(document.getElementsByClassName('offcanvas-backdrop')[0]);
|
378 |
|
379 | expect(toggle).toHaveBeenCalled();
|
380 | });
|
381 |
|
382 | it('should call toggle when clicking backdrop when fade is false', () => {
|
383 | const toggle = jest.fn();
|
384 | render(
|
385 | <Offcanvas isOpen toggle={toggle} fade={false}>
|
386 | <button id="clicker">Does Nothing</button>
|
387 | </Offcanvas>,
|
388 | );
|
389 |
|
390 | user.click(screen.getByText(/does nothing/i));
|
391 |
|
392 | expect(toggle).not.toHaveBeenCalled();
|
393 |
|
394 | user.click(document.getElementsByClassName('offcanvas-backdrop')[0]);
|
395 |
|
396 | expect(toggle).toHaveBeenCalled();
|
397 | });
|
398 |
|
399 | it('should destroy this._element', () => {
|
400 | const { rerender } = render(
|
401 | <Offcanvas isOpen toggle={toggle}>
|
402 | thor and dr.johns
|
403 | </Offcanvas>,
|
404 | );
|
405 |
|
406 | const element = screen.getByText(/thor and dr.johns/i);
|
407 | expect(element).toBeInTheDocument();
|
408 |
|
409 | rerender(
|
410 | <Offcanvas isOpen={false} toggle={toggle}>
|
411 | thor and dr.johns
|
412 | </Offcanvas>,
|
413 | );
|
414 | jest.advanceTimersByTime(300);
|
415 | expect(element).not.toBeInTheDocument();
|
416 | });
|
417 |
|
418 | it('should destroy this._element when unmountOnClose prop set to true', () => {
|
419 | const { rerender } = render(
|
420 | <Offcanvas isOpen toggle={toggle} unmountOnClose>
|
421 | thor and dr.johns
|
422 | </Offcanvas>,
|
423 | );
|
424 |
|
425 | const element = screen.getByText(/thor and dr.johns/i);
|
426 | expect(element).toBeInTheDocument();
|
427 |
|
428 | rerender(
|
429 | <Offcanvas isOpen={false} toggle={toggle} unmountOnClose>
|
430 | thor and dr.johns
|
431 | </Offcanvas>,
|
432 | );
|
433 | jest.advanceTimersByTime(300);
|
434 | expect(element).not.toBeInTheDocument();
|
435 | });
|
436 |
|
437 | it('should not destroy this._element when unmountOnClose prop set to false', () => {
|
438 | const { rerender } = render(
|
439 | <Offcanvas isOpen toggle={toggle} unmountOnClose={false}>
|
440 | thor and dr.johns
|
441 | </Offcanvas>,
|
442 | );
|
443 |
|
444 | const element = screen.getByText(/thor and dr.johns/i);
|
445 | expect(element).toBeInTheDocument();
|
446 |
|
447 | rerender(
|
448 | <Offcanvas isOpen={false} toggle={toggle} unmountOnClose={false}>
|
449 | thor and dr.johns
|
450 | </Offcanvas>,
|
451 | );
|
452 | jest.advanceTimersByTime(300);
|
453 | expect(element).toBeInTheDocument();
|
454 | });
|
455 |
|
456 | it('should destroy this._element on unmount', () => {
|
457 | const { unmount } = render(
|
458 | <Offcanvas isOpen toggle={toggle}>
|
459 | thor and dr.johns
|
460 | </Offcanvas>,
|
461 | );
|
462 |
|
463 | const element = screen.getByText(/thor and dr.johns/i);
|
464 | expect(element).toBeInTheDocument();
|
465 |
|
466 | unmount();
|
467 | jest.advanceTimersByTime(300);
|
468 | expect(element).not.toBeInTheDocument();
|
469 | });
|
470 |
|
471 | it('should remove exactly visibility styles from body', () => {
|
472 |
|
473 | document.body.style.background = 'blue';
|
474 |
|
475 | const { rerender } = render(
|
476 | <Offcanvas isOpen={false} toggle={toggle}>
|
477 | Yo!
|
478 | </Offcanvas>,
|
479 | );
|
480 |
|
481 |
|
482 | jest.advanceTimersByTime(300);
|
483 | expect(document.body.style.background).toBe('blue');
|
484 | expect(document.body.style.overflow).toBe('');
|
485 |
|
486 | rerender(
|
487 | <Offcanvas isOpen toggle={toggle}>
|
488 | Yo!
|
489 | </Offcanvas>,
|
490 | );
|
491 |
|
492 |
|
493 | jest.advanceTimersByTime(300);
|
494 | expect(document.body.style.background).toBe('blue');
|
495 | expect(document.body.style.overflow).toBe('hidden');
|
496 |
|
497 |
|
498 |
|
499 | document.body.style.color = 'red';
|
500 | expect(document.body.style.background).toBe('blue');
|
501 | expect(document.body.style.color).toBe('red');
|
502 | expect(document.body.style.overflow).toBe('hidden');
|
503 |
|
504 | rerender(
|
505 | <Offcanvas isOpen={false} toggle={toggle}>
|
506 | Yo!
|
507 | </Offcanvas>,
|
508 | );
|
509 |
|
510 |
|
511 | jest.advanceTimersByTime(301);
|
512 | expect(document.body.style.background).toBe('blue');
|
513 | expect(document.body.style.color).toBe('red');
|
514 | expect(document.body.style.overflow).toBe('');
|
515 | });
|
516 |
|
517 | it('should call onEnter & onExit props if provided', () => {
|
518 | const onEnter = jest.fn();
|
519 | const onExit = jest.fn();
|
520 | const { rerender, unmount } = render(
|
521 | <Offcanvas
|
522 | isOpen={false}
|
523 | onEnter={onEnter}
|
524 | onExit={onExit}
|
525 | toggle={toggle}
|
526 | >
|
527 | Yo!
|
528 | </Offcanvas>,
|
529 | );
|
530 |
|
531 | expect(onEnter).toHaveBeenCalled();
|
532 | expect(onExit).not.toHaveBeenCalled();
|
533 |
|
534 | onEnter.mockReset();
|
535 | onExit.mockReset();
|
536 |
|
537 | rerender(
|
538 | <Offcanvas isOpen onEnter={onEnter} onExit={onExit} toggle={toggle}>
|
539 | Yo!
|
540 | </Offcanvas>,
|
541 | );
|
542 | jest.advanceTimersByTime(300);
|
543 |
|
544 | expect(onEnter).not.toHaveBeenCalled();
|
545 | expect(onExit).not.toHaveBeenCalled();
|
546 |
|
547 | onEnter.mockReset();
|
548 | onExit.mockReset();
|
549 |
|
550 | unmount();
|
551 | expect(onEnter).not.toHaveBeenCalled();
|
552 | expect(onExit).toHaveBeenCalled();
|
553 | });
|
554 |
|
555 | it('should update element z index when prop changes', () => {
|
556 | const { rerender } = render(
|
557 | <Offcanvas isOpen zIndex={0}>
|
558 | Yo!
|
559 | </Offcanvas>,
|
560 | );
|
561 | expect(screen.getByText(/yo/i).parentElement).toHaveStyle('z-index: 0');
|
562 | rerender(
|
563 | <Offcanvas isOpen zIndex={1}>
|
564 | Yo!
|
565 | </Offcanvas>,
|
566 | );
|
567 | expect(screen.getByText(/yo/i).parentElement).toHaveStyle('z-index: 1');
|
568 | });
|
569 |
|
570 | it('should allow focus on only focusable elements', () => {
|
571 | render(
|
572 | <Offcanvas isOpen toggle={toggle}>
|
573 | <OffcanvasHeader toggle={toggle}>Offcanvas title</OffcanvasHeader>
|
574 | <OffcanvasBody>
|
575 | <a alt="test" href="/">
|
576 | First Test
|
577 | </a>
|
578 | <map name="test">
|
579 | <area alt="test" href="/" coords="200,5,200,30" />
|
580 | </map>
|
581 | <input type="text" aria-label="test text input" />
|
582 | <input type="hidden" />
|
583 | <input type="text" disabled value="Test" />
|
584 | <select name="test" id="select_test">
|
585 | <option>Second item</option>
|
586 | </select>
|
587 | <select name="test" id="select_test_disabled" disabled>
|
588 | <option>Third item</option>
|
589 | </select>
|
590 | <textarea
|
591 | name="textarea_test"
|
592 | id="textarea_test"
|
593 | cols="30"
|
594 | rows="10"
|
595 | aria-label="test text area"
|
596 | />
|
597 | <textarea
|
598 | name="textarea_test_disabled"
|
599 | id="textarea_test_disabled"
|
600 | cols="30"
|
601 | rows="10"
|
602 | disabled
|
603 | />
|
604 | <object>Test</object>
|
605 | <span tabIndex="0">test tab index</span>
|
606 | </OffcanvasBody>
|
607 | </Offcanvas>,
|
608 | );
|
609 |
|
610 | user.tab();
|
611 | expect(screen.getByLabelText(/close/i)).toHaveFocus();
|
612 | user.tab();
|
613 | expect(screen.getByText(/first test/i)).toHaveFocus();
|
614 | user.tab();
|
615 | expect(screen.getByLabelText(/test text input/i)).toHaveFocus();
|
616 | user.tab();
|
617 | expect(screen.getByText(/second item/i).parentElement).toHaveFocus();
|
618 | user.tab();
|
619 | expect(screen.getByLabelText(/test text area/i)).toHaveFocus();
|
620 | user.tab();
|
621 | expect(screen.getByText(/test tab index/i)).toHaveFocus();
|
622 | user.tab();
|
623 | expect(screen.getByLabelText(/close/i)).toHaveFocus();
|
624 | });
|
625 |
|
626 | it('should return the focus to the last focused element before the offcanvas has opened', () => {
|
627 | const { rerender } = render(
|
628 | <>
|
629 | <button className="focus">Focused</button>
|
630 | <Offcanvas isOpen={false}>
|
631 | <OffcanvasBody>Whatever</OffcanvasBody>
|
632 | </Offcanvas>
|
633 | </>,
|
634 | );
|
635 |
|
636 | user.tab();
|
637 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
638 |
|
639 | rerender(
|
640 | <>
|
641 | <button className="focus">Focused</button>
|
642 | <Offcanvas isOpen>
|
643 | <OffcanvasBody>Whatever</OffcanvasBody>
|
644 | </Offcanvas>
|
645 | </>,
|
646 | );
|
647 |
|
648 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
649 |
|
650 | rerender(
|
651 | <>
|
652 | <button className="focus">Focused</button>
|
653 | <Offcanvas isOpen={false}>
|
654 | <OffcanvasBody>Whatever</OffcanvasBody>
|
655 | </Offcanvas>
|
656 | </>,
|
657 | );
|
658 |
|
659 | jest.runAllTimers();
|
660 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
661 | });
|
662 |
|
663 | it('should not return the focus to the last focused element before the offcanvas has opened when "returnFocusAfterClose" is false', () => {
|
664 | const { rerender } = render(
|
665 | <>
|
666 | <button className="focus">Focused</button>
|
667 | <Offcanvas returnFocusAfterClose={false} isOpen={false}>
|
668 | <OffcanvasBody>Whatever</OffcanvasBody>
|
669 | </Offcanvas>
|
670 | </>,
|
671 | );
|
672 |
|
673 | user.tab();
|
674 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
675 |
|
676 | rerender(
|
677 | <>
|
678 | <button className="focus">Focused</button>
|
679 | <Offcanvas returnFocusAfterClose={false} isOpen>
|
680 | <OffcanvasBody>Whatever</OffcanvasBody>
|
681 | </Offcanvas>
|
682 | </>,
|
683 | );
|
684 |
|
685 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
686 |
|
687 | rerender(
|
688 | <>
|
689 | <button className="focus">Focused</button>
|
690 | <Offcanvas returnFocusAfterClose={false} isOpen={false}>
|
691 | <OffcanvasBody>Whatever</OffcanvasBody>
|
692 | </Offcanvas>
|
693 | </>,
|
694 | );
|
695 |
|
696 | jest.runAllTimers();
|
697 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
698 | });
|
699 |
|
700 | it('should return the focus to the last focused element before the offcanvas has opened when "unmountOnClose" is false', () => {
|
701 | const { rerender } = render(
|
702 | <>
|
703 | <button className="focus">Focused</button>
|
704 | <Offcanvas unmountOnClose={false} isOpen={false}>
|
705 | <OffcanvasBody>Whatever</OffcanvasBody>
|
706 | </Offcanvas>
|
707 | </>,
|
708 | );
|
709 |
|
710 | user.tab();
|
711 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
712 |
|
713 | rerender(
|
714 | <>
|
715 | <button className="focus">Focused</button>
|
716 | <Offcanvas unmountOnClose={false} isOpen>
|
717 | <OffcanvasBody>Whatever</OffcanvasBody>
|
718 | </Offcanvas>
|
719 | </>,
|
720 | );
|
721 |
|
722 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
723 |
|
724 | rerender(
|
725 | <>
|
726 | <button className="focus">Focused</button>
|
727 | <Offcanvas unmountOnClose={false} isOpen={false}>
|
728 | <OffcanvasBody>Whatever</OffcanvasBody>
|
729 | </Offcanvas>
|
730 | </>,
|
731 | );
|
732 |
|
733 | jest.runAllTimers();
|
734 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
735 | });
|
736 |
|
737 | it('should not return the focus to the last focused element before the offcanvas has opened when "returnFocusAfterClose" is false and "unmountOnClose" is false', () => {
|
738 | const { rerender } = render(
|
739 | <>
|
740 | <button className="focus">Focused</button>
|
741 | <Offcanvas
|
742 | returnFocusAfterClose={false}
|
743 | unmountOnClose={false}
|
744 | isOpen={false}
|
745 | >
|
746 | <OffcanvasBody>Whatever</OffcanvasBody>
|
747 | </Offcanvas>
|
748 | </>,
|
749 | );
|
750 |
|
751 | user.tab();
|
752 | expect(screen.getByText(/focused/i)).toHaveFocus();
|
753 |
|
754 | rerender(
|
755 | <>
|
756 | <button className="focus">Focused</button>
|
757 | <Offcanvas returnFocusAfterClose={false} unmountOnClose={false} isOpen>
|
758 | <OffcanvasBody>Whatever</OffcanvasBody>
|
759 | </Offcanvas>
|
760 | </>,
|
761 | );
|
762 |
|
763 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
764 |
|
765 | rerender(
|
766 | <>
|
767 | <button className="focus">Focused</button>
|
768 | <Offcanvas
|
769 | returnFocusAfterClose={false}
|
770 | unmountOnClose={false}
|
771 | isOpen={false}
|
772 | >
|
773 | <OffcanvasBody>Whatever</OffcanvasBody>
|
774 | </Offcanvas>
|
775 | </>,
|
776 | );
|
777 |
|
778 | jest.runAllTimers();
|
779 | expect(screen.getByText(/focused/i)).not.toHaveFocus();
|
780 | });
|
781 |
|
782 | it('should attach/detach trapFocus for dialogs', () => {
|
783 | const addEventListener = jest.spyOn(document, 'addEventListener');
|
784 | const removeEventListener = jest.spyOn(document, 'removeEventListener');
|
785 |
|
786 | const { unmount } = render(
|
787 | <Offcanvas isOpen toggle={toggle}>
|
788 | Yo!
|
789 | </Offcanvas>,
|
790 | );
|
791 |
|
792 | expect(addEventListener).toHaveBeenCalledTimes(1);
|
793 | expect(addEventListener).toHaveBeenCalledWith(
|
794 | 'focus',
|
795 | expect.any(Function),
|
796 | true,
|
797 | );
|
798 |
|
799 | unmount();
|
800 |
|
801 | expect(removeEventListener).toHaveBeenCalledTimes(1);
|
802 | expect(removeEventListener).toHaveBeenCalledWith(
|
803 | 'focus',
|
804 | expect.any(Function),
|
805 | true,
|
806 | );
|
807 |
|
808 | addEventListener.mockRestore();
|
809 | removeEventListener.mockRestore();
|
810 | });
|
811 |
|
812 | it('should trap focus inside the open dialog', () => {
|
813 | render(
|
814 | <>
|
815 | <Button className="first">Focused</Button>
|
816 | <Offcanvas isOpen trapFocus>
|
817 | <OffcanvasBody>
|
818 | Something else to see
|
819 | <Button className="focus">focusable element</Button>
|
820 | </OffcanvasBody>
|
821 | </Offcanvas>
|
822 | </>,
|
823 | );
|
824 | user.tab();
|
825 | expect(screen.getByText(/focusable element/i)).toHaveFocus();
|
826 |
|
827 | user.tab();
|
828 | expect(screen.getByText(/focusable element/i)).toHaveFocus();
|
829 | });
|
830 | });
|