import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Dropdown } from './dropdown'; import { DROPDOWN_OPEN_EVENT } from './dropdown.types'; const MARKUP = `
`; function setup(): HTMLElement { document.body.innerHTML = MARKUP; return document.body.firstElementChild as HTMLElement; } function items(root: HTMLElement): HTMLElement[] { return Array.from(root.querySelectorAll('[data-c42-dropdown-item]')); } describe('Dropdown', () => { beforeEach(() => { document.body.innerHTML = ''; }); it('wires ARIA roles and hides the menu on init', () => { const root = setup(); new Dropdown(root); const trigger = root.querySelector('[data-c42-dropdown-trigger]')!; const menu = root.querySelector('[data-c42-dropdown-menu]')!; expect(trigger.getAttribute('aria-haspopup')).toBe('menu'); expect(trigger.getAttribute('aria-expanded')).toBe('false'); expect(trigger.getAttribute('aria-controls')).toBe(menu.id); expect(menu.getAttribute('role')).toBe('menu'); expect(menu.hasAttribute('hidden')).toBe(true); expect(items(root)[0].getAttribute('role')).toBe('menuitem'); }); it('toggles open and closed on trigger click', () => { const root = setup(); const dropdown = new Dropdown(root); const trigger = root.querySelector('[data-c42-dropdown-trigger]')!; trigger.click(); expect(dropdown.isOpen).toBe(true); expect(trigger.getAttribute('aria-expanded')).toBe('true'); trigger.click(); expect(dropdown.isOpen).toBe(false); }); it('navigates items with arrow keys', () => { const root = setup(); const dropdown = new Dropdown(root); dropdown.openMenu(); const menu = root.querySelector('[data-c42-dropdown-menu]')!; const [first, second] = items(root); first.focus(); menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); expect(document.activeElement).toBe(second); }); it('closes on Escape and returns focus to the trigger', () => { const root = setup(); const dropdown = new Dropdown(root); const trigger = root.querySelector('[data-c42-dropdown-trigger]')!; dropdown.openMenu(); const menu = root.querySelector('[data-c42-dropdown-menu]')!; menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); expect(dropdown.isOpen).toBe(false); expect(document.activeElement).toBe(trigger); }); it('closes on outside pointerdown', () => { const root = setup(); const dropdown = new Dropdown(root); dropdown.openMenu(); document.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true })); expect(dropdown.isOpen).toBe(false); }); it('emits open event', () => { const root = setup(); const dropdown = new Dropdown(root); const spy = vi.fn(); dropdown.on(DROPDOWN_OPEN_EVENT, spy); dropdown.openMenu(); expect(spy).toHaveBeenCalledOnce(); }); it('stops reacting after destroy', () => { const root = setup(); const dropdown = new Dropdown(root); dropdown.destroy(); const trigger = root.querySelector('[data-c42-dropdown-trigger]')!; trigger.click(); expect(dropdown.isOpen).toBe(false); }); });