diff --git a/config.php b/config.php index e4b944f..1c3849d 100644 --- a/config.php +++ b/config.php @@ -1,7 +1,7 @@ { } }); }); + +describe('Board: Focus restoration after overlay close', () => { + afterEach(() => closeAll()); + + it('closing the editor returns focus to body', async () => { + click('#add-btn'); + await sleep(200); + // Editor is open — focus should be inside the modal (title input) + expect(document.activeElement !== document.body).toBe(true); + click('#modal-cancel'); + await sleep(200); + expect(document.activeElement).toBe(document.body); + }); + + it('closing the detail view returns focus to body', async () => { + const card = $('#board [data-id]'); + if (!card) return; + click(card); + await sleep(300); + if (!$('#detail')?.classList.contains('open')) return; + click('#detail-close'); + await sleep(200); + expect(document.activeElement).toBe(document.body); + }); + + it('closing search returns focus to body', async () => { + click('#search-btn'); + await sleep(200); + if (!$('#search')?.classList.contains('open')) return; + expect(document.activeElement?.id).toBe('search-input'); + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + await sleep(200); + expect(document.activeElement).toBe(document.body); + }); + + it('arrow keys work after editor round-trip (enter → enter → escape)', async () => { + const card = $('#board [data-id]'); + if (!card) return; + // Focus a card + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); + await sleep(100); + if (!document.querySelector('#board .card-focused')) return; + // Enter → detail + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); + await sleep(300); + if (!$('#detail')?.classList.contains('open')) return; + // Enter → close detail, open editor + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); + await sleep(300); + if (!$('#modal')?.classList.contains('open')) return; + // Escape → close editor + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + await sleep(200); + expect(document.activeElement).toBe(document.body); + // Arrows should work again + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); + await sleep(100); + expect(document.querySelector('#board .card-focused')).toExist(); + }); +}); diff --git a/public/plugins/tests/loader.js b/public/plugins/tests/loader.js index 6431fe6..97b5e7e 100644 --- a/public/plugins/tests/loader.js +++ b/public/plugins/tests/loader.js @@ -49,6 +49,21 @@ function ensureProgressBar() { // pushed off-viewport). hideProgress() reverts both. document.body.style.marginTop = '30px'; document.body.style.height = 'calc(100vh - 30px)'; + // Transparent overlay that blocks all mouse interaction while tests + // run. Tests dispatch synthetic events so they still work; only real + // user clicks/hovers are absorbed. Removed by hideProgress(). + let shield = document.getElementById('test-shield'); + if (!shield) { + shield = document.createElement('div'); + shield.id = 'test-shield'; + const ss = shield.style; + ss.setProperty('position', 'fixed', 'important'); + ss.setProperty('inset', '30px 0 0 0', 'important'); + ss.setProperty('z-index', '2147483646', 'important'); + ss.setProperty('background', 'transparent', 'important'); + ss.setProperty('cursor', 'not-allowed', 'important'); + document.documentElement.appendChild(shield); + } const bar = document.createElement('div'); bar.id = 'test-progress'; const s = bar.style; diff --git a/public/plugins/tests/runner.js b/public/plugins/tests/runner.js index 66a5211..5967035 100644 --- a/public/plugins/tests/runner.js +++ b/public/plugins/tests/runner.js @@ -236,9 +236,7 @@ function updateProgress(current, total, name, failed) { } function hideProgress() { getProgressBar()?.remove(); - // ensureProgressBar() in loader.js pushed the document down by 30px and - // shrunk it by the same amount so the footer wouldn't get clipped. - // Restore both inline overrides here. + document.getElementById('test-shield')?.remove(); document.body.style.marginTop = ''; document.body.style.height = ''; } @@ -498,4 +496,9 @@ document.addEventListener('keydown', e => { e.preventDefault(); panel ? close() : open(); } + if (e.key === 'Escape' && panel) { + e.preventDefault(); + e.stopPropagation(); + close(); + } });