/** * Task CAD-2 - Echte Hatch-Muster + Boundary-Erkennung. * * HATCH_PATTERNS: Standard-CAD-Muster (ANSI31, NET, GRASS etc.) * als Linien-Segment-Generatoren fuer eine BBox. * computeBoundary: umschlossene Flaeche aus Kontur-Punkten. */ import { describe, it, expect } from 'vitest'; import { HATCH_PATTERNS, getHatchPattern, generateHatchLines, computeBoundary, type HatchPatternId, } from '../src/services/hatchPatternService'; describe('CAD-2: HATCH_PATTERNS Bibliothek', () => { it('enthaelt die Standard-CAD-Muster', () => { const ids = HATCH_PATTERNS.map((p) => p.id); expect(ids).toContain('ANSI31'); expect(ids).toContain('ANSI32'); expect(ids).toContain('ANSI37'); expect(ids).toContain('NET'); expect(ids).toContain('GRASS'); expect(ids).toContain('DOTS'); expect(ids).toContain('SOLID'); expect(ids).toContain('lines'); // Alte bleiben erhalten expect(ids).toContain('cross'); }); it('jedes Muster hat id, label, description; spacing nur bei Nicht-SOLID', () => { for (const p of HATCH_PATTERNS) { expect(p.id.length).toBeGreaterThan(0); expect(p.label.length).toBeGreaterThan(0); // SOLID braucht keinen Abstand (Vollfuellung) if (!p.solid) { expect(p.defaultSpacing).toBeGreaterThan(0); } } }); it('getHatchPattern liefert undefined fuer unbekannte ID', () => { expect(getHatchPattern('gibts-nicht')).toBeUndefined(); expect(getHatchPattern('ANSI31')).toBeDefined(); expect(getHatchPattern('ANSI31')!.label).toContain('1'); }); }); describe('CAD-2: generateHatchLines (Muster-Geometrie)', () => { const bbox = { minX: 0, minY: 0, maxX: 100, maxY: 50 }; const spacing = 10; it('ANSI31 (45 Grad): erzeugt Diagonal-Linien', () => { const lines = generateHatchLines('ANSI31', bbox, spacing); expect(lines.length).toBeGreaterThan(3); for (const seg of lines) { const dx = seg.to.x - seg.from.x; const dy = seg.to.y - seg.from.y; expect(Math.abs(dy / dx)).toBeCloseTo(1, 1); // 45 Grad } }); it('NET: erzeugt horizontale UND vertikale Linien', () => { const lines = generateHatchLines('NET', bbox, spacing); const horiz = lines.filter((l) => l.from.y === l.to.y); const vert = lines.filter((l) => l.from.x === l.to.x); expect(horiz.length).toBeGreaterThan(0); expect(vert.length).toBeGreaterThan(0); }); it('DOTS: erzeugt kleine Kreise (als 4-Punkt-Polygone)', () => { const lines = generateHatchLines('DOTS', bbox, spacing); expect(lines.length).toBeGreaterThan(0); // DOTS sind als polyline-segments repraesentiert }); it('SOLID: erzeugt EINE grosse Flaeche (fill)', () => { const lines = generateHatchLines('SOLID', bbox, spacing); expect(lines).toHaveLength(1); expect((lines[0] as { fill?: string }).fill).toBeDefined(); }); it('unbekanntes Muster: leeres Array (renderer-sicher)', () => { expect(generateHatchLines('gibts-nicht', bbox, spacing)).toEqual([]); }); }); describe('CAD-2: computeBoundary (Boundary-Erkennung)', () => { it('Rechteck-Kontur: BBox wird korrekt berechnet', () => { const pts = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 50 }, { x: 0, y: 50 }]; const b = computeBoundary(pts); expect(b).toEqual({ minX: 0, minY: 0, maxX: 100, maxY: 50 }); }); it('Dreieck: BBox umschliesst alle Punkte', () => { const pts = [{ x: 10, y: 0 }, { x: 90, y: 80 }, { x: 10, y: 80 }]; const b = computeBoundary(pts); expect(b.minX).toBe(10); expect(b.maxX).toBe(90); expect(b.minY).toBe(0); expect(b.maxY).toBe(80); }); it('leere/wenige Punkte: null-Boundary', () => { expect(computeBoundary([])).toBeNull(); expect(computeBoundary([{ x: 0, y: 0 }])).toBeNull(); }); });