/** * Task D3 – Point/XLine/Ray: Konstruktions-Elemente. * * pointTool: Klick-Platzierer (kleiner Kreis mit point:true-Marker, * r=1.5 wie DXF-POINT-Import). xlineTool: 2 Klicks (Basis+Richtung), * sehr lange Linie in BEIDE Richtungen. rayTool: halblang (nur eine * Richtung). Alle mit construction:true und auf defpoints-Layer * (auto-angelegt falls fehlt). */ import { describe, it, expect, beforeEach } from 'vitest'; import { pointTool, xlineTool, rayTool } from '../src/plugins/builtin/core-drawing'; import { CADDocument } from '../src/kernel/document/CADDocument'; import { createInMemoryDoc } from './helpers/inMemoryDoc'; import type { CADElement, Pt } from '../src/types/cad.types'; function mkCtx() { const { ydoc } = createInMemoryDoc(); const doc = new CADDocument(ydoc); const previews: (CADElement | null)[] = []; const statuses: string[] = []; const ctx: any = { doc, options: {}, setStatus: (m: string) => statuses.push(m), setPreview: (el: CADElement | null) => previews.push(el), }; return { ctx, doc, previews, statuses }; } function pe(x: number, y: number): { world: Pt } { return { world: { x, y } } as never; } beforeEach(() => { pointTool.handlers.cancel?.({ setStatus: () => {}, setPreview: () => {} } as never); xlineTool.handlers.cancel?.({ setStatus: () => {}, setPreview: () => {} } as never); rayTool.handlers.cancel?.({ setStatus: () => {}, setPreview: () => {} } as never); }); describe('D3: pointTool', () => { it('Klick platziert Kreis mit point-Marker (r=1.5, wie DXF POINT)', () => { const { ctx, doc } = mkCtx(); pointTool.handlers.down!(pe(55, 66), ctx); const els = doc.getAllElements(); expect(els).toHaveLength(1); const el = els[0]; expect(el.type).toBe('circle'); expect(el.x).toBe(55); expect(el.y).toBe(66); expect(el.properties.radius).toBe(1.5); expect(el.properties.point).toBe(true); }); it('defpoints-Layer wird automatisch angelegt falls fehlt', () => { const { ctx, doc } = mkCtx(); expect(doc.getLayer('defpoints')).toBeUndefined(); pointTool.handlers.down!(pe(0, 0), ctx); expect(doc.getLayer('defpoints')).toBeDefined(); expect(doc.getLayer('defpoints')!.name).toBe('defpoints'); }); it('Element landet auf defpoints-Layer mit construction:true', () => { const { ctx, doc } = mkCtx(); pointTool.handlers.down!(pe(10, 10), ctx); const el = doc.getAllElements()[0]; expect(el.layerId).toBe('defpoints'); expect(el.properties.construction).toBe(true); }); }); describe('D3: xlineTool (Konstruktionslinie, beide Richtungen)', () => { it('2 Klicks erzeugen sehr lange Linie durch beide Punkte in BEIDE Richtungen', () => { const { ctx, doc } = mkCtx(); xlineTool.handlers.down!(pe(0, 0), ctx); xlineTool.handlers.down!(pe(10, 10), ctx); const els = doc.getAllElements(); expect(els).toHaveLength(1); const el = els[0]; expect(el.type).toBe('line'); const dx = (el.properties.x2 as number) - (el.properties.x1 as number); const dy = (el.properties.y2 as number) - (el.properties.y1 as number); // Sehr lange: Länge >> Distanz der beiden Klicks (14.14) expect(Math.hypot(dx, dy)).toBeGreaterThan(100000); // Mittelpunkt der Linie = Basispunkt expect((el.properties.x1 as number) + dx / 2).toBeCloseTo(0, 3); expect((el.properties.y1 as number) + dy / 2).toBeCloseTo(0, 3); }); it('Element auf defpoints-Layer mit construction:true', () => { const { ctx, doc } = mkCtx(); xlineTool.handlers.down!(pe(0, 0), ctx); xlineTool.handlers.down!(pe(5, 0), ctx); const el = doc.getAllElements()[0]; expect(el.layerId).toBe('defpoints'); expect(el.properties.construction).toBe(true); }); it('Preview bei move nach erstem Klick', () => { const { ctx, previews } = mkCtx(); xlineTool.handlers.down!(pe(0, 0), ctx); xlineTool.handlers.move!(pe(50, 50), ctx); expect(previews.length).toBeGreaterThanOrEqual(1); }); it('Erster Klick ohne zweiten erzeugt NICHTS (Session-State)', () => { const { ctx, doc } = mkCtx(); xlineTool.handlers.down!(pe(0, 0), ctx); expect(doc.getAllElements()).toHaveLength(0); }); }); describe('D3: rayTool (Strahl, nur EINE Richtung)', () => { it('2 Klicks erzeugen Linie ab Basispunkt NUR in Zugrichtung', () => { const { ctx, doc } = mkCtx(); rayTool.handlers.down!(pe(0, 0), ctx); rayTool.handlers.down!(pe(10, 0), ctx); const el = doc.getAllElements()[0]; expect(el.type).toBe('line'); expect(el.properties.x1).toBe(0); expect(el.properties.y1).toBe(0); // Endpunkt weit in +x-Richtung expect((el.properties.x2 as number)).toBeGreaterThanOrEqual(100000); // NICHT in Gegenrichtung: x1 ist exakt der Basispunkt expect(el.properties.x1).toBe(0); }); it('construction:true + defpoints-Layer', () => { const { ctx, doc } = mkCtx(); rayTool.handlers.down!(pe(0, 0), ctx); rayTool.handlers.down!(pe(0, 10), ctx); const el = doc.getAllElements()[0]; expect(el.layerId).toBe('defpoints'); expect(el.properties.construction).toBe(true); }); }); describe('D3: Manifeste', () => { it('ids/ribbonTab/tags korrekt', () => { expect(pointTool.manifest.id).toBe('point'); expect(xlineTool.manifest.id).toBe('xline'); expect(rayTool.manifest.id).toBe('ray'); for (const t of [pointTool, xlineTool, rayTool]) { expect(t.manifest.ribbonTab).toBe('canvas'); expect((t.manifest.tags ?? [])).toContain('basic'); } }); });