feat(briefwechsel): thumbnail rows with summary quote and bilateral distribution bar (#305) #311
@@ -1,8 +1,13 @@
|
|||||||
import AxeBuilder from '@axe-core/playwright';
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
import {
|
||||||
|
seedBilateralPair,
|
||||||
|
cleanupBilateralPair,
|
||||||
|
type BilateralPair
|
||||||
|
} from './fixtures/bilateral-correspondence';
|
||||||
|
|
||||||
// Accessibility coverage specifically for the briefwechsel thumbnail-row
|
// Accessibility coverage for the briefwechsel thumbnail-row layout. Seeds
|
||||||
// layout. Seeds two persons + a bilateral document via the API so the page
|
// two persons + a bilateral document via the shared fixture so the page
|
||||||
// reaches the results state (not the hero), then runs axe-core
|
// reaches the results state (not the hero), then runs axe-core
|
||||||
// (wcag2a + wcag2aa) across three viewports and two color schemes.
|
// (wcag2a + wcag2aa) across three viewports and two color schemes.
|
||||||
|
|
||||||
@@ -14,33 +19,15 @@ const VIEWPORTS = [
|
|||||||
|
|
||||||
const THEMES = ['light', 'dark'] as const;
|
const THEMES = ['light', 'dark'] as const;
|
||||||
|
|
||||||
let senderId: string;
|
let pair: BilateralPair;
|
||||||
let receiverId: string;
|
|
||||||
|
|
||||||
test.describe('Accessibility — /briefwechsel row layout', () => {
|
test.describe('Accessibility — /briefwechsel row layout', () => {
|
||||||
test.beforeAll(async ({ request }) => {
|
test.beforeAll(async ({ request }) => {
|
||||||
const timestamp = Date.now();
|
pair = await seedBilateralPair(request, 'A11y');
|
||||||
const senderRes = await request.post('/api/persons', {
|
|
||||||
data: { firstName: 'A11y', lastName: `Sender-${timestamp}` }
|
|
||||||
});
|
});
|
||||||
if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`);
|
|
||||||
senderId = (await senderRes.json()).id;
|
|
||||||
|
|
||||||
const receiverRes = await request.post('/api/persons', {
|
test.afterAll(async ({ request }) => {
|
||||||
data: { firstName: 'A11y', lastName: `Receiver-${timestamp}` }
|
await cleanupBilateralPair(request, pair);
|
||||||
});
|
|
||||||
if (!receiverRes.ok()) throw new Error(`Create receiver failed: ${receiverRes.status()}`);
|
|
||||||
receiverId = (await receiverRes.json()).id;
|
|
||||||
|
|
||||||
const docRes = await request.post('/api/documents', {
|
|
||||||
multipart: {
|
|
||||||
title: 'A11y Test Brief',
|
|
||||||
documentDate: '1950-06-15',
|
|
||||||
senderId,
|
|
||||||
receiverIds: receiverId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const vp of VIEWPORTS) {
|
for (const vp of VIEWPORTS) {
|
||||||
@@ -49,7 +36,7 @@ test.describe('Accessibility — /briefwechsel row layout', () => {
|
|||||||
await page.setViewportSize({ width: vp.width, height: vp.height });
|
await page.setViewportSize({ width: vp.width, height: vp.height });
|
||||||
await page.emulateMedia({ colorScheme: theme });
|
await page.emulateMedia({ colorScheme: theme });
|
||||||
await page.goto(
|
await page.goto(
|
||||||
`/briefwechsel?senderId=${encodeURIComponent(senderId)}&receiverId=${encodeURIComponent(receiverId)}`
|
`/briefwechsel?senderId=${encodeURIComponent(pair.senderId)}&receiverId=${encodeURIComponent(pair.receiverId)}`
|
||||||
);
|
);
|
||||||
await page.waitForSelector('[data-hydrated]');
|
await page.waitForSelector('[data-hydrated]');
|
||||||
|
|
||||||
|
|||||||
62
frontend/e2e/fixtures/bilateral-correspondence.ts
Normal file
62
frontend/e2e/fixtures/bilateral-correspondence.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import type { APIRequestContext } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test fixture for the briefwechsel row layout.
|
||||||
|
*
|
||||||
|
* Creates two persons and one document with sender/receiver between them so
|
||||||
|
* that `/briefwechsel?senderId=X&receiverId=Y` navigates straight to the row
|
||||||
|
* state (not the hero). Each seed uses a `Date.now()`-suffixed last name so
|
||||||
|
* parallel runs and reruns never collide.
|
||||||
|
*
|
||||||
|
* The backend does not expose a person-delete endpoint, so only the document
|
||||||
|
* is cleaned up in {@link cleanupBilateralPair}. The two timestamped persons
|
||||||
|
* remain in the DB — acceptable for the test environment, and the unique
|
||||||
|
* suffix means they cannot conflict with later runs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface BilateralPair {
|
||||||
|
senderId: string;
|
||||||
|
receiverId: string;
|
||||||
|
documentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function seedBilateralPair(
|
||||||
|
request: APIRequestContext,
|
||||||
|
prefix: string
|
||||||
|
): Promise<BilateralPair> {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
const senderRes = await request.post('/api/persons', {
|
||||||
|
data: { firstName: prefix, lastName: `Sender-${timestamp}` }
|
||||||
|
});
|
||||||
|
if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`);
|
||||||
|
const senderId = (await senderRes.json()).id as string;
|
||||||
|
|
||||||
|
const receiverRes = await request.post('/api/persons', {
|
||||||
|
data: { firstName: prefix, lastName: `Receiver-${timestamp}` }
|
||||||
|
});
|
||||||
|
if (!receiverRes.ok()) throw new Error(`Create receiver failed: ${receiverRes.status()}`);
|
||||||
|
const receiverId = (await receiverRes.json()).id as string;
|
||||||
|
|
||||||
|
const docRes = await request.post('/api/documents', {
|
||||||
|
multipart: {
|
||||||
|
title: `${prefix} Brief`,
|
||||||
|
documentDate: '1950-06-15',
|
||||||
|
senderId,
|
||||||
|
receiverIds: receiverId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`);
|
||||||
|
const documentId = (await docRes.json()).id as string;
|
||||||
|
|
||||||
|
return { senderId, receiverId, documentId };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupBilateralPair(
|
||||||
|
request: APIRequestContext,
|
||||||
|
pair: BilateralPair
|
||||||
|
): Promise<void> {
|
||||||
|
// Only the document is purged — the backend has no person-delete endpoint
|
||||||
|
// and the timestamped last names make orphaned person rows safe to leave.
|
||||||
|
await request.delete(`/api/documents/${pair.documentId}`);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user