/**
* Task F8 – SVG-Import als Block: Geometrie-Konvertierung.
*
* Path-Parser (M/L/H/V/C/S/Q/T/A/Z, relative + absolute), Bézier-Flattening,
* Arc→Polyline, Konventions-Fixes (rect=Zentrum, circle=cx/cy direkt),
* ellipse→Polyline statt verstümmeltem Kreis, sowie Block-Payload-Erzeugung
* (SVG → Element-Array + Thumbnail) für die globale Bibliothek.
*/
import { describe, it, expect } from 'vitest';
import { importSVG, importSVGAsBlockPayload } from '../src/services/importService';
describe('F8: SVG path → Polyline-Approximation', () => {
it('M + L + Z erzeugt geschlossene Polyline mit exakten Punkten', () => {
const res = importSVG('');
expect(res.success).toBe(true);
const poly = res.elements.find((e) => e.type === 'polygon');
expect(poly).toBeDefined();
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts.length).toBeGreaterThanOrEqual(3);
expect(pts[0]).toEqual({ x: 0, y: 0 });
expect(pts[1]).toEqual({ x: 100, y: 0 });
expect(pts[2]).toEqual({ x: 100, y: 100 });
});
it('kleine Befehle sind relativ (m/l): Startpunkt wirkt', () => {
const res = importSVG('');
const poly = res.elements.find((e) => e.type === 'polygon');
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts[0]).toEqual({ x: 10, y: 10 });
expect(pts[1]).toEqual({ x: 30, y: 10 });
expect(pts[2]).toEqual({ x: 30, y: 30 });
});
it('kubischer Bézier (C) wird geflattet: Kurvenpunkte + Endpunkt exakt', () => {
const res = importSVG('');
const poly = res.elements.find((e) => e.type === 'polyline');
expect(poly).toBeDefined();
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts.length).toBeGreaterThan(10); // geflattet, nicht nur Endpunkte
expect(pts[0]).toEqual({ x: 0, y: 0 });
expect(pts[pts.length - 1]).toEqual({ x: 100, y: 100 });
// Mittelpunkt der Kubik-Bézier bei t=0.5: exakt berechenbar
expect(pts[Math.floor(pts.length / 2)].y).toBeGreaterThan(20);
});
it('H/V-Befehle (horizontal/vertical) funktionieren', () => {
const res = importSVG('');
const poly = res.elements.find((e) => e.type === 'polygon');
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts[0]).toEqual({ x: 0, y: 0 });
expect(pts[1]).toEqual({ x: 50, y: 0 });
expect(pts[2]).toEqual({ x: 50, y: 30 });
});
it('Arc (A) wird approximiert: Halbkreis über (100,0) mit korrektem Bogenverlauf', () => {
const res = importSVG('');
const poly = res.elements.find((e) => e.type === 'polyline');
expect(poly).toBeDefined();
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts.length).toBeGreaterThan(8);
expect(pts[0]).toEqual({ x: 0, y: 0 });
expect(pts[pts.length - 1]).toEqual({ x: 100, y: 0 });
// Scheitel des Halbkreises: (50, -50) bei sweep=1 (y-up im Bogen oben)
const maxY = Math.max(...pts.map((p) => p.y));
const minY = Math.min(...pts.map((p) => p.y));
expect(minY).toBeLessThan(-40); // Bogen wölbt sich nach oben (negativ)
expect(maxY).toBeCloseTo(0, 5);
});
it('Mehrfach-Subpaths ergeben mehrere Elemente', () => {
const res = importSVG('');
const polys = res.elements.filter((e) => e.type === 'polyline');
expect(polys.length).toBe(2);
});
});
describe('F8: Konventions-Fixes', () => {
it('rect: x/y = Zentrum (App-Konvention, nicht Ecke)', () => {
const res = importSVG('');
const rect = res.elements.find((e) => e.type === 'rect');
expect(rect).toBeDefined();
expect(rect!.x).toBe(50);
expect(rect!.y).toBe(25);
expect(rect!.width).toBe(100);
});
it('circle: el.x/el.y = Zentrum direkt (Renderer-Konvention)', () => {
const res = importSVG('');
const circ = res.elements.find((e) => e.type === 'circle');
expect(circ).toBeDefined();
expect(circ!.x).toBe(30);
expect(circ!.y).toBe(40);
expect(circ!.properties.radius).toBe(10);
});
it('ellipse → geschlossenes Polygon mit rx/ry korrekt (nicht verstümmelter Kreis)', () => {
const res = importSVG('');
const poly = res.elements.find((e) => e.type === 'polygon');
expect(poly).toBeDefined();
const pts = poly!.properties.points as Array<{ x: number; y: number }>;
expect(pts.length).toBeGreaterThanOrEqual(32);
expect(Math.max(...pts.map((p) => p.x))).toBeCloseTo(150, 5);
expect(Math.min(...pts.map((p) => p.x))).toBeCloseTo(-50, 5);
expect(Math.max(...pts.map((p) => p.y))).toBeCloseTo(90, 5);
expect(Math.min(...pts.map((p) => p.y))).toBeCloseTo(10, 5);
});
});
describe('F8: SVG als Block-Payload', () => {
it('importSVGAsBlockPayload liefert Element-Array als JSON-String + svg als Thumbnail', () => {
const payload = importSVGAsBlockPayload('');
expect(payload.blockData).toBeTruthy();
const els = JSON.parse(payload.blockData);
expect(Array.isArray(els)).toBe(true);
expect(els.length).toBe(1);
expect(els[0].type).toBe('rect');
expect(payload.svg).toContain('