2026-06-26 17:48:24 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* SelectionEngine Tests – Selection modes, filters, box selection, hover, listeners
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
|
|
import { SelectionEngine } from '../src/canvas/SelectionEngine';
|
|
|
|
|
|
import { RenderEngine } from '../src/canvas/RenderEngine';
|
|
|
|
|
|
import { SpatialIndex } from '../src/canvas/SpatialIndex';
|
|
|
|
|
|
import { LayerManager } from '../src/canvas/LayerManager';
|
|
|
|
|
|
import { ZoomPanController } from '../src/canvas/ZoomPanController';
|
|
|
|
|
|
import type { CADElement, CADLayer } from '../src/types/cad.types';
|
|
|
|
|
|
|
|
|
|
|
|
function createMockCanvas(w = 800, h = 600): HTMLCanvasElement {
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
|
canvas.width = w;
|
|
|
|
|
|
canvas.height = h;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
if (ctx) {
|
|
|
|
|
|
ctx.fillRect = (() => {}) as any;
|
|
|
|
|
|
ctx.strokeRect = (() => {}) as any;
|
|
|
|
|
|
ctx.beginPath = (() => {}) as any;
|
|
|
|
|
|
ctx.moveTo = (() => {}) as any;
|
|
|
|
|
|
ctx.lineTo = (() => {}) as any;
|
|
|
|
|
|
ctx.stroke = (() => {}) as any;
|
|
|
|
|
|
ctx.fill = (() => {}) as any;
|
|
|
|
|
|
ctx.save = (() => {}) as any;
|
|
|
|
|
|
ctx.restore = (() => {}) as any;
|
|
|
|
|
|
ctx.scale = (() => {}) as any;
|
|
|
|
|
|
ctx.arc = (() => {}) as any;
|
|
|
|
|
|
ctx.setLineDash = (() => {}) as any;
|
|
|
|
|
|
ctx.clearRect = (() => {}) as any;
|
|
|
|
|
|
ctx.translate = (() => {}) as any;
|
|
|
|
|
|
ctx.rotate = (() => {}) as any;
|
|
|
|
|
|
ctx.clip = (() => {}) as any;
|
|
|
|
|
|
ctx.fillText = (() => {}) as any;
|
|
|
|
|
|
ctx.quadraticCurveTo = (() => {}) as any;
|
|
|
|
|
|
ctx.closePath = (() => {}) as any;
|
|
|
|
|
|
}
|
|
|
|
|
|
return canvas;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function makeLayer(id: string, overrides: Partial<CADLayer> = {}): CADLayer {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id,
|
|
|
|
|
|
name: id,
|
|
|
|
|
|
visible: true,
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
color: '#ffffff',
|
|
|
|
|
|
lineType: 'solid',
|
|
|
|
|
|
transparency: 0,
|
|
|
|
|
|
sortOrder: 0,
|
|
|
|
|
|
parentId: null,
|
|
|
|
|
|
...overrides,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function makeLine(id: string, x1: number, y1: number, x2: number, y2: number, layerId = 'layer-1'): CADElement {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id,
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
layerId,
|
|
|
|
|
|
x: (x1 + x2) / 2,
|
|
|
|
|
|
y: (y1 + y2) / 2,
|
|
|
|
|
|
width: Math.abs(x2 - x1),
|
|
|
|
|
|
height: Math.abs(y2 - y1),
|
|
|
|
|
|
properties: { x1, y1, x2, y2 },
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function makeRect(id: string, cx: number, cy: number, w: number, h: number, layerId = 'layer-1'): CADElement {
|
|
|
|
|
|
return {
|
|
|
|
|
|
id,
|
|
|
|
|
|
type: 'rect',
|
|
|
|
|
|
layerId,
|
|
|
|
|
|
x: cx,
|
|
|
|
|
|
y: cy,
|
|
|
|
|
|
width: w,
|
|
|
|
|
|
height: h,
|
|
|
|
|
|
properties: {},
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setup(): {
|
|
|
|
|
|
canvas: HTMLCanvasElement;
|
|
|
|
|
|
zpc: ZoomPanController;
|
|
|
|
|
|
spatialIndex: SpatialIndex;
|
|
|
|
|
|
layerManager: LayerManager;
|
|
|
|
|
|
renderEngine: RenderEngine;
|
|
|
|
|
|
selectionEngine: SelectionEngine;
|
|
|
|
|
|
} {
|
|
|
|
|
|
const canvas = createMockCanvas(800, 600);
|
|
|
|
|
|
const zpc = new ZoomPanController(canvas);
|
|
|
|
|
|
const spatialIndex = new SpatialIndex();
|
|
|
|
|
|
const layerManager = new LayerManager();
|
|
|
|
|
|
layerManager.addLayer(makeLayer('layer-1'));
|
|
|
|
|
|
const renderEngine = new RenderEngine(canvas, zpc, spatialIndex, layerManager);
|
|
|
|
|
|
const selectionEngine = new SelectionEngine(renderEngine, spatialIndex, layerManager);
|
|
|
|
|
|
return { canvas, zpc, spatialIndex, layerManager, renderEngine, selectionEngine };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
describe('SelectionEngine', () => {
|
|
|
|
|
|
let s: ReturnType<typeof setup>;
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
|
s = setup();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('initial state', () => {
|
|
|
|
|
|
it('should have empty selection', () => {
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should have default options (single mode, all filter)', () => {
|
|
|
|
|
|
const opts = s.selectionEngine.getOptions();
|
|
|
|
|
|
expect(opts.mode).toBe('single');
|
|
|
|
|
|
expect(opts.filter).toBe('all');
|
|
|
|
|
|
expect(opts.additive).toBe(false);
|
|
|
|
|
|
expect(opts.subtractive).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should have null hover', () => {
|
|
|
|
|
|
expect(s.selectionEngine.getHoverId()).toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('setOptions', () => {
|
|
|
|
|
|
it('should update options partially', () => {
|
|
|
|
|
|
s.selectionEngine.setOptions({ filter: 'lines' });
|
|
|
|
|
|
expect(s.selectionEngine.getOptions().filter).toBe('lines');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clickSelect – hit', () => {
|
|
|
|
|
|
it('should select an element when clicking on it', () => {
|
|
|
|
|
|
const line = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(line);
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(100, 50, [line]);
|
|
|
|
|
|
expect(result).not.toBeNull();
|
|
|
|
|
|
expect(result!.id).toBe('l1');
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().has('l1')).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should select a rect element when clicking within it', () => {
|
|
|
|
|
|
const rect = makeRect('r1', 100, 100, 80, 60);
|
|
|
|
|
|
s.spatialIndex.insert(rect);
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(100, 100, [rect]);
|
|
|
|
|
|
expect(result).not.toBeNull();
|
|
|
|
|
|
expect(result!.id).toBe('r1');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clickSelect – miss', () => {
|
|
|
|
|
|
it('should return null when clicking empty space', () => {
|
|
|
|
|
|
const line = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(line);
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(400, 400, [line]);
|
|
|
|
|
|
expect(result).toBeNull();
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should clear selection when clicking empty space in non-additive mode', () => {
|
|
|
|
|
|
const line = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(line);
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [line]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(1);
|
|
|
|
|
|
s.selectionEngine.clickSelect(400, 400, [line]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clickSelect – additive mode', () => {
|
|
|
|
|
|
it('should add to selection in additive mode', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
const l2 = makeLine('l2', 50, 100, 150, 100);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.spatialIndex.insert(l2);
|
|
|
|
|
|
s.selectionEngine.setOptions({ additive: true });
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1, l2]);
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 100, [l1, l2]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should not clear on miss in additive mode', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.selectionEngine.setOptions({ additive: true });
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
s.selectionEngine.clickSelect(400, 400, [l1]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(1);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clickSelect – subtractive mode', () => {
|
|
|
|
|
|
it('should remove from selection in subtractive mode', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
// First select normally
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().has('l1')).toBe(true);
|
|
|
|
|
|
// Now subtract
|
|
|
|
|
|
s.selectionEngine.setOptions({ subtractive: true });
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().has('l1')).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clickSelect – filter modes', () => {
|
|
|
|
|
|
it('should not select when filter does not match', () => {
|
|
|
|
|
|
const rect = makeRect('r1', 100, 100, 80, 60);
|
|
|
|
|
|
s.spatialIndex.insert(rect);
|
|
|
|
|
|
s.selectionEngine.setOptions({ filter: 'lines' });
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(100, 100, [rect]);
|
|
|
|
|
|
expect(result).toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should select when filter matches lines', () => {
|
|
|
|
|
|
const line = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(line);
|
|
|
|
|
|
s.selectionEngine.setOptions({ filter: 'lines' });
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(100, 50, [line]);
|
|
|
|
|
|
expect(result).not.toBeNull();
|
|
|
|
|
|
expect(result!.id).toBe('l1');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should select rects with rects filter', () => {
|
|
|
|
|
|
const rect = makeRect('r1', 100, 100, 80, 60);
|
|
|
|
|
|
s.spatialIndex.insert(rect);
|
|
|
|
|
|
s.selectionEngine.setOptions({ filter: 'rects' });
|
|
|
|
|
|
const result = s.selectionEngine.clickSelect(100, 100, [rect]);
|
|
|
|
|
|
expect(result).not.toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('box selection', () => {
|
|
|
|
|
|
it('should select elements within box (window mode)', () => {
|
|
|
|
|
|
const r1 = makeRect('r1', 100, 100, 40, 40);
|
|
|
|
|
|
const r2 = makeRect('r2', 300, 300, 40, 40);
|
|
|
|
|
|
s.spatialIndex.insert(r1);
|
|
|
|
|
|
s.spatialIndex.insert(r2);
|
|
|
|
|
|
// Window: left-to-right, fully enclosed
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(50, 50);
|
|
|
|
|
|
s.selectionEngine.updateBoxSelect(200, 200, [r1, r2]);
|
|
|
|
|
|
const selected = s.selectionEngine.finishBoxSelect([r1, r2]);
|
|
|
|
|
|
// r1 bbox: 80,80 to 120,120 — fully within 50,50 to 200,200
|
|
|
|
|
|
expect(selected.length).toBe(1);
|
|
|
|
|
|
expect(selected[0].id).toBe('r1');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should select elements intersecting box (crossing mode)', () => {
|
|
|
|
|
|
const r1 = makeRect('r1', 100, 100, 40, 40);
|
|
|
|
|
|
s.spatialIndex.insert(r1);
|
|
|
|
|
|
// Crossing: right-to-left (start.x > end.x)
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(200, 200);
|
|
|
|
|
|
s.selectionEngine.updateBoxSelect(90, 90, [r1]);
|
|
|
|
|
|
const selected = s.selectionEngine.finishBoxSelect([r1]);
|
|
|
|
|
|
expect(selected.length).toBe(1);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should clear box start/end after finish', () => {
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(50, 50);
|
|
|
|
|
|
s.selectionEngine.updateBoxSelect(100, 100, []);
|
|
|
|
|
|
s.selectionEngine.finishBoxSelect([]);
|
|
|
|
|
|
expect(s.selectionEngine.isBoxSelecting()).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should return empty when no box started', () => {
|
|
|
|
|
|
const result = s.selectionEngine.finishBoxSelect([]);
|
|
|
|
|
|
expect(result).toEqual([]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('cancelBoxSelect should stop box selection', () => {
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(50, 50);
|
|
|
|
|
|
s.selectionEngine.cancelBoxSelect();
|
|
|
|
|
|
expect(s.selectionEngine.isBoxSelecting()).toBe(false);
|
|
|
|
|
|
});
|
2026-07-04 16:43:55 +02:00
|
|
|
|
|
|
|
|
|
|
it('hasBoxMoved should return false when box not started', () => {
|
|
|
|
|
|
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('hasBoxMoved should return false when start equals end (no drag)', () => {
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(50, 50);
|
|
|
|
|
|
expect(s.selectionEngine.hasBoxMoved()).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('hasBoxMoved should return true when box end differs from start', () => {
|
|
|
|
|
|
s.selectionEngine.startBoxSelect(50, 50);
|
|
|
|
|
|
s.selectionEngine.updateBoxSelect(100, 100, []);
|
|
|
|
|
|
expect(s.selectionEngine.hasBoxMoved()).toBe(true);
|
|
|
|
|
|
});
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('clearSelection', () => {
|
|
|
|
|
|
it('should clear all selected ids', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
s.selectionEngine.clearSelection();
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('hover', () => {
|
|
|
|
|
|
it('should set and get hover id', () => {
|
|
|
|
|
|
s.selectionEngine.setHover('el-1');
|
|
|
|
|
|
expect(s.selectionEngine.getHoverId()).toBe('el-1');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should clear hover with null', () => {
|
|
|
|
|
|
s.selectionEngine.setHover('el-1');
|
|
|
|
|
|
s.selectionEngine.setHover(null);
|
|
|
|
|
|
expect(s.selectionEngine.getHoverId()).toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('selectByIds', () => {
|
|
|
|
|
|
it('should select by ids', () => {
|
|
|
|
|
|
s.selectionEngine.selectByIds(['a', 'b', 'c']);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(3);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should add to selection when additive=true', () => {
|
|
|
|
|
|
s.selectionEngine.selectByIds(['a']);
|
|
|
|
|
|
s.selectionEngine.selectByIds(['b'], true);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('selectAll', () => {
|
|
|
|
|
|
it('should select all visible elements matching filter', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
const l2 = makeLine('l2', 50, 100, 150, 100);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.spatialIndex.insert(l2);
|
|
|
|
|
|
s.selectionEngine.selectAll([l1, l2]);
|
|
|
|
|
|
expect(s.selectionEngine.getSelectedIds().size).toBe(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('invertSelection', () => {
|
|
|
|
|
|
it('should invert current selection', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
const l2 = makeLine('l2', 50, 100, 150, 100);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.spatialIndex.insert(l2);
|
|
|
|
|
|
s.selectionEngine.selectByIds(['l1']);
|
|
|
|
|
|
s.selectionEngine.invertSelection([l1, l2]);
|
|
|
|
|
|
const ids = s.selectionEngine.getSelectedIds();
|
|
|
|
|
|
expect(ids.has('l1')).toBe(false);
|
|
|
|
|
|
expect(ids.has('l2')).toBe(true);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('quickSelect', () => {
|
|
|
|
|
|
it('should select by type', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
const r1 = makeRect('r1', 100, 100, 80, 60);
|
|
|
|
|
|
const result = s.selectionEngine.quickSelect([l1, r1], { type: 'line' });
|
|
|
|
|
|
expect(result.length).toBe(1);
|
|
|
|
|
|
expect(result[0].id).toBe('l1');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should select by layerId', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50, 'layer-1');
|
|
|
|
|
|
const l2 = makeLine('l2', 50, 100, 150, 100, 'layer-2');
|
|
|
|
|
|
const result = s.selectionEngine.quickSelect([l1, l2], { layerId: 'layer-1' });
|
|
|
|
|
|
expect(result.length).toBe(1);
|
|
|
|
|
|
expect(result[0].id).toBe('l1');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('listeners', () => {
|
|
|
|
|
|
it('should call listener on selection change', () => {
|
|
|
|
|
|
let called = false;
|
|
|
|
|
|
let received: CADElement[] = [];
|
|
|
|
|
|
s.selectionEngine.addListener((els) => {
|
|
|
|
|
|
called = true;
|
|
|
|
|
|
received = els;
|
|
|
|
|
|
});
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
expect(called).toBe(true);
|
|
|
|
|
|
expect(received.length).toBe(1);
|
|
|
|
|
|
expect(received[0].id).toBe('l1');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should remove listener', () => {
|
|
|
|
|
|
let called = false;
|
|
|
|
|
|
const fn = (els: CADElement[]) => { called = true; };
|
|
|
|
|
|
s.selectionEngine.addListener(fn);
|
|
|
|
|
|
s.selectionEngine.removeListener(fn);
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
s.spatialIndex.insert(l1);
|
|
|
|
|
|
s.selectionEngine.clickSelect(100, 50, [l1]);
|
|
|
|
|
|
expect(called).toBe(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('getSelectedElements', () => {
|
|
|
|
|
|
it('should filter allElements by selected ids', () => {
|
|
|
|
|
|
const l1 = makeLine('l1', 50, 50, 150, 50);
|
|
|
|
|
|
const l2 = makeLine('l2', 50, 100, 150, 100);
|
|
|
|
|
|
s.selectionEngine.selectByIds(['l1']);
|
|
|
|
|
|
const result = s.selectionEngine.getSelectedElements([l1, l2]);
|
|
|
|
|
|
expect(result.length).toBe(1);
|
|
|
|
|
|
expect(result[0].id).toBe('l1');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|