Files
bea-edit/tests/e2e/pagination.spec.ts
T
2026-10-28 16:09:53 +01:00

198 lines
8.0 KiB
TypeScript
Raw 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 { expect, test, type Page } from '@playwright/test';
import { PDFDocument } from 'pdf-lib';
import { PAGE_NUMBER_MARGIN } from '../../src/lib/pdf-pagination';
import {
createEncryptedPdf,
createPdf,
createPreRotatedPdf,
dropFiles,
expectPdfDownload,
getPageContentText,
getPageText,
getPageTextAngle,
getPageTextMatrices,
getPageTextValues,
pdfDropzone,
trackPageErrors
} from './helpers';
/**
* Number of text operands equal to `label` on a page. The source documents carry
* their own text, so this counts the stamped sheet label instead of comparing
* the whole page.
*/
const countLabel = (document: PDFDocument, pageIndex: number, label: string) =>
getPageTextValues(document, pageIndex).filter((value) => value === label).length;
const downloadPaginated = async (page: Page, fileName: RegExp) => {
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Paginieren & herunterladen' }).click();
return expectPdfDownload(await downloadPromise, fileName);
};
test('pagination stamps every page with an Aktenzeichen and the configured start number', async ({
page
}) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{ name: 'akte.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Akte') }
]);
await expect(page.getByText('Seite 1 von 3')).toBeVisible();
await page.locator('#pagination-start').fill('26');
await page.locator('#pagination-text').fill('123 Ja 60/26');
await expect(page.getByTestId('pagination-example')).toContainText(
'Beispiel: 123 Ja 60/26, Bl. 26'
);
await page.getByRole('button', { name: 'Nächste Seite' }).click();
await expect(page.getByText('Seite 2 von 3')).toBeVisible();
await expect(page.getByTestId('pagination-preview')).toContainText('123 Ja 60/26, Bl. 27');
const bytes = await downloadPaginated(page, /^akte_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(output.getPageCount()).toBe(3);
// Exactly one label per page: a duplicated draw would still pass `toContain`.
expect(countLabel(output, 0, '123 Ja 60/26, Bl. 26')).toBe(1);
expect(countLabel(output, 1, '123 Ja 60/26, Bl. 27')).toBe(1);
expect(countLabel(output, 2, '123 Ja 60/26, Bl. 28')).toBe(1);
expect(pageErrors).toEqual([]);
});
test('pagination can keep the additional text on the first page only', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{ name: 'akte.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Akte') }
]);
await page.locator('#pagination-start').fill('7');
await page.locator('#pagination-text').fill('AZ 7/26');
await page.locator('#pagination-format').selectOption('s');
await page.getByLabel('Nur auf der ersten Seite').check();
await expect(page.getByTestId('pagination-example')).toContainText('AZ 7/26, S. 7');
await page.getByRole('button', { name: 'Nächste Seite' }).click();
await expect(page.getByTestId('pagination-preview')).toHaveText('S. 8');
const bytes = await downloadPaginated(page, /^akte_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(output.getPageCount()).toBe(3);
expect(getPageText(output, 0)).toContain('AZ 7/26, S. 7');
expect(getPageText(output, 1)).toContain('S. 8');
expect(getPageText(output, 1)).not.toContain('AZ 7/26');
expect(getPageText(output, 2)).toContain('S. 9');
expect(pageErrors).toEqual([]);
});
test('pagination supports number-only labels and jumps to a page', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{ name: 'nummern.pdf', mimeType: 'application/pdf', buffer: await createPdf(11, 'Nummern') }
]);
await page.locator('#pagination-start').fill('42');
await page.locator('#pagination-format').selectOption('nummer');
await expect(page.getByTestId('pagination-example')).toHaveText('Beispiel: 42');
await page.getByLabel('Springe zu Seite').fill('11');
await expect(page.getByText('Seite 11 von 11')).toBeVisible();
await expect(page.getByTestId('pagination-preview')).toHaveText('52');
const bytes = await downloadPaginated(page, /^nummern_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(output.getPageCount()).toBe(11);
expect(getPageText(output, 0)).toContain('42');
expect(getPageText(output, 10)).toContain('52');
expect(pageErrors).toEqual([]);
});
test('pagination keeps the page number upright on rotated scans', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{
name: 'gedreht.pdf',
mimeType: 'application/pdf',
buffer: await createPreRotatedPdf(2, 'Gedreht')
}
]);
const bytes = await downloadPaginated(page, /^gedreht_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(output.getPageCount()).toBe(2);
// `createPreRotatedPdf` stores 420×594 pages and puts /Rotate 90 on page 1, so
// the page is displayed as 594×420. The label is counter-rotated so it reads
// horizontally on screen instead of sideways.
// The stamp is the last text drawn on the page.
const rotated = getPageTextMatrices(output, 0).at(-1)!;
expect(Math.round(getPageTextAngle(rotated))).toBe(90);
expect(countLabel(output, 0, 'Bl. 1')).toBe(1);
// Visual bottom centre maps back into user space as x = stored width − margin.
expect(rotated.x).toBeCloseTo(420 - PAGE_NUMBER_MARGIN, -1);
// …and stays horizontally centred on the displayed width.
expect(Math.abs(rotated.y - 594 / 2)).toBeLessThan(15);
const plain = getPageTextMatrices(output, 1).at(-1)!;
expect(Math.round(getPageTextAngle(plain))).toBe(0);
expect(countLabel(output, 1, 'Bl. 2')).toBe(1);
expect(pageErrors).toEqual([]);
});
test('pagination offers the "Seite" format and applies it to every page', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{ name: 'formate.pdf', mimeType: 'application/pdf', buffer: await createPdf(2, 'Formate') }
]);
await page.locator('#pagination-format').selectOption('seite');
await expect(page.getByTestId('pagination-example')).toHaveText('Beispiel: Seite 1');
const bytes = await downloadPaginated(page, /^formate_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(countLabel(output, 0, 'Seite 1')).toBe(1);
expect(countLabel(output, 1, 'Seite 2')).toBe(1);
expect(pageErrors).toEqual([]);
});
test('pagination explains how to handle a password-protected PDF', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/pagination');
await dropFiles(page, pdfDropzone(page), [
{
name: 'geschuetzt.pdf',
mimeType: 'application/pdf',
buffer: await createEncryptedPdf(1, 'Geschützt', 'test-passwort')
}
]);
await expect(page.getByRole('alert')).toContainText('passwortgeschützt');
await expect(page.getByRole('alert')).toContainText('Passwort entfernen');
expect(pageErrors).toEqual([]);
});
test('merge hands the merged PDF over to pagination', async ({ page }) => {
const pageErrors = trackPageErrors(page);
await page.goto('/tools/merge');
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(2, 'Zwei') }
]);
await page.getByRole('button', { name: 'Zusammenfügen & paginieren' }).click();
await expect(page).toHaveURL(/\/tools\/pagination$/);
await expect(page.getByText('Seite 1 von 3')).toBeVisible({ timeout: 15_000 });
const bytes = await downloadPaginated(page, /^zusammengefuegt_paginiert\.pdf$/);
const output = await PDFDocument.load(bytes);
expect(output.getPageCount()).toBe(3);
expect(getPageText(output, 0)).toContain('Bl. 1');
expect(getPageText(output, 1)).toContain('Bl. 2');
expect(getPageText(output, 2)).toContain('Bl. 3');
expect(pageErrors).toEqual([]);
});