148 lines
5.4 KiB
TypeScript
148 lines
5.4 KiB
TypeScript
/**
|
||
* 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');
|
||
}
|
||
});
|
||
});
|