Files
crumbs/tests/e2e/offline.spec.ts
T
Frederic CabassutandClaude Opus 4.6 c49e127d0f feat: offline-first note loading from IDB cache
Load notes from IndexedDB first for instant display, then sync from
server in background. All mutations now persist to IDB when online,
keeping the cache always current. Eliminates empty state flash on load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00

120 lines
4.7 KiB
TypeScript

import { test, expect } from './helpers/fixtures.js';
test.describe('Offline Support', () => {
test('Scenario: Sync status is visible to the user', async ({ authenticatedPage: page }) => {
// Given the user is authenticated
// Then the sync status indicator is displayed
await expect(page.getByTestId('sync-indicator')).toBeVisible();
});
test('Scenario: Created note is immediately visible in the notes list', async ({ authenticatedPage: page }) => {
// When the user creates a note titled "Offline Test Note"
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Offline Test Note');
await page.getByTestId('close-editor-btn').click();
// Then the note appears in the list
await expect(page.getByText('Offline Test Note')).toBeVisible();
});
test('Scenario: Edited note persists in the UI when offline', async ({ authenticatedPage: page }) => {
// Given a note titled "Offline Edit" exists
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Offline Edit');
await page.getByTestId('close-editor-btn').click();
await expect(page.getByText('Offline Edit')).toBeVisible();
// When the network is unavailable
await page.context().setOffline(true);
// And the user edits the note title to "Offline Edited"
await page.getByTestId('note-card').click();
await page.getByTestId('note-title-input').clear();
await page.getByTestId('note-title-input').fill('Offline Edited');
await page.getByTestId('close-editor-btn').click();
// Then the note "Offline Edited" appears in the list
await expect(page.getByText('Offline Edited')).toBeVisible();
// And a "Saved offline" toast is shown
await expect(page.getByText('Saved offline')).toBeVisible();
});
test('Scenario: New note appears in the UI when created offline', async ({ authenticatedPage: page }) => {
// When the network is unavailable
await page.context().setOffline(true);
// And the user creates a note titled "Offline New"
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Offline New');
await page.getByTestId('close-editor-btn').click();
// Then the note "Offline New" appears in the list
await expect(page.getByText('Offline New')).toBeVisible();
// And a "Saved offline" toast is shown
await expect(page.getByText('Saved offline')).toBeVisible();
});
test('Scenario: Offline note survives a page reload after reconnecting', async ({ authenticatedPage: page }) => {
// When the network is unavailable
await page.context().setOffline(true);
// And the user creates a note titled "Survive Reload"
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Survive Reload');
await page.getByTestId('close-editor-btn').click();
await expect(page.getByText('Survive Reload')).toBeVisible();
// When the network is restored (triggers 'online' event + navigator.onLine = true)
await page.context().setOffline(false);
// And the sync completes
await page.waitForResponse('**/api/sync');
// And the user reloads the page
await page.reload();
// Then the note is still visible
await expect(page.getByText('Survive Reload')).toBeVisible();
});
test('Scenario: Notes load from local cache when the API is unavailable', async ({ authenticatedPage: page }) => {
// Given a note titled "Cached Note" exists
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Cached Note');
await page.getByTestId('close-editor-btn').click();
await expect(page.getByText('Cached Note')).toBeVisible();
// When the API is unavailable (simulating service worker serving cached shell)
await page.route('**/api/**', (route) => route.abort());
// And the user reloads the page
await page.reload();
// Then the note is still visible (loaded from IDB cache)
await expect(page.getByText('Cached Note')).toBeVisible();
});
test('Scenario: Trashed note disappears from the list when offline', async ({ authenticatedPage: page }) => {
// Given a note titled "Offline Trash" exists
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Offline Trash');
await page.getByTestId('close-editor-btn').click();
await expect(page.getByText('Offline Trash')).toBeVisible();
// When the network is unavailable
await page.context().setOffline(true);
// And the user trashes the note
await page.getByTestId('note-card').hover();
await page.getByTestId('trash-btn').click();
// Then the note is no longer visible
await expect(page.getByText('Offline Trash')).not.toBeVisible();
// And a "Saved offline" toast is shown
await expect(page.getByText('Saved offline')).toBeVisible();
});
});