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>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
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);
|
|
});
|
|
});
|