151 lines
5.3 KiB
TypeScript
151 lines
5.3 KiB
TypeScript
/**
|
|
* Pilot V2-Tools Tests (Task A3): line + select.
|
|
* line: Handler-Sequenz gegen echtes CADDocument (In-Memory), Undo-Verhalten.
|
|
* select: Bridge-basierte Auswahl mit Shift/Ctrl.
|
|
*/
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { lineTool, coreDrawingPlugin } from '../src/plugins/builtin/core-drawing';
|
|
import { selectTool, coreModifyPlugin } from '../src/plugins/builtin/core-modify';
|
|
import { CADDocument } from '../src/kernel/document/CADDocument';
|
|
import { createInMemoryDoc } from './helpers/inMemoryDoc';
|
|
import type { ToolPointerEvent, FullToolContext } from '../src/plugins/types';
|
|
import type { CADElement } from '../src/types/cad.types';
|
|
|
|
function pe(x: number, y: number, mods: Partial<Pick<ToolPointerEvent, 'shift' | 'ctrl'>> = {}): ToolPointerEvent {
|
|
return {
|
|
world: { x, y },
|
|
screen: { x: x / 2, y: y / 2 },
|
|
button: 0,
|
|
shift: mods.shift ?? false,
|
|
alt: false,
|
|
ctrl: mods.ctrl ?? false,
|
|
};
|
|
}
|
|
|
|
function makeCtx(overrides: Partial<FullToolContext> = {}): FullToolContext & { previews: (CADElement | null)[]; statuses: string[] } {
|
|
const previews: (CADElement | null)[] = [];
|
|
const statuses: string[] = [];
|
|
return {
|
|
options: {},
|
|
setStatus: (m) => statuses.push(m),
|
|
setPreview: (el) => previews.push(el),
|
|
previews,
|
|
statuses,
|
|
...overrides,
|
|
} as FullToolContext & { previews: (CADElement | null)[]; statuses: string[] };
|
|
}
|
|
|
|
function makeEl(id: string): CADElement {
|
|
return { id, type: 'rect', layerId: 'l', x: 0, y: 0, width: 1, height: 1, properties: {} } as unknown as CADElement;
|
|
}
|
|
|
|
describe('A3 pilot: line tool', () => {
|
|
it('erzeugt EINE Linie per down→up; ein undo entfernt sie', () => {
|
|
const { ydoc } = createInMemoryDoc();
|
|
const doc = new CADDocument(ydoc);
|
|
const ctx = makeCtx({ doc });
|
|
|
|
lineTool.handlers.down!(pe(0, 0), ctx);
|
|
lineTool.handlers.move!(pe(100, 50), ctx);
|
|
expect(ctx.previews.length).toBe(1); // Live-Vorschau war da
|
|
lineTool.handlers.up!(pe(100, 50), ctx);
|
|
|
|
const all = doc.getAllElements();
|
|
expect(all.length).toBe(1);
|
|
expect(all[0].type).toBe('line');
|
|
expect(all[0].properties.x1).toBe(0);
|
|
expect(all[0].properties.y2).toBe(50);
|
|
|
|
// Kern-Akzeptanz aus ROADMAP: Ein Werkzeug-Klick = ein Undo-Schritt
|
|
expect(doc.undo()).toBe(true);
|
|
expect(doc.getAllElements().length).toBe(0);
|
|
});
|
|
|
|
it('cancel verwirft die laufende Linie ohne Element', () => {
|
|
const { ydoc } = createInMemoryDoc();
|
|
const doc = new CADDocument(ydoc);
|
|
const ctx = makeCtx({ doc });
|
|
|
|
lineTool.handlers.down!(pe(5, 5), ctx);
|
|
lineTool.handlers.cancel!(ctx);
|
|
lineTool.handlers.up!(pe(9, 9), ctx); // up nach cancel: kein Start mehr
|
|
|
|
expect(doc.getAllElements().length).toBe(0);
|
|
});
|
|
|
|
it('Plugin-Manifest ist registrierbar und tagged basic', () => {
|
|
expect(coreDrawingPlugin.manifest.enabledByDefault).toBe(true);
|
|
const line = coreDrawingPlugin.tools?.find((t) => t.manifest.id === 'line');
|
|
expect(line?.manifest.tags).toContain('basic');
|
|
});
|
|
});
|
|
|
|
describe('A3 pilot: select tool', () => {
|
|
function makeBridge(elements: CADElement[]) {
|
|
let ids: string[] = [];
|
|
return {
|
|
bridge: {
|
|
getIds: () => ids,
|
|
setIds: (next: string[]) => {
|
|
ids = next;
|
|
},
|
|
hitTest: (wx: number, wy: number, tol: number): CADElement | null =>
|
|
elements.find((el) => Math.abs(el.x - wx) <= tol && Math.abs(el.y - wy) <= tol) ?? null,
|
|
},
|
|
getIds: () => ids,
|
|
};
|
|
}
|
|
|
|
it('wählt getroffenes Element ohne Modifier exklusiv', () => {
|
|
const els = [makeEl('a'), makeEl('b')];
|
|
els[0].x = 10; els[0].y = 10;
|
|
els[1].x = 500; els[1].y = 500;
|
|
const { bridge, getIds } = makeBridge(els);
|
|
const ctx = makeCtx({ selection: bridge });
|
|
|
|
selectTool.handlers.down!(pe(10, 10), ctx);
|
|
expect(getIds()).toEqual(['a']);
|
|
|
|
// Klick auf b ohne Shift ersetzt Selektion
|
|
selectTool.handlers.down!(pe(500, 500), ctx);
|
|
expect(getIds()).toEqual(['b']);
|
|
});
|
|
|
|
it('Shift addiert, Ctrl subtrahiert', () => {
|
|
const els = [makeEl('a'), makeEl('b'), makeEl('c')];
|
|
els[0].x = 10; els[0].y = 10;
|
|
els[1].x = 50; els[1].y = 50;
|
|
els[2].x = 90; els[2].y = 90;
|
|
const { bridge, getIds } = makeBridge(els);
|
|
const ctx = makeCtx({ selection: bridge });
|
|
|
|
selectTool.handlers.down!(pe(10, 10), ctx);
|
|
expect(getIds()).toEqual(['a']);
|
|
|
|
selectTool.handlers.down!(pe(50, 50, { shift: true }), ctx);
|
|
expect(getIds().sort()).toEqual(['a', 'b']);
|
|
|
|
selectTool.handlers.down!(pe(90, 90, { shift: true }), ctx);
|
|
expect(getIds().sort()).toEqual(['a', 'b', 'c']);
|
|
|
|
selectTool.handlers.down!(pe(50, 50, { ctrl: true }), ctx);
|
|
expect(getIds().sort()).toEqual(['a', 'c']);
|
|
});
|
|
|
|
it('Leerklick ohne Modifier leert die Selektion; Plugin tagged basic', () => {
|
|
const els = [makeEl('a')];
|
|
els[0].x = 10; els[0].y = 10;
|
|
const { bridge, getIds } = makeBridge(els);
|
|
const ctx = makeCtx({ selection: bridge });
|
|
|
|
selectTool.handlers.down!(pe(10, 10), ctx);
|
|
expect(getIds()).toEqual(['a']);
|
|
selectTool.handlers.down!(pe(999, 999), ctx);
|
|
expect(getIds()).toEqual([]);
|
|
|
|
expect(coreModifyPlugin.manifest.enabledByDefault).toBe(true);
|
|
const sel = coreModifyPlugin.tools?.find((t) => t.manifest.id === 'select');
|
|
expect(sel?.manifest.tags).toContain('basic');
|
|
});
|
|
});
|