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

177 lines
6.3 KiB
TypeScript
Raw Normal View History

/**
* elementToPrimitives Tests (Task C2).
* Deckt alle unterstützten Typen + Legacy-Konventionen + Grenzfälle ab.
*/
import { describe, it, expect } from 'vitest';
import { elementToPrimitives } from '../src/render/pixi/elementDrawers';
import type { CADElement } from '../src/types/cad.types';
function el(id: string, type: string, props: Record<string, unknown> = {}, geom: Partial<CADElement> = {}): CADElement {
return {
id,
type,
layerId: 'l',
x: 0,
y: 0,
width: 10,
height: 10,
properties: props,
...geom,
} as unknown as CADElement;
}
describe('elementToPrimitives', () => {
it('line: aus properties.x1..y2 mit stroke/sw', () => {
const prims = elementToPrimitives(el('l', 'line', {
x1: 0, y1: 0, x2: 100, y2: 50, stroke: '#f00', strokeWidth: 3,
}));
expect(prims.length).toBe(1);
expect(prims[0]).toEqual({
kind: 'line', from: { x: 0, y: 0 }, to: { x: 100, y: 50 },
stroke: '#f00', strokeWidth: 3,
});
});
it('line ohne Koordinaten ergibt leere Liste', () => {
expect(elementToPrimitives(el('l', 'line', {}))).toEqual([]);
});
it('rect: x/y ist Zentrum der BBox (Legacy-Konvention)', () => {
const prims = elementToPrimitives(el('r', 'rect', { fill: '#123456' }, {
x: 100, y: 50, width: 80, height: 40,
}));
expect(prims.length).toBe(1);
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.x).toBe(60); // 100 40
expect(p.y).toBe(30); // 50 20
expect(p.w).toBe(80);
expect(p.h).toBe(40);
expect(p.fill).toBe('#123456');
});
it('rect fill none wird zu null', () => {
const prims = elementToPrimitives(el('r', 'rect', { fill: 'none' }));
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.fill).toBeNull();
});
it('circle: radius mit Fallback width/2', () => {
const withExplicit = elementToPrimitives(el('c', 'circle', { radius: 25, fill: '#abcdef' }, { x: 5, y: 5 }));
const p = withExplicit[0];
if (p.kind !== 'circle') throw new Error('erwartete circle');
expect(p.radius).toBe(25);
expect(p.center).toEqual({ x: 5, y: 5 });
expect(p.fill).toBe('#abcdef');
const byWidth = elementToPrimitives(el('c', 'circle', {}, { width: 120 }));
const pb = byWidth[0];
if (pb.kind !== 'circle') throw new Error('erwartete circle');
expect(pb.radius).toBe(60); // fallback
});
it('arc: Winkel in Grad wie Legacy', () => {
const prims = elementToPrimitives(el('a', 'arc', {
radius: 40, startAngle: 0, endAngle: 90,
}, { x: 0, y: 0 }));
const p = prims[0];
if (p.kind !== 'arc') throw new Error('erwartete arc');
expect(p.startDeg).toBe(0);
expect(p.endDeg).toBe(90);
expect(p.radius).toBe(40);
});
it('polyline/polygon: points-Array, close je nach Typ', () => {
const pts = [{ x: 0, y: 0 }, { x: 10, y: 0 }, { x: 10, y: 10 }];
const open = elementToPrimitives(el('pl', 'polyline', { points: pts }));
const closed = elementToPrimitives(el('pg', 'polygon', { points: pts }));
expect(open.length).toBe(1);
expect(closed.length).toBe(1);
if (open[0].kind !== 'polyline' || closed[0].kind !== 'polyline') {
throw new Error('erwartete polyline');
}
expect(open[0].close).toBe(false);
expect(closed[0].close).toBe(true);
});
it('text: leer nicht gerendert; gefuellt mit fontSize/color', () => {
expect(elementToPrimitives(el('t', 'text', { text: '' }))).toEqual([]);
const filled = elementToPrimitives(el('t', 'text', {
text: 'Bühne A', fontSize: 18, color: '#ffffff',
}, { x: 50, y: 75 }));
expect(filled.length).toBe(1);
const p = filled[0];
if (p.kind !== 'text') throw new Error('erwartete text');
expect(p.text).toBe('Bühne A');
expect(p.fontSize).toBe(18);
expect(p.color).toBe('#ffffff');
});
it('unbekannter Typ ergibt leere Liste (renderer-sicher)', () => {
expect(elementToPrimitives(el('x', 'definitely-unknown'))).toEqual([]);
});
it('dimension: komplette Bemassung (Extension+Masslinie+Pfeile+Text, CAD-1)', () => {
const prims = elementToPrimitives(el('d', 'dimension', {
x1: 0, y1: 0, x2: 100, y2: 0, value: '250 cm',
}, { x: 50, y: 0 }));
// CAD-1: 2 Extension lines + 1 Masslinie + 2 Pfeilspitzen + 1 Text = 6
expect(prims.length).toBe(6);
expect(prims.filter((p) => p.kind === 'line').length).toBe(3);
expect(prims.filter((p) => p.kind === 'polyline').length).toBe(2);
const txt = prims.find((p) => p.kind === 'text');
if (!txt || txt.kind !== 'text') throw new Error('erwartete text');
expect(txt.text).toBe('250 cm');
});
it('leader: Linie + Label am Ende', () => {
const prims = elementToPrimitives(el('ld', 'leader', {
x1: 10, y1: 10, x2: 80, y2: 60, text: 'A1', fontSize: 14,
}));
expect(prims.length).toBe(2);
expect(prims[0].kind).toBe('line');
const txt = prims[1];
if (txt.kind !== 'text') throw new Error('erwartete text');
expect(txt.text).toBe('A1');
expect(txt.fontSize).toBe(14);
});
it('revcloud: geschlossener Punktzug', () => {
const prims = elementToPrimitives(el('rc', 'revcloud', {
points: [{ x: 0, y: 0 }, { x: 50, y: 0 }, { x: 50, y: 30 }],
}));
expect(prims.length).toBe(1);
const p = prims[0];
if (p.kind !== 'polyline') throw new Error('erwartete polyline');
expect(p.close).toBe(true);
});
it('chair: gefuelltes Rechteck mit Fallback-Farbe', () => {
const prims = elementToPrimitives(el('ch', 'chair', {}, { x: 50, y: 50, width: 20, height: 20 }));
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.fill).toBe('#4a90d9');
expect(p.x).toBe(40);
expect(p.y).toBe(40);
});
it('table: Kreis mit Fallback-Farbe und Radius aus width', () => {
const prims = elementToPrimitives(el('tb', 'table', {}, { x: 0, y: 0, width: 60, height: 60 }));
const p = prims[0];
if (p.kind !== 'circle') throw new Error('erwartete circle');
expect(p.radius).toBe(30);
expect(p.fill).toBe('#8b7355');
});
it('stage: dunkles Rechteck', () => {
const prims = elementToPrimitives(el('st', 'stage', {}, { x: 0, y: 0, width: 200, height: 100 }));
const p = prims[0];
if (p.kind !== 'rect') throw new Error('erwartete rect');
expect(p.fill).toBe('#3a3a4a');
expect(p.w).toBe(200);
expect(p.h).toBe(100);
});
});