import { test, expect, type APIRequestContext } from '@playwright/test'; /** * Global /zeitstrahl timeline (#779). Runs against the real stack with the * seeded admin session (auth.setup). Covers the primary journey (nav → page, * timeline inside
) and the 320px no-overflow guarantee on a populated * timeline seeded with 25+char correspondent names (REQ-005). */ const stamp = () => new Date().toISOString().replace(/[^0-9]/g, ''); async function createPerson(request: APIRequestContext, firstName: string, lastName: string) { const res = await request.post('/api/persons', { data: { personType: 'PERSON', firstName, lastName } }); if (!res.ok()) throw new Error(`create person failed: ${res.status()}`); return (await res.json()).id as string; } /** Seeds one dated letter with long sender/receiver names so it lands on the timeline. */ async function seedDatedLetter(request: APIRequestContext) { const senderId = await createPerson( request, 'Friedrich-Wilhelm', `Maximilian von Habsburg ${stamp()}` ); const receiverId = await createPerson( request, 'Maria-Magdalena', `Hohenzollern-Sigmaringen ${stamp()}` ); const createRes = await request.post('/api/documents', { multipart: { title: `E2E Zeitstrahl Brief ${stamp()}` } }); if (!createRes.ok()) throw new Error(`create document failed: ${createRes.status()}`); const docId = (await createRes.json()).id as string; const put = await request.put(`/api/documents/${docId}`, { multipart: { title: `E2E Zeitstrahl Brief ${stamp()}`, documentDate: '1915-06-15', metaDatePrecision: 'DAY', senderId, receiverIds: receiverId } }); if (!put.ok()) throw new Error(`update document failed: ${put.status()}`); } test.describe('Zeitstrahl — global timeline (#779)', () => { test('nav link opens /zeitstrahl and the timeline lives in
', async ({ page }) => { await page.goto('/'); await page.getByRole('navigation').getByRole('link', { name: 'Zeitstrahl' }).first().click(); await expect(page).toHaveURL(/\/zeitstrahl$/); await expect(page.getByRole('heading', { level: 1, name: 'Zeitstrahl' })).toBeVisible(); // The main landmark contains either the populated
    or the empty state. const main = page.getByRole('main'); const ol = main.locator('ol'); const empty = main.getByText('Noch keine Ereignisse.'); await expect(async () => { const populated = (await ol.count()) > 0; const isEmpty = await empty.isVisible().catch(() => false); expect(populated || isEmpty).toBe(true); }).toPass(); }); test('no horizontal overflow at 320px with long correspondent names (REQ-005)', async ({ page, request }) => { await seedDatedLetter(request); await page.setViewportSize({ width: 320, height: 900 }); await page.goto('/zeitstrahl'); // Populated: the seeded letter puts the timeline
      in the DOM. await expect(page.getByRole('main').locator('ol')).toHaveCount(1); const scrollWidth = await page.evaluate(() => document.body.scrollWidth); expect(scrollWidth).toBe(320); }); });