Files
hyprcrd/public/plugins/tests/board-find-replace.test.js
T
thandClaude Opus 4.6 d3d9355191 electron: external data dir, packaged path fix, footer responsive, test stabilization
- DB + uploads stored outside app bundle (survives updates)
- Router uses DOCUMENT_ROOT instead of __DIR__ (fixes AppImage paths)
- Footer count shifts right below 1250px, min-width 1150px
- Gallery opens gracefully on fetch errors
- Stabilized gallery, export, and find/replace tests with poll loops

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:24:00 +02:00

111 lines
4.0 KiB
JavaScript

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 openSearchReplace() {
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 'H', ctrlKey: true, shiftKey: true, bubbles: true,
}));
}
describe('Search: Find/Replace', () => {
afterEach(async () => {
await closeAll();
});
it('Ctrl+Shift+H opens search with replace row visible', async () => {
openSearchReplace();
await sleep(200);
const overlay = document.getElementById('search');
expect(overlay?.classList.contains('open')).toBe(true);
expect(document.getElementById('search-replace-row')).toBeVisible();
expect(document.getElementById('search-replace-input')).toExist();
expect(document.getElementById('search-replace-all')).toExist();
});
it('replace toggle shows/hides replace row', async () => {
openSearchReplace();
await sleep(200);
expect(document.getElementById('search-replace-row')).toBeVisible();
click('#search-replace-toggle');
await sleep(50);
expect(document.getElementById('search-replace-row')?.classList.contains('hidden')).toBe(true);
click('#search-replace-toggle');
await sleep(50);
expect(document.getElementById('search-replace-row')).toBeVisible();
});
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);
openSearchReplace();
await sleep(200);
const searchInput = document.getElementById('search-input');
const replInput = document.getElementById('search-replace-input');
if (!searchInput || !replInput) return;
searchInput.value = 'hello';
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
await sleep(200);
replInput.value = 'goodbye';
replInput.dispatchEvent(new Event('input', { bubbles: true }));
await sleep(50);
const applyBtn = document.getElementById('search-replace-all');
expect(applyBtn.disabled).toBe(false);
click(applyBtn);
await sleep(200);
const dialog = document.getElementById('dialog');
if (dialog?.classList.contains('open')) {
const okBtn = document.getElementById('dialog-ok');
if (okBtn) click(okBtn);
}
// Poll until the replace has actually persisted
let updated1, updated2;
for (let i = 0; i < 40; i++) {
await sleep(100);
updated1 = await getCard(c1.id);
if (updated1?.body === 'goodbye world goodbye') break;
}
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);
openSearchReplace();
await sleep(200);
const searchInput = document.getElementById('search-input');
if (!searchInput) return;
searchInput.value = 'some';
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
await sleep(100);
const applyBtn = document.getElementById('search-replace-all');
expect(applyBtn.disabled).toBe(true);
});
});