test(timeline): add /zeitstrahl E2E spec
Nav-link smoke + timeline-in-<main> (empty-or-populated), and the 320px no-overflow guarantee on a timeline seeded with 25+char correspondent names (REQ-005). Runs against the real stack via the seeded admin session. Refs #779 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
84
frontend/e2e/zeitstrahl.spec.ts
Normal file
84
frontend/e2e/zeitstrahl.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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 <main>) 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 <main>', 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 <ol> 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 <ol> in the DOM.
|
||||
await expect(page.getByRole('main').locator('ol')).toHaveCount(1);
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.body.scrollWidth);
|
||||
expect(scrollWidth).toBe(320);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user