/** * Tests for services/help.js * * Tests the HelpService for collapsible help panels: * togglePanel, dismissPanel, isPanelDismissed, expandAll, collapseAll, reset. */ let HelpService; beforeAll(async () => { // Stub fetch — initPanelStates calls /settings/api/bulk on init globalThis.fetch = vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, settings: {} }), }) ); window.api = { getCsrfToken: () => 'test-token' }; window.ui = { showMessage: vi.fn() }; await import('@js/services/help.js'); HelpService = window.HelpService; }); afterEach(() => { // Clean up any panels created in tests document.querySelectorAll('.ldr-help-panel').forEach(p => p.remove()); localStorage.clear(); }); describe('HelpService', () => { function createPanel(id, collapsed = false) { const panel = document.createElement('div'); panel.className = 'ldr-help-panel' + (collapsed ? ' collapsed' : ''); panel.id = 'help-panel-' + id; panel.setAttribute('data-panel-id', id); const header = document.createElement('div'); header.className = 'ldr-help-panel-header'; header.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); panel.appendChild(header); document.body.appendChild(panel); return panel; } describe('togglePanel', () => { it('toggles the collapsed class', () => { const panel = createPanel('test1'); expect(panel.classList.contains('collapsed')).toBe(false); HelpService.togglePanel('test1'); expect(panel.classList.contains('collapsed')).toBe(true); HelpService.togglePanel('test1'); expect(panel.classList.contains('collapsed')).toBe(false); }); it('updates aria-expanded on header', () => { const panel = createPanel('test2'); const header = panel.querySelector('.ldr-help-panel-header'); HelpService.togglePanel('test2'); expect(header.getAttribute('aria-expanded')).toBe('false'); HelpService.togglePanel('test2'); expect(header.getAttribute('aria-expanded')).toBe('true'); }); it('persists collapsed state to localStorage', () => { createPanel('test3'); HelpService.togglePanel('test3'); expect(localStorage.getItem('ldr_panel_collapsed_test3')).toBe('true'); HelpService.togglePanel('test3'); expect(localStorage.getItem('ldr_panel_collapsed_test3')).toBe('false'); }); it('does nothing for non-existent panel', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); expect(() => HelpService.togglePanel('nonexistent')).not.toThrow(); warnSpy.mockRestore(); }); }); describe('expandAll', () => { it('removes collapsed class from all panels', () => { const p1 = createPanel('e1', true); const p2 = createPanel('e2', true); const p3 = createPanel('e3', false); HelpService.expandAll(); expect(p1.classList.contains('collapsed')).toBe(false); expect(p2.classList.contains('collapsed')).toBe(false); expect(p3.classList.contains('collapsed')).toBe(false); }); it('updates aria-expanded to true on all expanded panels', () => { const p = createPanel('e4', true); HelpService.expandAll(); const header = p.querySelector('.ldr-help-panel-header'); expect(header.getAttribute('aria-expanded')).toBe('true'); }); }); describe('collapseAll', () => { it('adds collapsed class to all panels', () => { const p1 = createPanel('c1', false); const p2 = createPanel('c2', false); HelpService.collapseAll(); expect(p1.classList.contains('collapsed')).toBe(true); expect(p2.classList.contains('collapsed')).toBe(true); }); it('updates aria-expanded to false', () => { const p = createPanel('c3', false); HelpService.collapseAll(); const header = p.querySelector('.ldr-help-panel-header'); expect(header.getAttribute('aria-expanded')).toBe('false'); }); it('does not affect already-collapsed panels', () => { const p = createPanel('c4', true); HelpService.collapseAll(); expect(p.classList.contains('collapsed')).toBe(true); }); }); describe('isPanelDismissed', () => { it('returns false for unknown panels', () => { expect(HelpService.isPanelDismissed('unknown')).toBe(false); }); }); describe('dismissPanel', () => { it('hides the panel and marks it dismissed', async () => { const panel = createPanel('dismiss1'); globalThis.fetch = vi.fn(() => Promise.resolve({ ok: true, status: 200 }) ); await HelpService.dismissPanel('dismiss1'); expect(panel.style.display).toBe('none'); expect(HelpService.isPanelDismissed('dismiss1')).toBe(true); }); }); });