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 => ({ 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); });