import { describe, it, afterEach, expect, $, $$, click, sleep, closeAll, csrf } from './runner.js?v=4'; async function makeCard(title, body) { return fetch('api/cards', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf() }, body: JSON.stringify({ title, body, color: '#38bdf8' }), }).then(r => r.json()); } async function getCard(id) { const all = await fetch(`api/cards?ids=${id}`).then(r => r.json()); return all.find(c => c.id == id); } function openBoardFR() { // Ctrl+Shift+H opens the board find/replace overlay document.dispatchEvent(new KeyboardEvent('keydown', { key: 'H', ctrlKey: true, shiftKey: true, bubbles: true, })); } describe('Board: Find/Replace', () => { afterEach(async () => { document.getElementById('board-find-replace')?.remove(); await closeAll(); }); it('Ctrl+Shift+H opens the overlay', async () => { openBoardFR(); // Wait for the overlay (it does an async api('api/cards') fetch) for (let i = 0; i < 40; i++) { if (document.getElementById('board-find-replace')) break; await sleep(50); } expect(document.getElementById('board-find-replace')).toExist(); expect(document.getElementById('bfr-find')).toExist(); expect(document.getElementById('bfr-replace')).toExist(); }); it('typing in find shows matching cards', async () => { const c1 = await makeCard('__bfr_match_a__', 'the quick brown fox'); const c2 = await makeCard('__bfr_match_b__', 'the quick red fox'); const c3 = await makeCard('__bfr_nomatch__', 'no match here'); await window._reloadAll?.(); await sleep(200); openBoardFR(); for (let i = 0; i < 40; i++) { if (document.getElementById('bfr-find')) break; await sleep(50); } const findInp = document.getElementById('bfr-find'); if (!findInp) return; findInp.value = 'quick'; findInp.dispatchEvent(new Event('input', { bubbles: true })); await sleep(100); const status = document.getElementById('bfr-status'); // Should find 'quick' in at least 2 cards (c1 + c2) expect(status.textContent).toContain('2'); // The list should mention both matching card titles const list = document.getElementById('bfr-list'); expect(list.textContent).toContain('__bfr_match_a__'); expect(list.textContent).toContain('__bfr_match_b__'); // Non-matching card should NOT be in the list expect(list.textContent.includes('__bfr_nomatch__')).toBe(false); }); it('replace all updates card bodies after confirmation', async () => { const c1 = await makeCard('__bfr_repl_a__', 'hello world hello'); const c2 = await makeCard('__bfr_repl_b__', 'hello there'); await window._reloadAll?.(); await sleep(200); openBoardFR(); for (let i = 0; i < 40; i++) { if (document.getElementById('bfr-find')) break; await sleep(50); } const findInp = document.getElementById('bfr-find'); const replInp = document.getElementById('bfr-replace'); if (!findInp || !replInp) return; findInp.value = 'hello'; findInp.dispatchEvent(new Event('input', { bubbles: true })); await sleep(50); replInp.value = 'goodbye'; replInp.dispatchEvent(new Event('input', { bubbles: true })); await sleep(50); const applyBtn = document.getElementById('bfr-apply'); expect(applyBtn.disabled).toBe(false); click(applyBtn); await sleep(200); // The confirmation dialog should appear const dialog = document.getElementById('dialog'); if (dialog?.classList.contains('open')) { const okBtn = document.getElementById('dialog-ok'); if (okBtn) click(okBtn); await sleep(600); } // Verify card bodies were updated const updated1 = await getCard(c1.id); const updated2 = await getCard(c2.id); expect(updated1.body).toBe('goodbye world goodbye'); expect(updated2.body).toBe('goodbye there'); }); it('replace all button disabled without replace text', async () => { await makeCard('__bfr_dis__', 'some text'); await window._reloadAll?.(); await sleep(200); openBoardFR(); for (let i = 0; i < 40; i++) { if (document.getElementById('bfr-find')) break; await sleep(50); } const findInp = document.getElementById('bfr-find'); if (!findInp) return; findInp.value = 'some'; findInp.dispatchEvent(new Event('input', { bubbles: true })); await sleep(50); const applyBtn = document.getElementById('bfr-apply'); // Replace input is empty → button should be disabled expect(applyBtn.disabled).toBe(true); }); });