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

132 lines
4.3 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { PDFDocument } from 'pdf-lib';
import { TextOverlayError } from '../../src/lib/pdf-errors';
import type { OverlayPosition } from '../../src/lib/pdf-overlay';
import {
MAX_FREE_TEXT_LENGTH,
PAGINATION_FORMATS,
buildPaginationLabel,
normalizeFreeText,
normalizeStartNumber,
paginatePdf,
type PaginationFormat,
type PaginationOptions,
type PaginationTextScope
} from '../../src/lib/pdf-pagination';
import { getPageTextValues } from '../e2e/helpers';
const labelOptions = (
overrides: Partial<{
startNumber: number;
freeText: string;
textScope: PaginationTextScope;
format: PaginationFormat;
}> = {}
) => ({
startNumber: 1,
freeText: '',
textScope: 'all' as const,
format: 'blatt' as const,
...overrides
});
const paginationOptions = (overrides: Partial<PaginationOptions> = {}): PaginationOptions => ({
startNumber: 1,
freeText: '',
textScope: 'all',
format: 'blatt',
position: 'bottom-center' as OverlayPosition,
fontSize: 11,
...overrides
});
/** Blank pages, so the stamped sheet number is the only text in the output. */
const createPdf = async (pageCount: number, size: [number, number] = [420, 594]) => {
const document = await PDFDocument.create();
for (let index = 0; index < pageCount; index += 1) document.addPage(size);
return new Uint8Array(await document.save());
};
test('every format is offered with a rendered example label', () => {
expect(PAGINATION_FORMATS.map((format) => format.value)).toEqual([
'blatt',
'seite',
's',
'nummer'
]);
expect(PAGINATION_FORMATS.map((format) => format.label)).toEqual([
'Bl. 1',
'Seite 1',
'S. 1',
'1'
]);
});
test('label combines the free text with the ascending sheet number', () => {
expect(buildPaginationLabel(0, labelOptions({ startNumber: 26 }))).toBe('Bl. 26');
expect(buildPaginationLabel(2, labelOptions({ startNumber: 26 }))).toBe('Bl. 28');
expect(buildPaginationLabel(0, labelOptions({ freeText: ' 123 Ja 60/26 ' }))).toBe(
'123 Ja 60/26, Bl. 1'
);
expect(buildPaginationLabel(0, labelOptions({ format: 'seite', startNumber: 6 }))).toBe(
'Seite 6'
);
expect(buildPaginationLabel(0, labelOptions({ format: 's', startNumber: 6 }))).toBe('S. 6');
expect(buildPaginationLabel(0, labelOptions({ format: 'nummer', startNumber: 6 }))).toBe('6');
// No leading zeros — a start number of 6 is not "06".
expect(buildPaginationLabel(0, labelOptions({ startNumber: 6 }))).toBe('Bl. 6');
});
test('the free text is dropped on later pages for the first-page scope', () => {
const options = labelOptions({ freeText: 'AZ 7/26', textScope: 'first', format: 's' });
expect(buildPaginationLabel(0, options)).toBe('AZ 7/26, S. 1');
expect(buildPaginationLabel(1, options)).toBe('S. 2');
});
test('start number and free text are normalised at the boundary', () => {
expect(normalizeStartNumber(0)).toBe(1);
expect(normalizeStartNumber(-5)).toBe(1);
expect(normalizeStartNumber(2.6)).toBe(3);
expect(normalizeStartNumber(Number.NaN)).toBe(1);
expect(normalizeStartNumber(1_000_000)).toBe(99_999);
expect(normalizeFreeText(' 123 Ja 60/26 ')).toBe('123 Ja 60/26');
expect(normalizeFreeText('x'.repeat(90))).toHaveLength(MAX_FREE_TEXT_LENGTH);
});
test('paginatePdf stamps each page exactly once', async () => {
const output = await paginatePdf(
await createPdf(3),
paginationOptions({ startNumber: 26, freeText: '123 Ja 60/26' })
);
const document = await PDFDocument.load(output);
expect(document.getPageCount()).toBe(3);
expect(getPageTextValues(document, 0)).toEqual(['123 Ja 60/26, Bl. 26']);
expect(getPageTextValues(document, 1)).toEqual(['123 Ja 60/26, Bl. 27']);
expect(getPageTextValues(document, 2)).toEqual(['123 Ja 60/26, Bl. 28']);
});
test('paginatePdf honours the first-page-only scope', async () => {
const output = await paginatePdf(
await createPdf(2),
paginationOptions({ freeText: 'AZ 7/26', textScope: 'first', format: 'nummer' })
);
const document = await PDFDocument.load(output);
expect(getPageTextValues(document, 0)).toEqual(['AZ 7/26, 1']);
expect(getPageTextValues(document, 1)).toEqual(['2']);
});
test('a label that cannot fit at the minimum font size fails loudly', async () => {
await expect(
paginatePdf(
await createPdf(1, [120, 160]),
paginationOptions({ freeText: 'x'.repeat(MAX_FREE_TEXT_LENGTH), fontSize: 18 })
)
).rejects.toBeInstanceOf(TextOverlayError);
});