/** * CAD-14 Phase 3: selectTool V2 — Auswahl direkt auf dem V2-Dokument. * Klick = hitTest via SelectionBridge, Leerklick leert, Shift additiv, * Drag = Box-Select (Elemente mit Zentrum in BBox). */ import { describe, it, expect, beforeEach } from 'vitest'; import { selectTool } from '../src/plugins/builtin/core-drawing'; import { CADDocument } from '../src/kernel/document/CADDocument'; import { YjsDocument } from '../src/crdt/YjsDocument'; import type { CADElement, Pt } from '../src/types/cad.types'; function pe(x: number, y: number, shift = false): { world: Pt; shift: boolean } { return { world: { x, y }, shift } as never; } function makeCtx(ids: string[], hit: CADElement | null) { const selected: string[][] = []; const onChangeCalls: string[][] = []; const realDoc = new CADDocument(new YjsDocument()); let currentIds = [...ids]; const ctx: any = { options: {}, selection: { getIds: () => currentIds, setIds: (next: string[], additive?: boolean) => { currentIds = additive ? [...currentIds, ...next] : next; selected.push(currentIds); }, hitTest: (_x: number, _y: number, _tol: number) => hit, onChange: (ids2: string[]) => onChangeCalls.push(ids2), }, doc: realDoc, setStatus: () => {}, setPreview: () => {}, }; return { ctx, selected, onChangeCalls, realDoc, getCurrentIds: () => currentIds }; } function lineEl(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; } describe('CAD-14 Phase 3: selectTool V2', () => { beforeEach(() => { selectTool.handlers.cancel?.({ setStatus: () => {}, setPreview: () => {} } as never); }); it('Manifest: id=select, basic-tag (immer sichtbar)', () => { expect(selectTool.manifest.id).toBe('select'); expect((selectTool.manifest.tags ?? []).includes('basic')).toBe(true); }); it('Klick auf Element: hitTest-Treffer wird selektiert (setIds mit ID)', () => { const el = lineEl('e1', 0, 0, 100, 100); const { ctx, selected } = makeCtx([], el); selectTool.handlers.down!(pe(50, 50), ctx); expect(selected.length).toBe(1); expect(selected[0]).toEqual(['e1']); }); it('Leerklick (hitTest null): Auswahl geleert (setIds mit [])', () => { const { ctx, selected } = makeCtx(['e9'], null); selectTool.handlers.down!(pe(500, 500), ctx); expect(selected.length).toBe(1); expect(selected[0]).toEqual([]); }); it('Shift-Klick additiv: setIds mit additive=true', () => { const el = lineEl('e1', 0, 0, 100, 100); const { ctx, selected } = makeCtx(['e9'], el); selectTool.handlers.down!(pe(50, 50, true), ctx); expect(selected.length).toBe(1); // additive-Variante: bestaehige IDs + neue ID expect(selected[0]).toEqual(['e9', 'e1']); }); it('Box-Select: Element mit Zentrum in BBox wird selektiert', () => { const { ctx, realDoc, getCurrentIds } = makeCtx([], null); const el = lineEl('e1', 0, 0, 100, 100); realDoc.addElement(el); // down in leerer Gegend startet Box (Leerklick leert zuerst — korrekt), // move zeigt Vorschau, up schliesst BBox um Element. selectTool.handlers.down!(pe(-10, -10), ctx); selectTool.handlers.move!(pe(200, 200), ctx); selectTool.handlers.up!(pe(200, 200), ctx); expect(getCurrentIds()).toEqual(['e1']); }); it('onChange feuert nach Klick-Auswahl (App-Kanal)', () => { const el = lineEl('e1', 0, 0, 100, 100); const { ctx, onChangeCalls } = makeCtx([], el); selectTool.handlers.down!(pe(50, 50), ctx); expect(onChangeCalls.length).toBe(1); expect(onChangeCalls[0]).toEqual(['e1']); }); });