ChangelogModal renders the repo-root CHANGELOG.md (bundled at build time via Vite's ?raw query, 5 levels up from the component file) in an overlay+panel modal. Markdown is parsed inline (`#`/`##`/`###` headers, `-`/`*` lists, `**bold**`) with HTML-escape on every input line — input is static repo content so the surface is closed. Esc and overlay-click both call `onClose`. The keydown listener is attached only while `open` is true via a $effect. Three new Paraglide messages (en/es): `settings_about_view_changelog`, `changelog_modal_title`, `changelog_modal_close`. Unit tests (CHM-U-01..04) follow the Spinner.test.ts pattern (Svelte 5 mount/unmount + flushSync for the Escape spec). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
/**
|
|
* 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 `<h1>`.
|
|
* 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<string, unknown>) {
|
|
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);
|
|
});
|
|
});
|