Files
web-cad/frontend/tests/SpatialIndex.test.ts
T

154 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* SpatialIndex Tests rbush-based spatial search
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { SpatialIndex } from '../src/canvas/SpatialIndex';
import type { CADElement } from '../src/types/cad.types';
function makeElement(id: string, x: number, y: number, width: number, height: number): CADElement {
return {
id,
type: 'rect',
layerId: 'layer-1',
x,
y,
width,
height,
properties: {},
};
}
describe('SpatialIndex', () => {
let index: SpatialIndex;
beforeEach(() => {
index = new SpatialIndex();
});
describe('insert & search', () => {
it('should find an inserted element within its bounding box', () => {
const el = makeElement('el-1', 50, 50, 20, 20);
index.insert(el);
const results = index.search({ minX: 40, minY: 40, maxX: 60, maxY: 60 });
expect(results.length).toBe(1);
expect(results[0].id).toBe('el-1');
});
it('should not find an element outside the search viewport', () => {
const el = makeElement('el-1', 100, 100, 20, 20);
index.insert(el);
const results = index.search({ minX: 0, minY: 0, maxX: 50, maxY: 50 });
expect(results.length).toBe(0);
});
it('should find multiple elements within viewport', () => {
index.insert(makeElement('el-1', 10, 10, 10, 10));
index.insert(makeElement('el-2', 20, 20, 10, 10));
index.insert(makeElement('el-3', 100, 100, 10, 10));
const results = index.search({ minX: 0, minY: 0, maxX: 30, maxY: 30 });
expect(results.length).toBe(2);
const ids = results.map(e => e.id).sort();
expect(ids).toEqual(['el-1', 'el-2']);
});
it('should handle elements at origin', () => {
index.insert(makeElement('el-origin', 0, 0, 10, 10));
const results = index.search({ minX: -5, minY: -5, maxX: 5, maxY: 5 });
expect(results.length).toBe(1);
expect(results[0].id).toBe('el-origin');
});
});
describe('bulkInsert', () => {
it('should bulk insert and find all elements', () => {
const elements = [
makeElement('bulk-1', 10, 10, 5, 5),
makeElement('bulk-2', 20, 20, 5, 5),
makeElement('bulk-3', 30, 30, 5, 5),
makeElement('bulk-4', 200, 200, 5, 5),
];
index.bulkInsert(elements);
const results = index.search({ minX: 0, minY: 0, maxX: 50, maxY: 50 });
expect(results.length).toBe(3);
});
it('should work with empty array', () => {
index.bulkInsert([]);
const results = index.search({ minX: 0, minY: 0, maxX: 100, maxY: 100 });
expect(results.length).toBe(0);
});
});
describe('remove', () => {
it('should remove an element so it is no longer found', () => {
const el = makeElement('el-rm', 50, 50, 10, 10);
index.insert(el);
let results = index.search({ minX: 40, minY: 40, maxX: 60, maxY: 60 });
expect(results.length).toBe(1);
index.remove(el);
results = index.search({ minX: 40, minY: 40, maxX: 60, maxY: 60 });
expect(results.length).toBe(0);
});
it('should remove only the specified element', () => {
const el1 = makeElement('el-keep', 50, 50, 10, 10);
const el2 = makeElement('el-rm', 50, 50, 10, 10);
index.insert(el1);
index.insert(el2);
index.remove(el2);
const results = index.search({ minX: 40, minY: 40, maxX: 60, maxY: 60 });
expect(results.length).toBe(1);
expect(results[0].id).toBe('el-keep');
});
});
describe('clear', () => {
it('should clear all elements from the index', () => {
index.insert(makeElement('el-1', 10, 10, 5, 5));
index.insert(makeElement('el-2', 20, 20, 5, 5));
index.insert(makeElement('el-3', 30, 30, 5, 5));
index.clear();
const results = index.search({ minX: 0, minY: 0, maxX: 100, maxY: 100 });
expect(results.length).toBe(0);
});
});
describe('edge cases', () => {
it('should handle overlapping bounding boxes correctly', () => {
index.insert(makeElement('el-1', 25, 25, 30, 30)); // bbox: 10,10 to 40,40
index.insert(makeElement('el-2', 30, 30, 30, 30)); // bbox: 15,15 to 45,45
const results = index.search({ minX: 10, minY: 10, maxX: 40, maxY: 40 });
expect(results.length).toBe(2);
});
it('should handle zero-size elements', () => {
index.insert(makeElement('el-zero', 50, 50, 0, 0));
const results = index.search({ minX: 49, minY: 49, maxX: 51, maxY: 51 });
expect(results.length).toBe(1);
});
it('should handle negative coordinates', () => {
index.insert(makeElement('el-neg', -50, -50, 20, 20));
const results = index.search({ minX: -60, minY: -60, maxX: -40, maxY: -40 });
expect(results.length).toBe(1);
expect(results[0].id).toBe('el-neg');
});
it('should search with a large viewport containing all elements', () => {
index.insert(makeElement('el-1', 10, 10, 5, 5));
index.insert(makeElement('el-2', 1000, 1000, 5, 5));
const results = index.search({ minX: -10000, minY: -10000, maxX: 10000, maxY: 10000 });
expect(results.length).toBe(2);
});
});
});