/** * Fase 17.4.1 — ChangelogModal unit tests. * * Mirrors the Spinner.test.ts pattern (Svelte 5 `mount`/`unmount`, no * @testing-library dependency). The modal imports CHANGELOG.md via Vite's * `?raw` query — vitest runs against the same Vite config (see * apps/web/vitest.config.ts) so the import resolves to the file contents at * test time. * * Specs: * CHM-U-01 — `open={false}` renders nothing. * CHM-U-02 — `open={true}` renders the CHANGELOG `

`. * CHM-U-03 — click on the overlay fires `onClose`. * CHM-U-04 — pressing Escape fires `onClose`. */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { flushSync, mount, unmount } from 'svelte'; import ChangelogModal from './ChangelogModal.svelte'; let host: HTMLElement; // eslint-disable-next-line @typescript-eslint/no-explicit-any let instance: any; function render(props: Record) { host = document.createElement('div'); document.body.appendChild(host); instance = mount(ChangelogModal, { target: host, props }); return host; } beforeEach(() => { host = document.createElement('div'); }); afterEach(() => { if (instance) unmount(instance); if (host?.parentNode) host.parentNode.removeChild(host); instance = null; }); describe('ChangelogModal component', () => { it('CHM-U-01: open=false renders nothing visible', () => { const root = render({ open: false, onClose: () => {} }); expect(root.querySelector('[data-testid="changelog-modal"]')).toBeNull(); }); it('CHM-U-02: open=true renders the CHANGELOG h1 heading', () => { const root = render({ open: true, onClose: () => {} }); const modal = root.querySelector('[data-testid="changelog-modal"]'); expect(modal).not.toBeNull(); const h1 = modal?.querySelector('h1'); expect(h1).not.toBeNull(); expect(h1?.textContent?.trim()).toBe('Changelog'); }); it('CHM-U-03: click on overlay fires onClose', () => { const onClose = vi.fn(); const root = render({ open: true, onClose }); const overlay = root.querySelector('[data-testid="changelog-modal-overlay"]') as | HTMLElement | null; expect(overlay).not.toBeNull(); overlay?.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(onClose).toHaveBeenCalledTimes(1); }); it('CHM-U-04: Escape key fires onClose', () => { const onClose = vi.fn(); render({ open: true, onClose }); // $effect runs in a microtask after mount. flushSync drains pending // reactive effects so the keydown listener is attached before we // dispatch the event. flushSync(); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(onClose).toHaveBeenCalledTimes(1); }); });