feat(framework): server testing utils, transaction storage simplify, pathfinding tests (#384)
## Server Testing Utils - Add TestServer, TestClient, MockRoom for unit testing - Export testing utilities from @esengine/server/testing ## Transaction Storage (BREAKING) - Simplify RedisStorage/MongoStorage to factory pattern only - Remove DI client injection option - Add lazy connection and Symbol.asyncDispose support - Add 161 unit tests with full coverage ## Pathfinding Tests - Add 150 unit tests covering all components - BinaryHeap, Heuristics, AStarPathfinder, GridMap, NavMesh, PathSmoother ## Docs - Update storage.md for new factory pattern API
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import {
|
||||
bresenhamLineOfSight,
|
||||
raycastLineOfSight,
|
||||
LineOfSightSmoother,
|
||||
CatmullRomSmoother,
|
||||
CombinedSmoother
|
||||
} from '../../src/smoothing/PathSmoother';
|
||||
import { GridMap } from '../../src/grid/GridMap';
|
||||
import { createPoint, type IPoint } from '../../src/core/IPathfinding';
|
||||
|
||||
describe('Line of Sight Functions', () => {
|
||||
let grid: GridMap;
|
||||
|
||||
beforeEach(() => {
|
||||
grid = new GridMap(10, 10);
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// bresenhamLineOfSight
|
||||
// =========================================================================
|
||||
|
||||
describe('bresenhamLineOfSight', () => {
|
||||
it('should return true for clear line', () => {
|
||||
expect(bresenhamLineOfSight(0, 0, 5, 5, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for same point', () => {
|
||||
expect(bresenhamLineOfSight(5, 5, 5, 5, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for horizontal line', () => {
|
||||
expect(bresenhamLineOfSight(0, 5, 9, 5, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for vertical line', () => {
|
||||
expect(bresenhamLineOfSight(5, 0, 5, 9, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when blocked', () => {
|
||||
grid.setWalkable(5, 5, false);
|
||||
expect(bresenhamLineOfSight(0, 0, 9, 9, grid)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when start is blocked', () => {
|
||||
grid.setWalkable(0, 0, false);
|
||||
expect(bresenhamLineOfSight(0, 0, 5, 5, grid)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when end is blocked', () => {
|
||||
grid.setWalkable(5, 5, false);
|
||||
expect(bresenhamLineOfSight(0, 0, 5, 5, grid)).toBe(false);
|
||||
});
|
||||
|
||||
it('should detect obstacle in middle', () => {
|
||||
grid.setWalkable(3, 3, false);
|
||||
expect(bresenhamLineOfSight(0, 0, 6, 6, grid)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// raycastLineOfSight
|
||||
// =========================================================================
|
||||
|
||||
describe('raycastLineOfSight', () => {
|
||||
it('should return true for clear line', () => {
|
||||
expect(raycastLineOfSight(0, 0, 5, 5, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for same point', () => {
|
||||
expect(raycastLineOfSight(5, 5, 5, 5, grid)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when blocked', () => {
|
||||
grid.setWalkable(5, 5, false);
|
||||
expect(raycastLineOfSight(0, 0, 9, 9, grid)).toBe(false);
|
||||
});
|
||||
|
||||
it('should work with custom step size', () => {
|
||||
expect(raycastLineOfSight(0, 0, 5, 5, grid, 0.1)).toBe(true);
|
||||
grid.setWalkable(2, 2, false);
|
||||
expect(raycastLineOfSight(0, 0, 5, 5, grid, 0.1)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('LineOfSightSmoother', () => {
|
||||
let grid: GridMap;
|
||||
let smoother: LineOfSightSmoother;
|
||||
|
||||
beforeEach(() => {
|
||||
grid = new GridMap(20, 20);
|
||||
smoother = new LineOfSightSmoother();
|
||||
});
|
||||
|
||||
it('should return same path for 2 or fewer points', () => {
|
||||
const path1: IPoint[] = [createPoint(0, 0)];
|
||||
expect(smoother.smooth(path1, grid)).toEqual(path1);
|
||||
|
||||
const path2: IPoint[] = [createPoint(0, 0), createPoint(5, 5)];
|
||||
expect(smoother.smooth(path2, grid)).toEqual(path2);
|
||||
});
|
||||
|
||||
it('should remove unnecessary waypoints on straight line', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(1, 0),
|
||||
createPoint(2, 0),
|
||||
createPoint(3, 0),
|
||||
createPoint(4, 0),
|
||||
createPoint(5, 0)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0]).toEqual(createPoint(0, 0));
|
||||
expect(result[1]).toEqual(createPoint(5, 0));
|
||||
});
|
||||
|
||||
it('should remove unnecessary waypoints on diagonal', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(1, 1),
|
||||
createPoint(2, 2),
|
||||
createPoint(3, 3),
|
||||
createPoint(4, 4),
|
||||
createPoint(5, 5)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0]).toEqual(createPoint(0, 0));
|
||||
expect(result[1]).toEqual(createPoint(5, 5));
|
||||
});
|
||||
|
||||
it('should keep waypoints around obstacles', () => {
|
||||
// Create obstacle
|
||||
grid.setWalkable(5, 5, false);
|
||||
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(4, 5),
|
||||
createPoint(6, 5),
|
||||
createPoint(10, 10)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
// Should keep at least start, one waypoint near obstacle, and end
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('should use custom line of sight function', () => {
|
||||
const customLOS = (x1: number, y1: number, x2: number, y2: number) => {
|
||||
// Always blocked
|
||||
return false;
|
||||
};
|
||||
|
||||
const customSmoother = new LineOfSightSmoother(customLOS);
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(1, 1),
|
||||
createPoint(2, 2)
|
||||
];
|
||||
|
||||
const result = customSmoother.smooth(path, grid);
|
||||
// Should not simplify because LOS always fails
|
||||
expect(result).toEqual(path);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CatmullRomSmoother', () => {
|
||||
let grid: GridMap;
|
||||
let smoother: CatmullRomSmoother;
|
||||
|
||||
beforeEach(() => {
|
||||
grid = new GridMap(20, 20);
|
||||
smoother = new CatmullRomSmoother(5, 0.5);
|
||||
});
|
||||
|
||||
it('should return same path for 2 or fewer points', () => {
|
||||
const path1: IPoint[] = [createPoint(0, 0)];
|
||||
expect(smoother.smooth(path1, grid)).toEqual(path1);
|
||||
|
||||
const path2: IPoint[] = [createPoint(0, 0), createPoint(5, 5)];
|
||||
expect(smoother.smooth(path2, grid)).toEqual(path2);
|
||||
});
|
||||
|
||||
it('should add interpolation points', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(5, 0),
|
||||
createPoint(10, 0)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
// Should have more points due to interpolation
|
||||
expect(result.length).toBeGreaterThan(path.length);
|
||||
});
|
||||
|
||||
it('should preserve start and end points', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(5, 5),
|
||||
createPoint(10, 0)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
expect(result[0].x).toBeCloseTo(0, 1);
|
||||
expect(result[0].y).toBeCloseTo(0, 1);
|
||||
expect(result[result.length - 1]).toEqual(createPoint(10, 0));
|
||||
});
|
||||
|
||||
it('should create smooth curve', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(5, 5),
|
||||
createPoint(10, 0)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
|
||||
// Check that middle points are near the original waypoint
|
||||
const middlePoints = result.filter(p =>
|
||||
Math.abs(p.x - 5) < 2 && Math.abs(p.y - 5) < 2
|
||||
);
|
||||
expect(middlePoints.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should work with different segment counts', () => {
|
||||
const smootherLow = new CatmullRomSmoother(2);
|
||||
const smootherHigh = new CatmullRomSmoother(10);
|
||||
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(5, 5),
|
||||
createPoint(10, 0)
|
||||
];
|
||||
|
||||
const resultLow = smootherLow.smooth(path, grid);
|
||||
const resultHigh = smootherHigh.smooth(path, grid);
|
||||
|
||||
expect(resultHigh.length).toBeGreaterThan(resultLow.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CombinedSmoother', () => {
|
||||
let grid: GridMap;
|
||||
let smoother: CombinedSmoother;
|
||||
|
||||
beforeEach(() => {
|
||||
grid = new GridMap(20, 20);
|
||||
smoother = new CombinedSmoother(5, 0.5);
|
||||
});
|
||||
|
||||
it('should first simplify then curve smooth', () => {
|
||||
// Path with redundant points
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(1, 0),
|
||||
createPoint(2, 0),
|
||||
createPoint(3, 0),
|
||||
createPoint(4, 0),
|
||||
createPoint(5, 0),
|
||||
createPoint(6, 3),
|
||||
createPoint(7, 6),
|
||||
createPoint(8, 6),
|
||||
createPoint(9, 6),
|
||||
createPoint(10, 6)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
|
||||
// Should have smoothed the path
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
expect(result[0].x).toBeCloseTo(0, 1);
|
||||
expect(result[result.length - 1]).toEqual(createPoint(10, 6));
|
||||
});
|
||||
|
||||
it('should handle simple path', () => {
|
||||
const path: IPoint[] = [
|
||||
createPoint(0, 0),
|
||||
createPoint(10, 10)
|
||||
];
|
||||
|
||||
const result = smoother.smooth(path, grid);
|
||||
expect(result.length).toBe(2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user