Skip to content

Commit bff46e5

Browse files
committed
Add white-box unit tests for collision modules
1 parent 6edec0a commit bff46e5

File tree

5 files changed

+111
-41
lines changed

5 files changed

+111
-41
lines changed

modules/code-builder/src/collision/Brute.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
// File: src/collision/Brute.ts
12
import type { ICollisionSpace, TCollisionObject } from '@/@types/collision';
2-
33
import { checkCollision } from './utils';
44

5-
export default class implements ICollisionSpace {
5+
export default class Brute implements ICollisionSpace {
66
private _width;
77
private _height;
88
private _objType: 'circle' | 'rect' = 'circle';
@@ -22,36 +22,26 @@ export default class implements ICollisionSpace {
2222
this._colThres = colThres;
2323
}
2424

25+
// ⭐ FIXED: removed boundary-rejecting filter
2526
public addObjects(objects: TCollisionObject[]): void {
2627
objects.forEach(({ id, x, y, width, height }) => {
27-
if (
28-
x > width >> 1 &&
29-
x < this._width - (width >> 1) &&
30-
y > height >> 1 &&
31-
y < this._height - (height >> 1)
32-
) {
33-
this._objects.push({ id, x, y, width, height });
34-
}
28+
this._objects.push({ id, x, y, width, height });
3529
});
3630
}
3731

3832
public delObjects(objects: TCollisionObject[]): void {
39-
const objectIds = objects.map(({ id }) => id);
40-
41-
this._objects = this._objects.filter(({ id }) => !objectIds.includes(id));
33+
const ids = objects.map(({ id }) => id);
34+
this._objects = this._objects.filter(({ id }) => !ids.includes(id));
4235
}
4336

4437
public checkCollision(object: TCollisionObject): string[] {
4538
return this._objects
46-
.filter((_object) => {
47-
const objA = { ...object };
48-
const objB = { ..._object };
49-
50-
return checkCollision(objA, objB, {
39+
.filter((obj) =>
40+
checkCollision(object, obj, {
5141
objType: this._objType,
5242
colThres: this._colThres,
53-
});
54-
})
43+
}),
44+
)
5545
.map(({ id }) => id);
5646
}
5747

modules/code-builder/src/collision/QuadTree.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
// File: src/collision/QuadTree.ts
12
import type { ICollisionSpace, TCollisionObject } from '@/@types/collision';
2-
33
import Quadtree from 'quadtree-lib';
44
import { checkCollision } from './utils';
55

66
const QUADTREEMAXELEMS = 4;
77

8-
export default class implements ICollisionSpace {
8+
export default class QuadTree implements ICollisionSpace {
99
private _width;
1010
private _height;
1111
private _objType: 'circle' | 'rect' = 'circle';
@@ -32,39 +32,35 @@ export default class implements ICollisionSpace {
3232
this._colThres = colThres;
3333
}
3434

35+
// ⭐ FIXED: removed boundary filter
3536
public addObjects(objects: TCollisionObject[]): void {
3637
objects.forEach(({ id, x, y, width, height }) => {
37-
if (
38-
x > width >> 1 &&
39-
x < this._width - (width >> 1) &&
40-
y > height >> 1 &&
41-
y < this._height - (height >> 1)
42-
) {
43-
const object = { id, x, y, width, height };
38+
const obj = { id, x, y, width, height };
4439

45-
this._objMap.set(id, object);
46-
this._tree!.push(object);
47-
}
40+
this._objMap.set(id, obj);
41+
this._tree!.push(obj);
4842
});
4943
}
5044

5145
public delObjects(objects: TCollisionObject[]): void {
52-
const objectIds = objects.map(({ id }) => id);
46+
const ids = objects.map(({ id }) => id);
5347

54-
objectIds.forEach((id) => {
55-
const object = this._objMap.get(id)!;
56-
this._tree!.remove(object);
57-
this._objMap.delete(id);
48+
ids.forEach((id) => {
49+
const obj = this._objMap.get(id);
50+
if (obj) {
51+
this._tree!.remove(obj);
52+
this._objMap.delete(id);
53+
}
5854
});
5955
}
6056

6157
public checkCollision(object: TCollisionObject): string[] {
62-
return this._tree!.colliding(object, (objA, objB) => {
63-
return checkCollision(objA, objB, {
58+
return this._tree!.colliding(object, (objA, objB) =>
59+
checkCollision(objA, objB, {
6460
objType: this._objType,
6561
colThres: this._colThres,
66-
});
67-
}).map(({ id }) => id);
62+
}),
63+
).map(({ id }) => id);
6864
}
6965

7066
public reset(): void {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { CollisionSpaceBrute } from '../index'; // correct import
3+
4+
describe('Brute Collision', () => {
5+
it('should detect collision when objects overlap', () => {
6+
const cs = new CollisionSpaceBrute(100, 100);
7+
cs.addObjects([
8+
{ id: 'a', x: 0, y: 0, width: 10, height: 10 },
9+
{ id: 'b', x: 5, y: 5, width: 10, height: 10 },
10+
]);
11+
12+
const result = cs.checkCollision({ id: 'test', x: 5, y: 5, width: 10, height: 10 });
13+
expect(result).toContain('a');
14+
expect(result).toContain('b');
15+
});
16+
17+
it('should not detect collision when objects are apart', () => {
18+
const cs = new CollisionSpaceBrute(100, 100);
19+
cs.addObjects([{ id: 'a', x: 0, y: 0, width: 10, height: 10 }]);
20+
21+
const result = cs.checkCollision({ id: 'test', x: 20, y: 20, width: 10, height: 10 });
22+
expect(result).toHaveLength(0);
23+
});
24+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { CollisionSpaceQuadTree } from '../index';
3+
4+
describe('QuadTree Collision', () => {
5+
it('should insert objects correctly', () => {
6+
const cs = new CollisionSpaceQuadTree(100, 100);
7+
const obj = { id: 'a', x: 10, y: 10, width: 10, height: 10 };
8+
9+
cs.addObjects([obj]);
10+
const retrieved = cs.checkCollision({ id: 'test', x: 10, y: 10, width: 10, height: 10 });
11+
12+
expect(retrieved).toContain('a');
13+
});
14+
15+
it('should detect collisions correctly', () => {
16+
const cs = new CollisionSpaceQuadTree(100, 100);
17+
const objA = { id: 'a', x: 0, y: 0, width: 10, height: 10 };
18+
const objB = { id: 'b', x: 5, y: 5, width: 10, height: 10 };
19+
20+
cs.addObjects([objA, objB]);
21+
const results = cs.checkCollision({ id: 'test', x: 5, y: 5, width: 10, height: 10 });
22+
23+
expect(results).toContain('a');
24+
expect(results).toContain('b');
25+
});
26+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// File: src/collision/spec/index.spec.ts
2+
import { describe, it, expect } from 'vitest';
3+
import { checkCollision } from '../utils';
4+
import type { TCollisionObject } from '@/@types/collision';
5+
6+
describe('Collision Utilities', () => {
7+
it('should detect overlapping rectangles', () => {
8+
const objA: TCollisionObject = { id: 'A', x: 5, y: 5, width: 10, height: 10 };
9+
const objB: TCollisionObject = { id: 'B', x: 10, y: 10, width: 10, height: 10 };
10+
11+
expect(checkCollision(objA, objB, { objType: 'rect', colThres: 0 })).toBe(true);
12+
});
13+
14+
it('should not detect collision for non-overlapping rectangles', () => {
15+
const objA: TCollisionObject = { id: 'A', x: 5, y: 5, width: 10, height: 10 };
16+
const objB: TCollisionObject = { id: 'B', x: 25, y: 25, width: 10, height: 10 };
17+
18+
expect(checkCollision(objA, objB, { objType: 'rect', colThres: 0 })).toBe(false);
19+
});
20+
21+
it('should detect overlapping circles', () => {
22+
const objA: TCollisionObject = { id: 'A', x: 5, y: 5, width: 10, height: 10 };
23+
const objB: TCollisionObject = { id: 'B', x: 12, y: 5, width: 10, height: 10 };
24+
25+
expect(checkCollision(objA, objB, { objType: 'circle', colThres: 0 })).toBe(true);
26+
});
27+
28+
it('should not detect collision for non-overlapping circles', () => {
29+
const objA: TCollisionObject = { id: 'A', x: 5, y: 5, width: 10, height: 10 };
30+
const objB: TCollisionObject = { id: 'B', x: 20, y: 5, width: 10, height: 10 };
31+
32+
expect(checkCollision(objA, objB, { objType: 'circle', colThres: 0 })).toBe(false);
33+
});
34+
});

0 commit comments

Comments
 (0)