Files
familienarchiv/frontend/e2e/bottom-panel.spec.ts
Marcel 8c2bdbd777
Some checks failed
CI / Unit & Component Tests (push) Successful in 4m47s
CI / Backend Unit Tests (push) Successful in 2m20s
CI / E2E Tests (push) Failing after 24m42s
feat(frontend): add floating bottom panel to document detail page
Replaces the left sidebar layout with:
- Full-viewport PDF/image viewer (never resizes, position: absolute)
- Fixed floating bottom panel with tabs: Metadaten, Transkription,
  Diskussion, Verlauf
- Compact top bar with title, date · sender → receivers row, and
  Annotieren / Edit / Download actions
- Drag-to-resize panel with localStorage persistence of open/height/tab
- Panel opens automatically to Diskussion when an annotation is clicked
- Documents without a file default to showing the Metadaten tab

New components: DocumentTopBar, DocumentViewer, DocumentBottomPanel,
PanelMetadata, PanelTranscription, PanelDiscussion, PanelHistory

PdfViewer: annotateMode and activeAnnotationId lifted to bindable props;
AnnotationCommentPanel removed (discussion moves to the Diskussion tab).

Closes #62
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 22:35:28 +01:00

181 lines
6.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
/**
* Bottom panel E2E tests — issue #62.
* Verifies the new document detail layout: full-viewport viewer + floating bottom panel.
*/
let pdfDocHref: string;
let noFileDocHref: string;
test.describe('Document bottom panel', () => {
test.beforeAll(async ({ request }) => {
const baseURL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
// Create a document with a PDF and a date for metadata tests.
const createRes = await request.post('/api/documents', {
multipart: { title: 'E2E Bottom Panel Test', documentDate: '1945-05-08' }
});
if (!createRes.ok()) throw new Error(`Create document failed: ${createRes.status()}`);
const doc = await createRes.json();
const uploadRes = await request.put(`/api/documents/${doc.id}`, {
multipart: {
title: doc.title,
documentDate: '1945-05-08',
transcription: 'Dies ist eine vollständige Transkription des Dokuments für den E2E-Test.',
file: {
name: 'minimal.pdf',
mimeType: 'application/pdf',
buffer: fs.readFileSync(PDF_FIXTURE)
}
}
});
if (!uploadRes.ok()) throw new Error(`Upload PDF failed: ${uploadRes.status()}`);
pdfDocHref = `${baseURL}/documents/${doc.id}`;
// Create a document WITHOUT a file — panel should open to Metadaten by default.
const noFileRes = await request.post('/api/documents', {
multipart: { title: 'E2E Bottom Panel No-File Test' }
});
if (!noFileRes.ok()) throw new Error(`Create no-file document failed: ${noFileRes.status()}`);
noFileDocHref = `${baseURL}/documents/${noFileRes.json().then ? (await noFileRes.json()).id : ''}`;
const noFileDoc = await noFileRes.json();
noFileDocHref = `${baseURL}/documents/${noFileDoc.id}`;
});
test('bottom panel tab bar is visible and panel content is closed by default on a PDF document', async ({
page
}) => {
test.setTimeout(30_000);
// Clear localStorage to ensure no previous panel state.
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
// Tab bar must always be visible.
await expect(page.getByRole('button', { name: 'Metadaten' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Transkription' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Diskussion' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Verlauf' })).toBeVisible();
// Panel content must NOT be visible when closed.
await expect(page.locator('[data-testid="bottom-panel-content"]')).not.toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-closed-default.png' });
});
test('clicking Metadaten tab opens the panel and shows metadata content', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
await page.getByRole('button', { name: 'Metadaten' }).click();
// Panel content becomes visible.
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
// Metadata section heading should be present.
await expect(page.getByText('Details', { exact: false })).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-metadata.png' });
});
test('clicking Transkription tab shows transcription text', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
await page.getByRole('button', { name: 'Transkription' }).click();
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
await expect(
page.getByText('Dies ist eine vollständige Transkription', { exact: false })
).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-transcription.png' });
});
test('clicking Diskussion tab shows the comment input', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
await page.getByRole('button', { name: 'Diskussion' }).click();
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
await expect(page.getByPlaceholder('Kommentar schreiben…')).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-discussion.png' });
});
test('clicking × close button collapses the panel content', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
// Open the panel first.
await page.getByRole('button', { name: 'Metadaten' }).click();
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
// Close it.
await page.locator('[data-testid="panel-close-btn"]').click();
await expect(page.locator('[data-testid="bottom-panel-content"]')).not.toBeVisible();
// Tab bar still visible after closing.
await expect(page.getByRole('button', { name: 'Metadaten' })).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-closed-after-x.png' });
});
test('panel open state persists after page reload', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(pdfDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
// Open the panel to Diskussion.
await page.getByRole('button', { name: 'Diskussion' }).click();
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
// Reload — panel should re-open on the same tab.
await page.reload();
await page.waitForSelector('[data-hydrated]');
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
await expect(page.getByPlaceholder('Kommentar schreiben…')).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-persisted.png' });
});
test('document without a file opens panel to Metadaten by default', async ({ page }) => {
test.setTimeout(30_000);
await page.goto(noFileDocHref);
await page.evaluate(() => localStorage.clear());
await page.reload();
await page.waitForSelector('[data-hydrated]');
// Panel should be open to Metadaten by default when there is no file.
await expect(page.locator('[data-testid="bottom-panel-content"]')).toBeVisible();
await expect(page.getByText('Details', { exact: false })).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/bottom-panel-no-file-default.png' });
});
});