import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { Modal } from './modal'; import { MODAL_CLOSE_EVENT, MODAL_OPEN_EVENT } from './modal.types'; const MARKUP = `
Link
`; function setup(): HTMLElement { document.body.innerHTML = MARKUP; return document.body.firstElementChild as HTMLElement; } describe('Modal', () => { beforeEach(() => { document.body.innerHTML = ''; }); afterEach(() => { document.body.style.overflow = ''; }); it('wires dialog ARIA and stays hidden on init', () => { const root = setup(); new Modal(root); const content = root.querySelector('[data-c42-modal-content]')!; expect(content.getAttribute('role')).toBe('dialog'); expect(content.getAttribute('aria-modal')).toBe('true'); expect(root.hasAttribute('hidden')).toBe(true); }); it('throws without content', () => { document.body.innerHTML = '
'; const root = document.body.firstElementChild as HTMLElement; expect(() => new Modal(root)).toThrow(/content element/); }); it('opens, locks scroll and focuses first focusable', () => { const root = setup(); const modal = new Modal(root); modal.openModal(); expect(modal.isOpen).toBe(true); expect(root.dataset.state).toBe('open'); expect(document.body.style.overflow).toBe('hidden'); expect(document.activeElement).toBe(root.querySelector('[data-c42-modal-close]')); }); it('closes on overlay click and unlocks scroll', () => { const root = setup(); const modal = new Modal(root); modal.openModal(); root.querySelector('[data-c42-modal-overlay]')!.click(); expect(modal.isOpen).toBe(false); expect(document.body.style.overflow).toBe(''); }); it('closes on Escape', () => { const root = setup(); const modal = new Modal(root); modal.openModal(); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(modal.isOpen).toBe(false); }); it('closes via a close button', () => { const root = setup(); const modal = new Modal(root); modal.openModal(); root.querySelector('[data-c42-modal-close]')!.click(); expect(modal.isOpen).toBe(false); }); it('restores focus to the previously focused element', () => { document.body.innerHTML = `${MARKUP}`; const opener = document.getElementById('opener') as HTMLElement; const root = document.querySelector('[data-c42-modal]')!; opener.focus(); const modal = new Modal(root); modal.openModal(); modal.close(); expect(document.activeElement).toBe(opener); }); it('respects closeOnEscape=false', () => { const root = setup(); const modal = new Modal(root, { closeOnEscape: false }); modal.openModal(); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(modal.isOpen).toBe(true); }); it('emits open and close events', () => { const root = setup(); const modal = new Modal(root); const onOpen = vi.fn(); const onClose = vi.fn(); modal.on(MODAL_OPEN_EVENT, onOpen); modal.on(MODAL_CLOSE_EVENT, onClose); modal.openModal(); modal.close(); expect(onOpen).toHaveBeenCalledOnce(); expect(onClose).toHaveBeenCalledOnce(); }); it('supports defaultOpen', () => { const root = setup(); const modal = new Modal(root, { defaultOpen: true }); expect(modal.isOpen).toBe(true); }); });