Files
web-cad/frontend/tests/pdfLayoutExport.test.ts
T

100 lines
4.0 KiB
TypeScript
Raw Normal View History

/**
* Task G4 PDF-Export je Layout im Ausgabemaßstab.
*
* exportLayoutPDF rendert den Layout-Viewport (center/scale) auf eine A4-
* Seite (landscape), skaliert Weltkoordinaten in den Ausgabemaßstab,
* berücksichtigt Linienbreiten aus Layern (lineType) und unterstützt
* eine monochrome Option. Titelblock (frameBlockRef) wird mitgezeichnet.
* Rückgabe: PDF-Bytes, per PDFDocument.load verifizierbar.
*/
import { describe, it, expect } from 'vitest';
import { PDFDocument } from 'pdf-lib';
import { CADDocument } from '../src/kernel/document/CADDocument';
import { createInMemoryDoc } from './helpers/inMemoryDoc';
import { exportLayoutPDF } from '../src/services/pdfExport';
import type { CADElement, LayoutDefinition } from '../src/types/cad.types';
function line(id: string, x1: number, y1: number, x2: number, y2: number, layerId = 'layer-0'): CADElement {
return {
id, type: 'line', layerId,
x: Math.min(x1, x2), y: Math.min(y1, y2),
width: Math.abs(x2 - x1), height: Math.abs(y2 - y1),
properties: { x1, y1, x2, y2, stroke: '#ff0000' },
} as CADElement;
}
function mkDoc(els: CADElement[], layouts: LayoutDefinition[]): CADDocument {
const { ydoc } = createInMemoryDoc();
const doc = new CADDocument(ydoc);
for (const e of els) doc.addElement(e);
for (const l of layouts) doc.addLayout(l);
return doc;
}
const LAYOUT: LayoutDefinition = {
id: 'layout-1', name: 'A4 Plan',
viewport: { center: { x: 50, y: 50 }, scale: 1 },
};
describe('G4: exportLayoutPDF', () => {
it('liefert valides PDF mit genau einer A4-landscape-Seite', async () => {
const doc = mkDoc([line('l1', 0, 0, 100, 100)], [LAYOUT]);
const bytes = await exportLayoutPDF(doc, 'layout-1');
expect(bytes.byteLength).toBeGreaterThan(500);
const pdf = await PDFDocument.load(bytes);
expect(pdf.getPageCount()).toBe(1);
const { width, height } = pdf.getPage(0).getSize();
expect(width).toBe(842); // A4 landscape pt
expect(height).toBe(595);
});
it('Titelblock-Elemente (frameBlockRef) werden mitgezeichnet', async () => {
const doc = mkDoc([line('l1', 0, 0, 100, 0)], [LAYOUT]);
// Titelblock-Definition anlegen und referenzieren
const blockId = 'title-1';
doc.addBlock({
id: blockId, name: 'Titelblock', description: '', category: 'frame',
elements: [line('tb1', 0, 0, 50, 0)],
});
doc.updateLayout('layout-1', { frameBlockRef: blockId });
const bytes = await exportLayoutPDF(doc, 'layout-1');
const pdf = await PDFDocument.load(bytes);
expect(pdf.getPageCount()).toBe(1);
// Strukturelle Prüfung: Export läuft fehlerfrei mit Frame-Block
expect(bytes.byteLength).toBeGreaterThan(500);
});
it('monochrome Option exportiert ohne Farbinformationen zu verlieren', async () => {
const doc = mkDoc([line('l1', 0, 0, 100, 100)], [LAYOUT]);
const bytes = await exportLayoutPDF(doc, 'layout-1', { monochrome: true });
const pdf = await PDFDocument.load(bytes);
expect(pdf.getPageCount()).toBe(1);
});
it('leeres Layout (keine Elemente im Viewport) exportiert trotzdem eine Seite', async () => {
const doc = mkDoc([], [LAYOUT]);
const bytes = await exportLayoutPDF(doc, 'layout-1');
const pdf = await PDFDocument.load(bytes);
expect(pdf.getPageCount()).toBe(1);
});
it('Layout ohne existierende ID → throw', async () => {
const doc = mkDoc([], []);
await expect(exportLayoutPDF(doc, 'gibts-nicht')).rejects.toThrow();
});
it('unsichtbare Layer werden übersprungen (Export läuft fehlerfrei)', async () => {
const { ydoc } = createInMemoryDoc();
const doc = new CADDocument(ydoc);
ydoc.data.layers.set('layer-0', {
id: 'layer-0', name: '0', visible: false, locked: false,
color: '#ffffff', lineType: 'solid', transparency: 0, sortOrder: 0, parentId: null,
});
doc.addElement(line('l1', 0, 0, 100, 100));
doc.addLayout(LAYOUT);
const bytes = await exportLayoutPDF(doc, 'layout-1');
const pdf = await PDFDocument.load(bytes);
expect(pdf.getPageCount()).toBe(1);
});
});