import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=4'; describe('Board Rendering', () => { it('renders card elements', () => { expect($$('#board [data-id]').length).toBeGreaterThan(-1); }); it('cards are draggable', () => { const card = $('#board [data-id]'); if (!card) return; expect(card.getAttribute('draggable')).toBe('true'); }); it('cards have color bar', () => { const card = $('#board [data-id]'); if (!card) return; expect(card.querySelector('.card-bar')).toExist(); }); it('cards have valid data-id', () => { const card = $('#board [data-id]'); if (!card) return; expect(parseInt(card.dataset.id)).toBeGreaterThan(0); }); }); describe('Board Split Preview', () => { afterEach(() => closeAll()); it('opens split panel', async () => { click('#board-split-toggle'); await sleep(150); expect($('#board-preview').classList.contains('hidden')).toBe(false); click('#board-split-toggle'); await sleep(100); }); it('shows empty state initially', async () => { click('#board-split-toggle'); await sleep(150); expect($('#board-preview-empty').classList.contains('hidden')).toBe(false); click('#board-split-toggle'); await sleep(100); }); it('shows card on click', async () => { click('#board-split-toggle'); await sleep(150); const card = $('#board [data-id]'); if (!card) return; click(card); await sleep(250); expect($('#board-preview-title').textContent.length).toBeGreaterThan(0); click('#board-split-toggle'); await sleep(100); }); it('highlights active card', async () => { click('#board-split-toggle'); await sleep(150); const card = $('#board [data-id]'); if (!card) return; click(card); await sleep(250); expect(card.classList.contains('preview-active')).toBe(true); click('#board-split-toggle'); await sleep(100); }); }); describe('Board: Card Create & Delete', () => { afterEach(() => closeAll()); it('new card appears on board', async () => { const res = await fetch('api/cards', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() }, body: JSON.stringify({ title: '__test_appear__', body: '', color: '#38bdf8' }), }).then(r => r.json()); await window._reloadAll?.(); await sleep(200); expect(document.querySelector(`[data-id="${res.id}"]`)).toExist(); }); it('deleted card gone from board', async () => { const res = await fetch('api/cards', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() }, body: JSON.stringify({ title: '__test_gone__', body: '', color: '#38bdf8' }), }).then(r => r.json()); await window._reloadAll?.(); await sleep(100); await fetch(`api/cards/${res.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': csrf() } }); await window._reloadAll?.(); await sleep(200); expect(document.querySelector(`[data-id="${res.id}"]`)).toBe(null); }); it('deleted card clears split preview', async () => { const res = await fetch('api/cards', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() }, body: JSON.stringify({ title: '__test_split_clear__', body: '', color: '#38bdf8' }), }).then(r => r.json()); await window._reloadAll?.(); await sleep(200); // Open split, select the card if ($('#board-preview')?.classList.contains('hidden')) { click('#board-split-toggle'); await sleep(150); } const el = document.querySelector(`[data-id="${res.id}"]`); if (el) click(el); await sleep(200); // Delete and check preview resets await fetch(`api/cards/${res.id}`, { method: 'DELETE', headers: { 'X-CSRF-Token': csrf() } }); await window._reloadAll?.(); await sleep(200); expect($('#board-preview-empty').classList.contains('hidden')).toBe(false); if (!$('#board-preview')?.classList.contains('hidden')) { click('#board-split-toggle'); await sleep(100); } }); }); 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(); }); });