diff --git a/apps/web/e2e/editor.spec.ts b/apps/web/e2e/editor.spec.ts index 01d08de..f63a0d4 100644 --- a/apps/web/e2e/editor.spec.ts +++ b/apps/web/e2e/editor.spec.ts @@ -3,10 +3,13 @@ import { expect, test } from '@playwright/test'; import { contextForUser } from './helpers'; /** - * TipTap editor pack (issue #25). Runs against the local dev stack (api + - * web); no Mailpit needed. Creates its own page per test via the api (the - * sidebar/"new page" flow is issue #26) and navigates straight to - * `/p/:pondSlug/:pageSlug`. + * TipTap editor pack (issue #25, modernized for live collaboration after + * #36/#38 — the REST autosave and its "saved" indicator are retired; the + * editor syncs over the collab server and reports a connection status). + * Runs against the local dev stack (api + web + collab); no Mailpit needed. + * Creates its own page per test via the api and navigates straight to + * `/p/:pondSlug/:pageSlug`. Status assertions use the language-neutral + * `data-status` attribute — the UI language follows the user's locale. */ const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173'; @@ -31,11 +34,14 @@ test('typing persists across reload and undo/redo work', async ({ browser }) => await page.goto(`/p/${pondSlug}/${pageSlug}`); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); + const status = page.locator('.editor-connection'); + await expect(status).toHaveAttribute('data-status', 'connected', { timeout: 10000 }); + const content = page.locator('.ProseMirror'); await expect(content).toHaveAttribute('contenteditable', 'true'); await content.click(); await page.keyboard.type('Hello editor'); - await expect(page.getByRole('status')).toHaveText(/saved|gespeichert/i, { timeout: 10000 }); + await expect(content).toContainText('Hello editor'); await page.reload(); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); @@ -75,23 +81,33 @@ test('edit mode hides the sidebar; leaving edit mode restores it', async ({ brow await context.close(); }); -test('a save failure shows a truthful error and retries once online again', async ({ browser }) => { +test('going offline is reported honestly and edits sync after reconnect', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const { pondSlug, pageSlug } = await createPage(context, `E2E Offline ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pondSlug}/${pageSlug}`); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); - await page.locator('.ProseMirror').click(); + const status = page.locator('.editor-connection'); + await expect(status).toHaveAttribute('data-status', 'connected', { timeout: 10000 }); + + const content = page.locator('.ProseMirror'); + await content.click(); + + // Editing keeps working offline; the status tells the truth about it. await context.setOffline(true); await page.keyboard.type('offline text'); - await expect(page.getByRole('status')).toHaveText(/failed|retrying|fehlgeschlagen/i, { - timeout: 10000, - }); + await expect(content).toContainText('offline text'); + await expect(status).toHaveAttribute('data-status', 'offline', { timeout: 10000 }); + // Reconnect: the provider resumes and the offline edit reaches the server — + // proven by a reload, which resolves the page against the live document. await context.setOffline(false); - await expect(page.getByRole('status')).toHaveText(/saved|gespeichert/i, { timeout: 10000 }); + await expect(status).toHaveAttribute('data-status', 'connected', { timeout: 20000 }); + await page.reload(); + await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); + await expect(content).toContainText('offline text'); await context.close(); });