refactoring, numbering plan

This commit is contained in:
rehlert committed 2026-10-28 16:09:53 +01:00
1 parent b5279434b2
commit 3b04039aa9
47 files changed
+3485 -953

No files matched your search

+211
View File
@@ -0,0 +1,211 @@
import { expect, test } from '@playwright/test';
import { unzipSync } from 'fflate';
import { readFile } from 'node:fs/promises';
import { PDFDocument } from 'pdf-lib';
import {
createEncryptedPdf,
createPdf,
dropFiles,
expectPdfDownload,
getPageContentStreamCount,
getPageText,
pdfDropzone,
trackPageErrors
} from './helpers';
const row = (page: import('@playwright/test').Page, fileName: string) =>
page.locator(`[data-exhibit-item="${fileName}"]`);
const labelOf = (page: import('@playwright/test').Page, fileName: string) =>
row(page, fileName).getAttribute('data-exhibit-label');
const renamedOf = (page: import('@playwright/test').Page, fileName: string) =>
row(page, fileName).locator('[data-exhibit-filename]').innerText();
test('exhibits numbers dropped PDFs, follows the list order and renames the downloads', async ({
page
}) => {
const pageErrors = trackPageErrors(page);
const zweiBytes = await createPdf(2, 'Zwei');
const zweiInput = await PDFDocument.load(zweiBytes);
const originalStreamCounts = zweiInput
.getPages()
.map((_, pageIndex) => getPageContentStreamCount(zweiInput, pageIndex));
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'eins.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Eins') },
{ name: 'zwei.pdf', mimeType: 'application/pdf', buffer: zweiBytes },
{ name: 'drei.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Drei') }
]);
await expect(page.locator('[data-exhibit-item]')).toHaveCount(3);
expect(await labelOf(page, 'eins.pdf')).toBe('Anlage 1');
await page.getByRole('button', { name: 'Anlage K', exact: true }).click();
await page.locator('#exhibit-start-number').fill('6');
await expect(row(page, 'eins.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage K 6');
await expect(row(page, 'drei.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage K 8');
expect(await renamedOf(page, 'zwei.pdf')).toBe('→ Anlage_K_07_zwei.pdf');
// Reordering the second file to the front renumbers the pending rows.
await page.getByRole('button', { name: 'zwei.pdf nach oben verschieben' }).click();
await expect(row(page, 'zwei.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage K 6');
await expect(row(page, 'eins.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage K 7');
expect(await renamedOf(page, 'zwei.pdf')).toBe('→ Anlage_K_06_zwei.pdf');
// The preview renders only the selected Anlage, page 1, on demand.
await page.getByRole('button', { name: 'Vorschau für zwei.pdf' }).click();
await expect(page.getByTestId('exhibit-preview')).toContainText('Anlage K 6');
await expect(page.getByTestId('exhibit-preview')).toHaveAttribute('data-position', 'top-right');
await expect(page.getByText('Seite 1 von 2')).toBeVisible();
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(3, { timeout: 30_000 });
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'zwei.pdf herunterladen' }).click();
const outputBytes = await expectPdfDownload(await downloadPromise, /^Anlage_K_06_zwei\.pdf$/);
const output = await PDFDocument.load(outputBytes);
expect(output.getPageCount()).toBe(2);
expect(getPageText(output, 0)).toContain('Anlage K 6');
// Only the first page is stamped by default; page 2 keeps its stream count.
expect(getPageContentStreamCount(output, 1)).toBe(originalStreamCounts[1]);
expect(getPageText(output, 1)).not.toContain('Anlage K 6');
expect(pageErrors).toEqual([]);
});
test('exhibits switches to three digits from 98 and keeps labels unpadded', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'a.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'A') },
{ name: 'b.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'B') },
{ name: 'c.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'C') }
]);
await page.locator('#exhibit-start-number').fill('98');
await expect(row(page, 'a.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage 98');
expect(await renamedOf(page, 'a.pdf')).toBe('→ Anlage_098_a.pdf');
expect(await renamedOf(page, 'c.pdf')).toBe('→ Anlage_100_c.pdf');
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(3, { timeout: 30_000 });
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'a.pdf herunterladen' }).click();
const first = await expectPdfDownload(await downloadPromise, /^Anlage_098_a\.pdf$/);
expect(getPageText(await PDFDocument.load(first), 0)).toContain('Anlage 98');
const secondPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'c.pdf herunterladen' }).click();
await expectPdfDownload(await secondPromise, /^Anlage_100_c\.pdf$/);
expect(pageErrors).toEqual([]);
});
test('exhibits keeps the original names and honours the every-page option', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'original.pdf', mimeType: 'application/pdf', buffer: await createPdf(2, 'Original') }
]);
await page.getByRole('checkbox', { name: 'Dateien umbenennen' }).uncheck();
await expect(row(page, 'original.pdf').locator('[data-exhibit-filename]')).toHaveCount(0);
await page.getByRole('checkbox', { name: 'Auf jeder Seite stempeln' }).check();
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toBeVisible({ timeout: 20_000 });
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'original.pdf herunterladen' }).click();
const outputBytes = await expectPdfDownload(await downloadPromise, /^original\.pdf$/);
const output = await PDFDocument.load(outputBytes);
expect(getPageText(output, 0)).toContain('Anlage 1');
expect(getPageText(output, 1)).toContain('Anlage 1');
expect(pageErrors).toEqual([]);
});
test('exhibits freezes the number of finished rows and says so', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'erst.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Erst') },
{ name: 'dann.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Dann') }
]);
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(2, { timeout: 20_000 });
await expect(row(page, 'erst.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage 1');
await page.getByRole('button', { name: 'dann.pdf nach oben verschieben' }).click();
await expect(row(page, 'dann.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage 2');
await expect(row(page, 'erst.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage 1');
expect(await renamedOf(page, 'dann.pdf')).toBe('→ Anlage_02_dann.pdf');
expect(pageErrors).toEqual([]);
});
test('exhibits blocks an empty designation and recovers from a broken file', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'gut.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Gut') },
{ name: 'kaputt.pdf', mimeType: 'application/pdf', buffer: Buffer.from('kein PDF') }
]);
await page.locator('#exhibit-prefix').fill(' ');
await expect(page.getByText('Bitte geben Sie eine Bezeichnung an')).toBeVisible();
await expect(page.getByRole('button', { name: 'Anlagen erstellen', exact: true })).toBeDisabled();
await page.locator('#exhibit-prefix').fill('Anlage B');
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(1, { timeout: 20_000 });
await expect(row(page, 'kaputt.pdf')).toContainText('Fehler');
await expect(row(page, 'gut.pdf')).toHaveAttribute('data-exhibit-label', 'Anlage B 1');
expect(pageErrors).toEqual([]);
});
test('exhibits bundles all finished Anlagen in one ZIP', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{ name: 'eins.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Eins') },
{ name: 'zwei.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Zwei') }
]);
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(2, { timeout: 20_000 });
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Alle herunterladen' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toMatch(/^anlagen_\d{4}-\d{2}-\d{2}\.zip$/);
const zipPath = await download.path();
expect(zipPath).not.toBeNull();
expect(Object.keys(unzipSync(new Uint8Array(await readFile(zipPath!)))).sort()).toEqual([
'Anlage_01_eins.pdf',
'Anlage_02_zwei.pdf'
]);
expect(pageErrors).toEqual([]);
});
test('exhibits reports encrypted PDFs as row errors and keeps going', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/exhibits');
await dropFiles(page, pdfDropzone(page), [
{
name: 'gesperrt.pdf',
mimeType: 'application/pdf',
buffer: await createEncryptedPdf(1, 'Gesperrt', 'geheim')
},
{ name: 'offen.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Offen') }
]);
await page.getByRole('button', { name: 'Anlagen erstellen', exact: true }).click();
await expect(page.getByText('Fertig ✓')).toHaveCount(1, { timeout: 20_000 });
await expect(row(page, 'gesperrt.pdf')).toContainText('passwortgeschützt');
expect(pageErrors).toEqual([]);
});