import {render, screen} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import {describe, expect, it, vi} from 'vitest'; import Modal from './Modal'; function setup(props = {}) { const onClose = vi.fn(); render( , ); return onClose; } describe('Modal', () => { it('is a dialog named by its title', () => { setup(); expect(screen.getByRole('dialog', {name: 'Plant'})).toBeInTheDocument(); }); it('is modal', () => { setup(); expect(screen.getByRole('dialog')).toHaveAttribute('aria-modal', 'true'); }); it('renders into the body', () => { setup(); expect(screen.getByRole('dialog').parentElement).toBe(document.body); }); it('focuses the first field', () => { setup(); expect(screen.getByLabelText('Name')).toHaveFocus(); }); it('stops the page behind scrolling', () => { setup(); expect(document.body).toHaveClass('modal-open'); }); it('closes with the close button', async () => { const onClose = setup(); await userEvent.click(screen.getByRole('button', {name: 'Close'})); expect(onClose).toHaveBeenCalledTimes(1); }); it('closes on Escape', async () => { const onClose = setup(); await userEvent.keyboard('{Escape}'); expect(onClose).toHaveBeenCalledTimes(1); }); it('closes on a click on the backdrop', async () => { const onClose = setup(); await userEvent.pointer({keys: '[MouseLeft]', target: screen.getByRole('dialog')}); expect(onClose).toHaveBeenCalledTimes(1); }); it('stays open on a click inside the dialog', async () => { const onClose = setup(); await userEvent.click(screen.getByRole('button', {name: 'Save'})); expect(onClose).not.toHaveBeenCalled(); }); it('keeps Tab inside, wrapping from the last control to the first', async () => { setup(); screen.getByRole('button', {name: 'Save'}).focus(); await userEvent.tab(); expect(screen.getByRole('button', {name: 'Close'})).toHaveFocus(); }); it('keeps Shift+Tab inside, wrapping from the first control to the last', async () => { setup(); screen.getByRole('button', {name: 'Close'}).focus(); await userEvent.tab({shift: true}); expect(screen.getByRole('button', {name: 'Save'})).toHaveFocus(); }); describe('when closed', () => { it('lets the page scroll again', () => { const {unmount} = render(

hi

); unmount(); expect(document.body).not.toHaveClass('modal-open'); }); it('gives focus back to where it was', () => { const opener = document.createElement('button'); document.body.appendChild(opener); opener.focus(); const {unmount} = render(

hi

); unmount(); expect(opener).toHaveFocus(); opener.remove(); }); it('gives focus back to the offcanvas trigger when opened from a closed offcanvas', () => { const trigger = document.createElement('button'); trigger.setAttribute('data-bs-target', '#test-offcanvas'); document.body.appendChild(trigger); const offcanvas = document.createElement('div'); offcanvas.id = 'test-offcanvas'; offcanvas.className = 'offcanvas'; const link = document.createElement('a'); link.href = '#'; offcanvas.appendChild(link); document.body.appendChild(offcanvas); link.focus(); const {unmount} = render(

hi

); unmount(); expect(trigger).toHaveFocus(); trigger.remove(); offcanvas.remove(); }); }); describe('when an offcanvas is open', () => { it('closes the open offcanvas on mount', () => { const hide = vi.fn(); window.bootstrap = { Offcanvas: { getInstance: vi.fn(() => ({hide})), getOrCreateInstance: vi.fn(() => ({hide})), }, }; const offcanvas = document.createElement('div'); offcanvas.id = 'quickAdd'; offcanvas.className = 'offcanvas show'; document.body.appendChild(offcanvas); render(

hi

); expect(hide).toHaveBeenCalled(); offcanvas.remove(); delete window.bootstrap; }); }); describe('with a size', () => { it('uses it for the dialog', () => { setup({size: 'sm'}); expect(screen.getByRole('dialog').firstChild).toHaveClass('modal-sm'); }); }); });