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

150 lines
5.6 KiB
TypeScript

/**
* Task D11 - Snap-Erweiterungen: tangent, perpendicular, quadrant, fromOffset.
*
* quadrant: Kreis-Quadrantenpunkte (0/90/180/270 auf dem Radius).
* perpendicular: Fuspunkt auf Linie senkrecht zum Referenzpunkt.
* tangent: Tangentialpunkte am Kreis vom Referenzpunkt.
* fromOffset: Basispunkt + dx/dy-Eingabefeld.
*/
import { describe, it, expect } from 'vitest';
import { SnapEngine, fromOffset, type SnapConfig } from '../src/canvas/SnapEngine';
import type { CADElement } from '../src/types/cad.types';
function cfg(modes: string[], extra: Partial<SnapConfig> = {}): Partial<SnapConfig> {
return {
enabled: true,
modes: new Set(modes as never),
tolerance: 10,
...extra,
};
}
function circle(id: string, cx: number, cy: number, r: number): CADElement {
return {
id, type: 'circle', layerId: 'l',
x: cx, y: cy, width: r * 2, height: r * 2,
properties: { radius: r },
} as unknown as CADElement;
}
function line(id: string, x1: number, y1: number, x2: number, y2: number): CADElement {
return {
id, type: 'line', layerId: 'l',
x: (x1 + x2) / 2, y: (y1 + y2) / 2,
width: Math.abs(x2 - x1), height: Math.abs(y2 - y1),
properties: { x1, y1, x2, y2 },
} as unknown as CADElement;
}
// ─── Quadrant ──────────────────────────────────────────────
describe('D11: quadrant snap', () => {
it('Kreis r=50: alle 4 Quadrantenpunkte werden geliefert', () => {
const eng = new SnapEngine(cfg(['quadrant']));
eng.setElements([circle('c1', 100, 100, 50)]);
// Cursor in der Naehe des 0-Quadranten (150,100)
const res = eng.snap(148, 100);
expect(res.point).not.toBeNull();
expect(res.point!.type).toBe('quadrant');
expect(res.point!.x).toBe(150);
expect(res.point!.y).toBe(100);
});
it('90-Quadrant (100,50) wird gefunden', () => {
const eng = new SnapEngine(cfg(['quadrant']));
eng.setElements([circle('c1', 100, 100, 50)]);
const res = eng.snap(100, 52);
expect(res.point!.x).toBe(100);
expect(res.point!.y).toBe(50);
expect(res.point!.type).toBe('quadrant');
});
it('270-Quadrant (unten) wird gefunden', () => {
const eng = new SnapEngine(cfg(['quadrant']));
eng.setElements([circle('c1', 100, 100, 50)]);
const res = eng.snap(100, 148);
expect(res.point!.y).toBe(150);
expect(res.point!.type).toBe('quadrant');
});
it('Modus nicht aktiviert -> kein quadrant snap', () => {
const eng = new SnapEngine(cfg(['endpoint']));
eng.setElements([circle('c1', 100, 100, 50)]);
const res = eng.snap(148, 100);
expect(res.point?.type ?? null).not.toBe('quadrant');
});
});
// ─── Perpendicular ─────────────────────────────────────────
describe('D11: perpendicular snap', () => {
it('Fuspunkt senkrecht vom refPoint auf horizontale Linie', () => {
const eng = new SnapEngine(cfg(['perpendicular']));
// Horizontale Linie y=100, refPoint (50,50): Fuspunkt (50,100)
eng.setElements([line('l1', 0, 100, 200, 100)]);
const res = eng.snap(52, 98, { x: 50, y: 50 });
expect(res.point).not.toBeNull();
expect(res.point!.type).toBe('perpendicular');
expect(res.point!.x).toBe(50);
expect(res.point!.y).toBe(100);
});
it('Schrge Linie: Fuspunkt berechnet', () => {
const eng = new SnapEngine(cfg(['perpendicular']));
// Linie (0,0)-(100,100), refPoint (100,0): Fuspunkt (50,50)
eng.setElements([line('l1', 0, 0, 100, 100)]);
const res = eng.snap(51, 49, { x: 100, y: 0 });
expect(res.point!.x).toBeCloseTo(50, 0);
expect(res.point!.y).toBeCloseTo(50, 0);
});
it('ohne refPoint kein perpendicular snap (Modus still)', () => {
const eng = new SnapEngine(cfg(['perpendicular']));
eng.setElements([line('l1', 0, 100, 200, 100)]);
const res = eng.snap(50, 98);
expect(res.point?.type ?? null).not.toBe('perpendicular');
});
});
// ─── Tangent ───────────────────────────────────────────────
describe('D11: tangent snap', () => {
it('Tangentialpunkt am Kreis von refPoint (rechts vom Kreis)', () => {
const eng = new SnapEngine(cfg(['tangent']));
// Kreis (100,100) r=50, refPoint (200,100), d=100:
// halfAng = acos(r/d) = 60° → Tangentenpunkte bei (125, 143.3)/(125, 56.7)
eng.setElements([circle('c1', 100, 100, 50)]);
const res = eng.snap(127, 145, { x: 200, y: 100 });
expect(res.point).not.toBeNull();
expect(res.point!.type).toBe('tangent');
expect(res.point!.x).toBeCloseTo(125, 0);
expect(res.point!.y).toBeCloseTo(143.3, 0);
});
it('refPoint AUF dem Kreiszentrum -> kein Tangentenpunkt (degeneriert)', () => {
const eng = new SnapEngine(cfg(['tangent']));
eng.setElements([circle('c1', 100, 100, 50)]);
const res = eng.snap(148, 100, { x: 100, y: 100 });
expect(res.point?.type ?? null).not.toBe('tangent');
});
});
// ─── fromOffset ────────────────────────────────────────────
describe('D11: fromOffset', () => {
it('Basispunkt + dx/dy ergibt Zielpunkt', () => {
const p = fromOffset({ x: 100, y: 200 }, 50, -30);
expect(p).toEqual({ x: 150, y: 170 });
});
it('dx=dy=0 bleibt am Basispunkt', () => {
const p = fromOffset({ x: 10, y: 20 }, 0, 0);
expect(p).toEqual({ x: 10, y: 20 });
});
it('negative Offsets funktionieren', () => {
const p = fromOffset({ x: 0, y: 0 }, -100, -50);
expect(p).toEqual({ x: -100, y: -50 });
});
});