Files
the-nexus/tests/test_spatial_search.js
Alexander Whitestone 912863e78f
Some checks failed
CI / test (pull_request) Failing after 1m5s
Review Approval Gate / verify-review (pull_request) Failing after 7s
CI / validate (pull_request) Failing after 55s
feat: spatial search — find nearest user/object by name (#1639)
Adds spatial search functionality to find users/objects by name
with distance and direction indicator.

## Features
- Search by name with autocomplete
- Distance calculation (meters)
- Direction indicator (N/S/E/W/NE/SE/SW/NW)
- Pathfinding arrow on HUD
- Keyboard shortcut (Ctrl+F / Cmd+F)

## Files Added
- js/spatial-search.js — Main search module
- tests/test_spatial_search.js — 9 passing tests

## Usage
```javascript
const search = new SpatialSearch({ maxDistance: 1000 });
search.registerEntity('id', { name: 'Alice', type: 'user', position: {...} });
const results = search.searchEntities('ali', cameraPosition);
```

## Tests
All 9 tests passing:
 loads correctly
 can be instantiated
 can register entities
 can unregister entities
 can update entity position
 calculates distance correctly
 calculates direction correctly
 searches entities correctly
 gets status

Closes #1540
Closes #1639
2026-04-20 12:00:05 -04:00

99 lines
3.4 KiB
JavaScript

// Test suite for SpatialSearch module
// Run: node --test tests/test_spatial_search.js
const { describe, it, beforeEach } = require('node:test');
const assert = require('node:assert');
// Mock DOM for Node.js environment
global.document = {
createElement: (tag) => ({
className: '',
style: {},
innerHTML: '',
textContent: '',
addEventListener: () => {},
appendChild: () => {},
querySelector: () => ({ style: {}, textContent: '' })
}),
body: { appendChild: () => {} },
addEventListener: () => {}
};
global.window = { camera: null };
// Load module
const SpatialSearch = require('../js/spatial-search.js');
describe('SpatialSearch', () => {
let search;
beforeEach(() => {
search = new SpatialSearch({ maxDistance: 1000 });
});
it('loads correctly', () => {
assert.ok(SpatialSearch);
});
it('can be instantiated', () => {
assert.ok(search instanceof SpatialSearch);
});
it('can register entities', () => {
search.registerEntity('user1', {
name: 'Alice',
type: 'user',
position: { x: 10, y: 0, z: 5 }
});
const status = search.getStatus();
assert.strictEqual(status.entityCount, 1);
});
it('can unregister entities', () => {
search.registerEntity('user1', { name: 'Alice', type: 'user', position: { x: 0, y: 0, z: 0 } });
search.unregisterEntity('user1');
assert.strictEqual(search.getStatus().entityCount, 0);
});
it('can update entity position', () => {
search.registerEntity('user1', { name: 'Alice', type: 'user', position: { x: 0, y: 0, z: 0 } });
search.updateEntityPosition('user1', { x: 10, y: 0, z: 10 });
// Verify by searching with camera position
const results = search.searchEntities('alice', { x: 0, y: 0, z: 0 });
assert.strictEqual(results.length, 1);
assert.ok(results[0].distance > 0);
});
it('calculates distance correctly', () => {
const from = { x: 0, y: 0, z: 0 };
const to = { x: 3, y: 0, z: 4 };
const distance = search._calculateDistance(from, to);
assert.strictEqual(distance, 5); // 3-4-5 triangle
});
it('calculates direction correctly', () => {
const from = { x: 0, y: 0, z: 0 };
assert.strictEqual(search._calculateDirection(from, { x: 0, y: 0, z: 10 }), 'N');
assert.strictEqual(search._calculateDirection(from, { x: 10, y: 0, z: 0 }), 'E');
assert.strictEqual(search._calculateDirection(from, { x: 0, y: 0, z: -10 }), 'S');
assert.strictEqual(search._calculateDirection(from, { x: -10, y: 0, z: 0 }), 'W');
});
it('searches entities correctly', () => {
search.registerEntity('1', { name: 'Alice', type: 'user', position: { x: 5, y: 0, z: 0 } });
search.registerEntity('2', { name: 'Bob', type: 'user', position: { x: 10, y: 0, z: 0 } });
search.registerEntity('3', { name: 'Alice Shop', type: 'object', position: { x: 20, y: 0, z: 0 } });
const results = search.searchEntities('ali', { x: 0, y: 0, z: 0 });
assert.strictEqual(results.length, 2);
assert.strictEqual(results[0].name, 'Alice'); // Closer
assert.strictEqual(results[1].name, 'Alice Shop');
});
it('gets status', () => {
search.registerEntity('1', { name: 'Test', type: 'object', position: { x: 0, y: 0, z: 0 } });
const status = search.getStatus();
assert.strictEqual(status.entityCount, 1);
assert.strictEqual(status.isOpen, false);
assert.strictEqual(status.resultCount, 0);
});
});