import { isInaccessible } from '@testing-library/react' import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event' import { render } from '~/src/utils/test' import { ConfirmModal, ConfirmModalClose, ConfirmModalContent, ConfirmModalFooter, ConfirmModalHeader, ConfirmModalTrigger, } from './ConfirmModal' import { type ConfirmModalContentProps, type ConfirmModalProps, } from './ConfirmModal.types' const TRIGGER_TEXT = 'Open' const CANCEL_TEXT = 'Cancel' const ACTION_TEXT = 'Action' const TITLE_TEXT = 'Title' const DESCRIPTION_TEXT = 'Description' describe('ConfirmModal', () => { const renderModal = ({ modalProps, modalContentProps, }: { modalProps?: ConfirmModalProps modalContentProps?: ConfirmModalContentProps } = {}) => render( } /> ) let user: ReturnType let renderOpenedModal: typeof renderModal beforeEach(() => { user = userEvent.setup({ /** * To prevent `pointer-events: none` error. * @see https://testing-library.com/docs/user-event/options#pointereventscheck */ pointerEventsCheck: PointerEventsCheckLevel.Never, }) renderOpenedModal = (props) => renderModal({ modalProps: { defaultShow: true }, ...props }) }) describe('Accessibility', () => { it('should be accessible', () => { const { container } = renderModal() expect(isInaccessible(container)).toBe(false) }) }) describe('Focus Management', () => { it('should focus on the cancel button element when the modal is opened', async () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) await user.click(trigger) expect(document.activeElement).toBe( getByRole('button', { name: CANCEL_TEXT }) ) }) it('should focus on the modal trigger when the modal is closed', async () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) await user.click(trigger) await user.click(getByRole('button', { name: CANCEL_TEXT })) expect(document.activeElement).toBe(trigger) }) it('should be trapped inside the modal when the modal is opened', async () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) await user.click(trigger) const cancelButton = getByRole('button', { name: CANCEL_TEXT }) const actionButton = getByRole('button', { name: ACTION_TEXT }) expect(document.activeElement).toBe(cancelButton) await user.tab() expect(document.activeElement).toBe(actionButton) await user.tab() expect(document.activeElement).not.toBe(trigger) /* Cancel button */ await user.tab({ shift: true }) expect(document.activeElement).toBe(actionButton) await user.tab({ shift: true }) expect(document.activeElement).toBe(cancelButton) await user.tab({ shift: true }) expect(document.activeElement).not.toBe(trigger) /* Action button */ }) }) describe('ConfirmModalContent', () => { describe('ARIA', () => { it('should have \'role="alertdialog"\' attribute', () => { const { getByRole } = renderOpenedModal() expect(getByRole('alertdialog')).toBeInTheDocument() }) it('should have \'aria-modal="true"\' attribute', () => { const { getByRole } = renderOpenedModal() expect(getByRole('alertdialog')).toHaveAttribute('aria-modal', 'true') }) it("should have proper 'aria-labelledby' attribute", () => { const { getByRole } = renderOpenedModal() expect( getByRole('alertdialog', { name: TITLE_TEXT }) ).toBeInTheDocument() }) it("should have proper 'aria-describedby' attribute", () => { const { getByRole } = renderOpenedModal() expect( getByRole('alertdialog', { description: DESCRIPTION_TEXT }) ).toBeInTheDocument() }) }) describe('User Interactions', () => { it('should close the modal when the user clicks the outside of the modal', async () => { const { queryByRole, container } = renderOpenedModal() await user.click(container) expect(queryByRole('alertdialog')).not.toBeInTheDocument() }) it('should close the modal when the user presses the ESC key', async () => { const { queryByRole } = renderOpenedModal() await user.keyboard('{Escape}') expect(queryByRole('alertdialog')).not.toBeInTheDocument() }) }) describe('Data Attributes', () => { it("should have proper 'data-state' attribute", () => { const { getByRole } = renderOpenedModal() expect(getByRole('alertdialog')).toHaveAttribute('data-state', 'open') }) }) }) describe('ConfirmModalHeader', () => { describe('ARIA, Semantics', () => { it('the title should be an h2 element', () => { const { getByRole } = renderOpenedModal() expect( getByRole('heading', { name: TITLE_TEXT, level: 2 }) ).toBeInTheDocument() }) }) }) describe('ConfirmModalTrigger', () => { describe('ARIA', () => { it('should have \'aria-haspopup="dialog"\' attribute', () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) expect(trigger).toHaveAttribute('aria-haspopup', 'dialog') }) it("should have proper 'aria-expanded' attribute", async () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) expect(trigger).toHaveAttribute('aria-expanded', 'false') await user.click(trigger) expect(trigger).toHaveAttribute('aria-expanded', 'true') }) it('should have \'aria-controls="{dialogElement.id}"\' attribute', async () => { const { getByRole } = renderModal() const trigger = getByRole('button', { name: TRIGGER_TEXT }) await user.click(trigger) expect(trigger).toHaveAttribute( 'aria-controls', getByRole('alertdialog').id ) }) }) describe('User Interactions', () => { it('should open modal when clicked (Uncontrolled)', async () => { const { getByRole } = renderModal() await user.click(getByRole('button', { name: TRIGGER_TEXT })) expect(getByRole('alertdialog')).toBeInTheDocument() }) it('should open modal when clicked (Controlled)', async () => { const onShow = jest.fn() const { getByRole } = renderModal({ modalProps: { show: false, onShow }, }) await user.click(getByRole('button', { name: TRIGGER_TEXT })) expect(onShow).toHaveBeenCalledTimes(1) }) }) }) describe('ConfirmModalClose', () => { describe('User Interactions', () => { it('should close modal when clicked (Uncontrolled)', async () => { const { getByRole, queryByRole } = renderModal() await user.click(getByRole('button', { name: TRIGGER_TEXT })) await user.click(getByRole('button', { name: CANCEL_TEXT })) expect(queryByRole('alertdialog')).not.toBeInTheDocument() }) it('should close modal when clicked (Controlled)', async () => { const onHide = jest.fn() const { getByRole } = renderModal({ modalProps: { show: true, onHide }, }) await user.click(getByRole('button', { name: CANCEL_TEXT })) expect(onHide).toHaveBeenCalledTimes(1) }) }) }) describe('ConfirmModalAction', () => { describe('User Interactions', () => { it('should close modal when clicked (Uncontrolled)', async () => { const { getByRole, queryByRole } = renderModal() await user.click(getByRole('button', { name: TRIGGER_TEXT })) await user.click(getByRole('button', { name: ACTION_TEXT })) expect(queryByRole('alertdialog')).not.toBeInTheDocument() }) it('should close modal when clicked (Controlled)', async () => { const onHide = jest.fn() const { getByRole } = renderModal({ modalProps: { show: true, onHide }, }) await user.click(getByRole('button', { name: ACTION_TEXT })) expect(onHide).toHaveBeenCalledTimes(1) }) }) }) })