forked from mirror/crumbs-original
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>
120 lines
4.7 KiB
TypeScript
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();
|
|
});
|
|
});
|