import { describe, it, expect } from 'vitest';
import { inCircle, inCone, angleBetween, nearest } from '../shapes';
describe('inCircle', () => {
it('точка внутри и на границе', () => {
const c = { x: 0, y: 0 };
expect(inCircle(c, 10, { x: 3, y: 4 })).toBe(true);
expect(inCircle(c, 5, { x: 3, y: 4 })).toBe(true); // ровно 5
expect(inCircle(c, 5, { x: 3, y: 5 })).toBe(false);
expect(inCircle(c, 0, { x: 0, y: 0 })).toBe(true);
});
it('точка вне центра', () => {
expect(inCircle({ x: 100, y: 50 }, 10, { x: 0, y: 0 })).toBe(false);
// расстояние sqrt(50² + 30²) ≈ 58.3
expect(inCircle({ x: 100, y: 50 }, 60, { x: 50, y: 20 })).toBe(true);
});
});
describe('inCone', () => {
const from = { x: 0, y: 0 };
const dir = { x: 1, y: 0 }; // смотрит вправо
const halfAngle = Math.PI / 4; // 45°
it('внутри конуса', () => {
expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 10 })).toBe(true);
expect(inCone(from, dir, 50, halfAngle, { x: 30, y: -10 })).toBe(true);
});
it('за пределами дальности', () => {
expect(inCone(from, dir, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
});
it('за границей угла («слепая зона» за спиной)', () => {
expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 40 })).toBe(false);
expect(inCone(from, dir, 50, halfAngle, { x: -30, y: 0 })).toBe(false);
});
it('вблизи границы угла и радиуса', () => {
// чуть внутри границы угла 45° (tan 45°=1)
expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 9.9 })).toBe(true);
// чуть снаружи
expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 10.1 })).toBe(false);
// расстояние ровно 50 — граница радиуса включена
expect(inCone(from, dir, 50, halfAngle, { x: 50, y: 0 })).toBe(true);
expect(inCone(from, dir, 50, halfAngle, { x: 50.1, y: 0 })).toBe(false);
});
it('нулевое направление — только дальность', () => {
expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 0, y: -40 })).toBe(true);
expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
});
});
describe('angleBetween', () => {
it('известные углы', () => {
expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 1 })).toBeCloseTo(Math.PI / 2);
expect(angleBetween({ x: 1, y: 0 }, { x: -1, y: 0 })).toBeCloseTo(Math.PI);
expect(angleBetween({ x: 2, y: 0 }, { x: 5, y: 0 })).toBeCloseTo(0);
});
it('нулевой вектор даёт 0 (без NaN)', () => {
expect(angleBetween({ x: 0, y: 0 }, { x: 1, y: 0 })).toBe(0);
expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 0 })).toBe(0);
});
});
describe('nearest', () => {
it('выбирает ближайшую', () => {
const list = [
{ x: 50, y: 0 },
{ x: 10, y: 0 },
{ x: 30, y: 0 }
];
expect(nearest(list, { x: 0, y: 0 })).toEqual({ x: 10, y: 0 });
});
it('учитывает maxRange', () => {
const list = [{ x: 100, y: 0 }];
expect(nearest(list, { x: 0, y: 0 }, 50)).toBeNull();
expect(nearest(list, { x: 0, y: 0 }, 100)).toEqual({ x: 100, y: 0 });
});
it('пустой список — null', () => {
expect(nearest([], { x: 0, y: 0 })).toBeNull();
});
});