fix: preserve search results when background sync runs

Keep canonical notes separate from active search results so periodic
reloads no longer replace the filtered view.

Cancel superseded requests and ignore stale responses to prevent older
queries from overwriting newer results. Reconcile results with canonical
notes so updates propagate and removed or trashed notes disappear.

Add unit and end-to-end coverage for reconciliation, background sync,
and out-of-order search responses.

Co-authored-by: Google Gemini <gemini@google.com>
This commit is contained in:
Romain LE DISEZ
2026-07-15 09:57:00 +02:00
committed by Fred Cabassut
co-authored by Google Gemini
parent 32c39b0000
commit b802f17504
4 changed files with 129 additions and 24 deletions
+52
View File
@@ -31,4 +31,56 @@ test.describe('Search', () => {
// Then no notes are displayed
await expect(page.getByTestId('note-card')).toHaveCount(0);
});
test('Scenario: Background sync preserves active search results', async ({ authenticatedPage: page }) => {
await createNote(page, 'Sync Search Target 3812925');
await createNote(page, 'Sync Search Other 3812925');
await page.getByTestId('search-input').fill('Sync Search Target 3812925');
await expect(page.getByText('Sync Search Target 3812925')).toBeVisible();
await expect(page.getByText('Sync Search Other 3812925')).not.toBeVisible();
const notesReloaded = page.waitForResponse(
(response) => response.url().includes('/api/notes?filter=all') && response.ok()
);
await page.getByTestId('sync-indicator').click();
await notesReloaded;
await expect(page.getByText('Sync Search Target 3812925')).toBeVisible();
await expect(page.getByText('Sync Search Other 3812925')).not.toBeVisible();
});
test('Scenario: A stale response cannot replace newer search results', async ({ authenticatedPage: page }) => {
await createNote(page, 'Race Slow 3812925');
await createNote(page, 'Race Fast 3812925');
const slowResponse = await page.request.get('/api/search?q=Race%20Slow%203812925');
const fastResponse = await page.request.get('/api/search?q=Race%20Fast%203812925');
const slowResults = await slowResponse.json();
const fastResults = await fastResponse.json();
await page.route('**/api/search?*', async (route) => {
const requestQuery = new URL(route.request().url()).searchParams.get('q');
if (requestQuery === 'Race Slow 3812925') {
await new Promise((resolve) => setTimeout(resolve, 250));
await route.fulfill({ json: slowResults });
return;
}
await route.fulfill({ json: fastResults });
});
const searchInput = page.getByTestId('search-input');
const slowRequestStarted = page.waitForRequest(
(request) => new URL(request.url()).searchParams.get('q') === 'Race Slow 3812925'
);
await searchInput.fill('Race Slow 3812925');
await slowRequestStarted;
await searchInput.fill('Race Fast 3812925');
await expect(page.getByText('Race Fast 3812925')).toBeVisible();
await expect(page.getByText('Race Slow 3812925')).not.toBeVisible();
await page.waitForTimeout(300);
await expect(page.getByText('Race Fast 3812925')).toBeVisible();
await expect(page.getByText('Race Slow 3812925')).not.toBeVisible();
});
});