import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, expect, it, vi } from 'vitest' import { ModalShell } from '../ModalShell' import { topmostDialog } from '../focusable' afterEach(cleanup) it('traps focus, closes on Escape, and restores focus to the launcher', () => { const launcher = document.createElement('button') document.body.appendChild(launcher) launcher.focus() const onClose = vi.fn() const content = (open: boolean) =>

Test dialog

const { rerender } = render(content(true)) const dialog = screen.getByRole('dialog', { name: 'Test dialog' }) expect(dialog).toHaveFocus() launcher.focus() expect(dialog).toHaveFocus() const [first, last] = screen.getAllByRole('button').filter(button => button !== launcher) fireEvent.keyDown(document, { key: 'Tab', shiftKey: true }) expect(last).toHaveFocus() last.focus() fireEvent.keyDown(document, { key: 'Tab' }) expect(first).toHaveFocus() fireEvent.keyDown(document, { key: 'Escape' }) expect(onClose).toHaveBeenCalledOnce() rerender(content(false)) expect(launcher).toHaveFocus() launcher.remove() }) it('leaves Escape and Tab to a dialog opened above it', async () => { const onClose = vi.fn() render(

Parent

) const nested = document.createElement('div') nested.setAttribute('role', 'dialog') nested.setAttribute('aria-modal', 'true') nested.style.position = 'fixed' nested.style.zIndex = '1100' const input = document.createElement('input') nested.appendChild(input) document.body.appendChild(nested) const parent = document.querySelector('[aria-labelledby="dialog-title"]')! await waitFor(() => { expect(parent).toHaveAttribute('aria-modal', 'false') expect(parent).toHaveAttribute('aria-hidden', 'true') }) input.focus() fireEvent.keyDown(document, { key: 'Escape' }) fireEvent.keyDown(document, { key: 'Tab' }) expect(onClose).not.toHaveBeenCalled() expect(input).toHaveFocus() input.blur() fireEvent.keyDown(document, { key: 'Escape' }) expect(onClose).not.toHaveBeenCalled() nested.remove() await waitFor(() => { expect(parent).toHaveAttribute('aria-modal', 'true') expect(parent).not.toHaveAttribute('aria-hidden') }) }) it('uses visual overlay order when a lower-layer dialog portals later', () => { const account = document.createElement('div') account.setAttribute('role', 'dialog') account.style.zIndex = '1300' const picker = document.createElement('div') picker.setAttribute('role', 'dialog') picker.style.zIndex = '1100' document.body.append(account, picker) expect(topmostDialog()).toBe(account) account.remove() picker.remove() })