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>
181 lines
6.9 KiB
TypeScript
181 lines
6.9 KiB
TypeScript
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' });
|
||
});
|
||
});
|