task(CAD-14): phase 4c - legacy interaction engine DELETED (1173 lines), dispatcher is the single engine (native snap/ortho/pan/wheel/cursor), ToolState moved to ui.types
This commit is contained in:
@@ -12,6 +12,9 @@ import type { ZoomPanController } from '../src/canvas/ZoomPanController';
|
||||
function makeFakeZoomPan(): ZoomPanController {
|
||||
return {
|
||||
screenToWorld: (sx: number, sy: number) => ({ x: sx * 2, y: sy * 3 }),
|
||||
// CAD-14 Phase 4c: natives Pan/Wheel im Dispatcher (spies für Assertions)
|
||||
pan: vi.fn(),
|
||||
zoomAt: vi.fn(),
|
||||
} as unknown as ZoomPanController;
|
||||
}
|
||||
|
||||
@@ -60,11 +63,12 @@ describe('InteractionDispatcher', () => {
|
||||
function setup(tool?: ToolExtensionV2) {
|
||||
canvas = document.createElement('canvas');
|
||||
document.body.appendChild(canvas);
|
||||
const zoomPan = makeFakeZoomPan();
|
||||
const dispatcher = new InteractionDispatcher(canvas, {
|
||||
zoomPan: makeFakeZoomPan(),
|
||||
zoomPan,
|
||||
toolLookup: tool ? () => tool : undefined,
|
||||
});
|
||||
return { dispatcher };
|
||||
return { dispatcher, zoomPan };
|
||||
}
|
||||
|
||||
it('routet down→move→up in Reihenfolge an das aktive Tool', () => {
|
||||
@@ -103,13 +107,17 @@ describe('InteractionDispatcher', () => {
|
||||
expect(log).toEqual(['down', 'cancel']);
|
||||
});
|
||||
|
||||
it('Rechtsklick bricht ab, ohne down durchzureichen', () => {
|
||||
it('Rechtsklick startet natives Pan (kein Tool-Event)', () => {
|
||||
const { tool, log } = makeRecordingTool();
|
||||
const { dispatcher } = setup(tool);
|
||||
const { dispatcher, zoomPan } = setup(tool);
|
||||
dispatcher.setActive('fake');
|
||||
|
||||
fire(canvas, 'pointerdown', { button: 2 });
|
||||
expect(log).toEqual(['cancel']);
|
||||
fire(window, 'pointermove', { clientX: 120, clientY: 60 });
|
||||
fire(window, 'pointerup', { button: 2, clientX: 120, clientY: 60 });
|
||||
// CAD-14 Phase 4c: Rechtsklick = natives Pan, Tool bleibt unberührt
|
||||
expect(log).toEqual([]);
|
||||
expect(zoomPan.pan).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('setActive(null) deaktiviert Routing komplett', () => {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* CAD-14 Phase 4c: Dispatcher-native Pan/Wheel/Cursor/Snap.
|
||||
* Ersetzt die Legacy-InteractionEngine vollstaendig:
|
||||
* - Snap/Ortho werden auf Tool-Pointer-Events angewandt (falls Deps gesetzt)
|
||||
* - Pan bei Mittel-/Rechtsklick-Drag (auch ohne aktives Tool)
|
||||
* - Wheel-Zoom am Cursor
|
||||
* - Cursor-Meldung bei jedem Move (auch ohne Tool)
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { InteractionDispatcher } from '../src/interaction/dispatcher';
|
||||
import type { ToolExtensionV2 } from '../src/plugins/types';
|
||||
|
||||
function makeCanvas(): HTMLCanvasElement {
|
||||
const c = document.createElement('canvas');
|
||||
c.getBoundingClientRect = () => ({ left: 0, top: 0, width: 800, height: 600, right: 800, bottom: 600, x: 0, y: 0, toJSON: () => ({}) } as DOMRect);
|
||||
return c;
|
||||
}
|
||||
|
||||
const zoomPan = {
|
||||
screenToWorld: (sx: number, sy: number) => ({ x: sx, y: sy }),
|
||||
pan: vi.fn(),
|
||||
zoomAt: vi.fn(),
|
||||
};
|
||||
|
||||
function lineTool(): ToolExtensionV2 {
|
||||
return {
|
||||
manifest: { id: 'line', label: 'Linie', icon: 'L', ribbonTab: 'canvas', tags: ['basic'] },
|
||||
optionsSchema: [],
|
||||
handlers: {
|
||||
down: vi.fn(),
|
||||
move: vi.fn(),
|
||||
up: vi.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function fireMouse(canvas: HTMLCanvasElement, type: string, opts: { button?: number; x?: number; y?: number } = {}) {
|
||||
const e = new MouseEvent(type, { button: opts.button ?? 0, clientX: opts.x ?? 0, clientY: opts.y ?? 0, bubbles: true });
|
||||
canvas.dispatchEvent(e);
|
||||
}
|
||||
|
||||
describe('CAD-14 Phase 4c: dispatcher native pan/wheel/cursor', () => {
|
||||
let canvas: HTMLCanvasElement;
|
||||
let onCursor: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
canvas = makeCanvas();
|
||||
onCursor = vi.fn();
|
||||
});
|
||||
|
||||
it('Pan: Mittelklick-Drag verschiebt via zoomPan.pan (auch ohne Tool)', () => {
|
||||
const d = new InteractionDispatcher(canvas, { zoomPan: zoomPan as never, onCursorMoved: onCursor });
|
||||
canvas.dispatchEvent(new MouseEvent('pointerdown', { button: 1, clientX: 100, clientY: 100 }));
|
||||
window.dispatchEvent(new MouseEvent('pointermove', { clientX: 140, clientY: 120 }));
|
||||
window.dispatchEvent(new MouseEvent('pointerup', { button: 1, clientX: 140, clientY: 120 }));
|
||||
expect(zoomPan.pan).toHaveBeenCalled();
|
||||
d.destroy();
|
||||
});
|
||||
|
||||
it('Wheel: zoomAt am Cursor mit Faktor', () => {
|
||||
const d = new InteractionDispatcher(canvas, { zoomPan: zoomPan as never });
|
||||
canvas.dispatchEvent(new WheelEvent('wheel', { deltaY: -120, clientX: 300, clientY: 200, cancelable: true }));
|
||||
expect(zoomPan.zoomAt).toHaveBeenCalled();
|
||||
d.destroy();
|
||||
});
|
||||
|
||||
it('Cursor-Meldung: onCursorMoved feuert bei pointermove ohne aktives Tool', () => {
|
||||
const d = new InteractionDispatcher(canvas, { zoomPan: zoomPan as never, onCursorMoved: onCursor });
|
||||
window.dispatchEvent(new MouseEvent('pointermove', { clientX: 50, clientY: 60 }));
|
||||
expect(onCursor).toHaveBeenCalledWith(50, 60);
|
||||
d.destroy();
|
||||
});
|
||||
|
||||
it('Snap: down/up Positionen werden gesnappt (snap-Deps)', () => {
|
||||
const snap = vi.fn((x: number, y: number) => ({ x: Math.round(x / 10) * 10, y: Math.round(y / 10) * 10 }));
|
||||
const tool = lineTool();
|
||||
const d = new InteractionDispatcher(canvas, { zoomPan: zoomPan as never, snap, toolLookup: () => tool } as never);
|
||||
d.setActive('line');
|
||||
expect(d.getActive()).toBe(tool);
|
||||
canvas.dispatchEvent(new MouseEvent('pointerdown', { button: 0, clientX: 12, clientY: 12 }));
|
||||
window.dispatchEvent(new MouseEvent('pointermove', { clientX: 35, clientY: 35 }));
|
||||
window.dispatchEvent(new MouseEvent('pointerup', { button: 0, clientX: 35, clientY: 35 }));
|
||||
expect(tool.handlers.down).toHaveBeenCalled();
|
||||
expect((tool.handlers.down as any).mock.calls[0][0].world).toEqual({ x: 10, y: 10 });
|
||||
expect((tool.handlers.up as any).mock.calls[0][0].world).toEqual({ x: 40, y: 40 });
|
||||
d.destroy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user