Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 1m36s
CI / Backend Unit Tests (pull_request) Failing after 2m36s
CI / E2E Tests (pull_request) Failing after 1h49m0s
Blockers (14): - B1: fix senderName/receiverName to use $derived instead of $state + sync $effect - B2: migrate all korrespondenz components from messages-extra shim to paraglide m.* - B3: i18n CorrespondenzEmptyState (heading, subtext, search placeholder) - B4: add response.ok checks to admin layout server load - B5: add response.ok checks to korrespondenz page server load - B6: add page.server.spec.ts with 5 test suites for korrespondenz load function - B7: add axe-core accessibility checks to all e2e korrespondenz tests - B8: add Testcontainers JPQL tests for findSinglePersonCorrespondence (DISTINCT + sender) - B9: hide auth reset-token endpoint from OpenAPI spec; remove from generated api.ts - B11: replace amber hardcoded hex colors in SinglePersonHintBar with brand tokens - B12: replace clipboard emoji with Heroicons SVG in SinglePersonHintBar - B13: create DateInput component (German dd.mm.yyyy); use it in CorrespondenzFilterControls - B14: add Paraglide compile step to CI workflow before lint/test Suggestions (11): - S1: make CorrespondentSuggestionsDropdown a pure display component; lift fetch to PersonBar - S2: fix leftover messages-extra import in ConversationTimeline; use brand tokens for status dots - S3: add intent comment to EntityNav openFlyout behavior - S4: rename canManageGroups → canManagePermissions throughout admin - S6: remove domFlush helper from DateInput spec; use expect.poll instead - S7: replace test.skip with throw new Error in bilateral e2e tests - S8: add inverse aria-disabled test for filter strip - S9: remove sm:min-h-0 from sort button to preserve 44px touch target - S10: add title attributes to tablet trigger buttons in EntityNav - S11: delete messages-extra.ts shim entirely Also: fix admin pages revealing blank strip at bottom (-mb-6 on admin layout) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
128 lines
5.2 KiB
TypeScript
128 lines
5.2 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
import AxeBuilder from '@axe-core/playwright';
|
||
|
||
function buildAxe(page: Parameters<typeof AxeBuilder>[0]['page']) {
|
||
return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']);
|
||
}
|
||
|
||
test.describe('Korrespondenz – empty state', () => {
|
||
test('shows the search heading when no person is selected', async ({ page }) => {
|
||
await page.goto('/korrespondenz');
|
||
await expect(page.getByText(/Korrespondenz durchsuchen/i)).toBeVisible();
|
||
const a11y = await buildAxe(page).analyze();
|
||
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||
await page.screenshot({ path: 'test-results/e2e/korrespondenz-empty.png' });
|
||
});
|
||
|
||
test('nav link goes to /korrespondenz', async ({ page }) => {
|
||
await page.goto('/');
|
||
// Click the nav link (desktop text or mobile icon)
|
||
const navLink = page.getByRole('link', { name: /Korrespondenz/i }).first();
|
||
await navLink.click();
|
||
await expect(page).toHaveURL(/\/korrespondenz/);
|
||
});
|
||
});
|
||
|
||
test.describe('Korrespondenz – single-person mode', () => {
|
||
test('shows hint bar and documents when navigated with senderId', async ({ page }) => {
|
||
// Get a real person ID from the persons list
|
||
await page.goto('/persons');
|
||
const firstPersonLink = page.locator('a[href^="/persons/"]').first();
|
||
await firstPersonLink.click();
|
||
await page.waitForURL(/\/persons\/.+/);
|
||
|
||
// Extract the person ID from the URL
|
||
const personId = page.url().split('/persons/')[1].split('?')[0];
|
||
|
||
// Navigate to korrespondenz in single-person mode
|
||
await page.goto(`/korrespondenz?senderId=${personId}`);
|
||
|
||
// Hint bar should be visible
|
||
await expect(page.getByText(/Alle Briefe von/i)).toBeVisible();
|
||
|
||
// Filter controls should be active (not dimmed)
|
||
const filterStrip = page.locator('[aria-disabled="false"]').first();
|
||
await expect(filterStrip).toBeAttached();
|
||
|
||
const a11y = await buildAxe(page).analyze();
|
||
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||
await page.screenshot({ path: 'test-results/e2e/korrespondenz-single-person.png' });
|
||
});
|
||
|
||
test('sort toggle changes URL direction param', async ({ page }) => {
|
||
await page.goto('/persons');
|
||
const firstPersonLink = page.locator('a[href^="/persons/"]').first();
|
||
await firstPersonLink.click();
|
||
await page.waitForURL(/\/persons\/.+/);
|
||
const personId = page.url().split('/persons/')[1].split('?')[0];
|
||
|
||
await page.goto(`/korrespondenz?senderId=${personId}&dir=DESC`);
|
||
await page.getByTestId('conv-sort-btn').click();
|
||
|
||
await expect(page).toHaveURL(/dir=ASC/);
|
||
await page.screenshot({ path: 'test-results/e2e/korrespondenz-sort-asc.png' });
|
||
});
|
||
});
|
||
|
||
test.describe('Korrespondenz – bilateral mode', () => {
|
||
test('shows asymmetry bar when both persons have shared documents', async ({ page }) => {
|
||
// Navigate to a person then follow a co-correspondent suggestion if available
|
||
await page.goto('/persons');
|
||
const firstPersonLink = page.locator('a[href^="/persons/"]').first();
|
||
await firstPersonLink.click();
|
||
await page.waitForURL(/\/persons\/.+/);
|
||
const senderId = page.url().split('/persons/')[1].split('?')[0];
|
||
|
||
// Try to find a co-correspondent link from the person detail page
|
||
const corrLink = page
|
||
.locator('a[href*="/korrespondenz?senderId="][href*="receiverId="]')
|
||
.first();
|
||
if (await corrLink.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||
await corrLink.click();
|
||
await page.waitForURL(/\/korrespondenz\?.*receiverId=/);
|
||
|
||
// Hint bar should NOT be shown in bilateral mode
|
||
await expect(page.getByText(/Alle Briefe von/i)).not.toBeVisible();
|
||
|
||
const a11y = await buildAxe(page).analyze();
|
||
expect(a11y.violations, JSON.stringify(a11y.violations, null, 2)).toHaveLength(0);
|
||
await page.screenshot({ path: 'test-results/e2e/korrespondenz-bilateral.png' });
|
||
} else {
|
||
// E2E seed must include bilateral correspondents — a missing link is a test failure.
|
||
throw new Error(
|
||
`No bilateral correspondent links found for person ${senderId}. Ensure the E2E seed contains at least one bilateral correspondence pair.`
|
||
);
|
||
}
|
||
});
|
||
|
||
test('swap button swaps sender and receiver in URL', async ({ page }) => {
|
||
await page.goto('/persons');
|
||
const firstPersonLink = page.locator('a[href^="/persons/"]').first();
|
||
await firstPersonLink.click();
|
||
await page.waitForURL(/\/persons\/.+/);
|
||
const senderId = page.url().split('/persons/')[1].split('?')[0];
|
||
|
||
const corrLink = page
|
||
.locator('a[href*="/korrespondenz?senderId="][href*="receiverId="]')
|
||
.first();
|
||
if (await corrLink.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||
const href = await corrLink.getAttribute('href');
|
||
await corrLink.click();
|
||
await page.waitForURL(/\/korrespondenz\?.*receiverId=/);
|
||
|
||
// Extract original receiverId from the href
|
||
const url = new URL(href!, 'http://x');
|
||
const originalReceiverId = url.searchParams.get('receiverId')!;
|
||
|
||
// Click swap
|
||
await page.getByTestId('conv-swap-btn').click();
|
||
|
||
// After swap the former receiver is now senderId
|
||
await expect(page).toHaveURL(new RegExp(`senderId=${originalReceiverId}`));
|
||
await page.screenshot({ path: 'test-results/e2e/korrespondenz-swapped.png' });
|
||
} else {
|
||
test.skip(true, `No bilateral correspondent links found for person ${senderId}`);
|
||
}
|
||
});
|
||
});
|