search: saved searches via bookmark button
Small bookmark icon in the search header bar opens a popup listing localStorage-persisted search strings. Clicking an entry fills the input and runs the search; the x button deletes it; "save current search" at the bottom saves the active input (no dupes). List persists across sessions, no server round-trip. Tests: 6 cases covering button/popup toggle, save + recall, delete, save-current-button, and duplicate rejection. All snapshot/restore localStorage in try/finally so they don't leak into user data or cross-test state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
be28080439
commit
883640e20c
5 files changed
+218
-2
No files matched your search
@@ -44,3 +44,125 @@ describe('Search', () => {
|
||||
expect($('#search')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search: saved searches', () => {
|
||||
const SAVED_KEY = 'hyprcrd-saved-searches';
|
||||
|
||||
afterEach(async () => {
|
||||
// Close overlays and restore localStorage to avoid cross-test pollution.
|
||||
document.querySelector('#search-saved-popup')?.classList.add('hidden');
|
||||
await closeAll();
|
||||
});
|
||||
|
||||
it('bookmark button and popup exist', () => {
|
||||
expect($('#search-saved-btn')).toExist();
|
||||
expect($('#search-saved-popup')).toExist();
|
||||
expect($('#search-saved-popup').classList.contains('hidden')).toBe(true);
|
||||
});
|
||||
|
||||
it('clicking bookmark opens the popup', async () => {
|
||||
click('#search-btn');
|
||||
await sleep(100);
|
||||
click('#search-saved-btn');
|
||||
await sleep(40);
|
||||
expect($('#search-saved-popup').classList.contains('hidden')).toBe(false);
|
||||
});
|
||||
|
||||
it('saves and recalls a search', async () => {
|
||||
// Seed localStorage directly to avoid UI coupling for the save path.
|
||||
const before = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
const probe = '__test_saved_search_probe__';
|
||||
try {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify([probe, ...before]));
|
||||
|
||||
click('#search-btn');
|
||||
await sleep(100);
|
||||
click('#search-saved-btn');
|
||||
await sleep(60);
|
||||
|
||||
const popup = $('#search-saved-popup');
|
||||
const rows = popup.querySelectorAll('.search-saved-row');
|
||||
expect(rows.length).toBeGreaterThan(0);
|
||||
// First row should be our probe.
|
||||
expect(rows[0].textContent).toContain(probe);
|
||||
|
||||
// Click the row → fills input.
|
||||
click(rows[0]);
|
||||
await sleep(40);
|
||||
expect($('#search-input').value).toBe(probe);
|
||||
} finally {
|
||||
// Restore original saved searches.
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify(before));
|
||||
}
|
||||
});
|
||||
|
||||
it('deletes a saved search', async () => {
|
||||
const before = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
const probe = '__test_saved_del__';
|
||||
try {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify([probe]));
|
||||
|
||||
click('#search-btn');
|
||||
await sleep(100);
|
||||
click('#search-saved-btn');
|
||||
await sleep(60);
|
||||
|
||||
const delBtn = $('#search-saved-popup').querySelector('.search-saved-del');
|
||||
expect(delBtn).toExist();
|
||||
click(delBtn);
|
||||
await sleep(40);
|
||||
|
||||
const after = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
expect(after.includes(probe)).toBe(false);
|
||||
} finally {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify(before));
|
||||
}
|
||||
});
|
||||
|
||||
it('save-current button adds input value', async () => {
|
||||
const before = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
const probe = '__test_saved_add_' + Date.now() + '__';
|
||||
try {
|
||||
click('#search-btn');
|
||||
await sleep(100);
|
||||
const inp = $('#search-input');
|
||||
inp.value = probe;
|
||||
inp.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
click('#search-saved-btn');
|
||||
await sleep(40);
|
||||
click('#search-saved-add');
|
||||
await sleep(40);
|
||||
|
||||
const after = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
expect(after.includes(probe)).toBe(true);
|
||||
} finally {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify(before));
|
||||
}
|
||||
});
|
||||
|
||||
it('does not add duplicates', async () => {
|
||||
const before = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
const probe = '__test_saved_dupe__';
|
||||
try {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify([probe]));
|
||||
|
||||
click('#search-btn');
|
||||
await sleep(100);
|
||||
const inp = $('#search-input');
|
||||
inp.value = probe;
|
||||
inp.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
click('#search-saved-btn');
|
||||
await sleep(40);
|
||||
click('#search-saved-add');
|
||||
await sleep(40);
|
||||
|
||||
const after = JSON.parse(localStorage.getItem(SAVED_KEY) || '[]');
|
||||
const count = after.filter(q => q === probe).length;
|
||||
expect(count).toBe(1);
|
||||
} finally {
|
||||
localStorage.setItem(SAVED_KEY, JSON.stringify(before));
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user