import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { App } from '../src/app.js'; import { SettingsPanel } from '../src/components/SettingsPanel.js'; function makeBody() { return { classList: { _set: new Set(), add(c) { this._set.add(c); }, remove(c) { this._set.delete(c); }, toggle(c, on) { on ? this._set.add(c) : this._set.delete(c); }, contains(c) { return this._set.has(c); }, forEach(cb) { this._set.forEach(cb); }, }, focus: vi.fn(), }; } function makeSelect(value = '') { return { value }; } function makeToggle() { return { classList: { _set: new Set(), toggle(c, on) { on ? this._set.add(c) : this._set.delete(c); }, contains(c) { return this._set.has(c); }, }, setAttribute: vi.fn(), }; } function makeClassList() { return { _set: new Set(), add(c) { this._set.add(c); }, remove(c) { this._set.delete(c); }, toggle(c, on) { const enabled = on === undefined ? !this._set.has(c) : on; enabled ? this._set.add(c) : this._set.delete(c); return enabled; }, contains(c) { return this._set.has(c); }, }; } function makePanelNode({ tagName = 'BUTTON', value = '', attrs = {} } = {}) { const listeners = {}; const attributes = { ...attrs }; return { tagName, value, classList: makeClassList(), addEventListener: vi.fn((type, cb) => { listeners[type] = cb; }), click() { listeners.click?.({ type: 'click' }); }, setAttribute: vi.fn((name, next) => { attributes[name] = String(next); }), getAttribute: vi.fn((name) => attributes[name] ?? null), }; } function makeSettingsContainer() { const listeners = {}; const nodes = {}; const selects = []; const container = { innerHTML: '', replaceChildren: vi.fn(() => { container.innerHTML = ''; }), insertAdjacentHTML: vi.fn((_position, html) => { container.innerHTML = String(html); nodes['#btn-close-settings'] = makePanelNode({ attrs: { 'aria-label': 'Close settings' } }); nodes['#toggle-hearing'] = makePanelNode({ attrs: { role: 'switch', 'aria-checked': 'false' } }); nodes['#toggle-vision'] = makePanelNode({ attrs: { role: 'switch', 'aria-checked': 'false' } }); nodes['#toggle-staff'] = makePanelNode({ attrs: { role: 'switch', 'aria-checked': 'false' } }); nodes['#setting-lang'] = makePanelNode({ tagName: 'SELECT', value: 'en' }); nodes['#setting-pace'] = makePanelNode({ tagName: 'SELECT', value: 'medium' }); nodes['#setting-register'] = makePanelNode({ tagName: 'SELECT', value: 'casual' }); nodes['#setting-theme'] = makePanelNode({ tagName: 'SELECT', value: 'neon' }); selects.splice(0, selects.length, nodes['#setting-lang'], nodes['#setting-pace'], nodes['#setting-register'], nodes['#setting-theme']); }), querySelector: vi.fn((selector) => nodes[selector] || null), querySelectorAll: vi.fn((selector) => selector === 'select' ? selects : []), addEventListener: vi.fn((type, cb) => { listeners[type] = cb; }), dispatchEvent: vi.fn((event) => { listeners[event.type]?.(event); return true; }), }; return container; } describe('App settings & accessibility application', () => { beforeEach(() => { vi.stubGlobal('document', { getElementById: vi.fn(() => null), querySelectorAll: vi.fn(() => []), addEventListener: vi.fn(), }); vi.stubGlobal('localStorage', { _store: {}, getItem(k) { return this._store[k] ?? null; }, setItem(k, v) { this._store[k] = String(v); }, removeItem(k) { delete this._store[k]; }, }); vi.stubGlobal('window', { matchMedia: () => ({ matches: false, addEventListener: () => {} }), addEventListener: () => {}, }); vi.stubGlobal('document', { ...globalThis.document, body: makeBody(), getElementById: vi.fn(() => null), querySelectorAll: vi.fn(() => []), addEventListener: vi.fn(), }); }); afterEach(() => { vi.unstubAllGlobals(); }); it('persists settings to localStorage', () => { const app = new App(); app._applySettings({ language: 'es', pace: 'fast' }); const stored = JSON.parse(localStorage.getItem('vantage_settings')); expect(stored.language).toBe('es'); expect(stored.pace).toBe('fast'); }); it('applies and persists the selected theme from settings', () => { const app = new App(); app._applySettings({ theme: 'light' }); expect(document.body.classList.contains('theme-light')).toBe(true); expect(localStorage.getItem('vantage_theme')).toBe('light'); }); it('normalizes invalid theme values before applying body classes', () => { const app = new App(); app._applySettings({ theme: 'bad theme