From b84acfc3e7a2714a02c587d15db456e47e950c2d Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 20 Nov 2025 15:25:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B=D1=87=D0=B8=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BE=D1=80=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=20=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calculateCoordinates.spec.ts | 163 ++++++++++++++++++ .../calculateCoordinates.ts | 57 ++++++ 2 files changed, 220 insertions(+) create mode 100644 src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.spec.ts create mode 100644 src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.ts diff --git a/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.spec.ts b/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.spec.ts new file mode 100644 index 0000000..2abe2a4 --- /dev/null +++ b/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.spec.ts @@ -0,0 +1,163 @@ +import { CIRCLE_RADIUS } from '@/widgets/TimeFrameSlider/model' + +import { calculateCoordinates } from './calculateCoordinates' + +describe('calculateCoordinates', () => { + // Тесты на валидацию dotsAmount + describe('Валидация dotsAmount', () => { + it('должен выбросить ошибку, если dotsAmount меньше 2', () => { + expect(() => calculateCoordinates(1, 0)).toThrow( + 'Количество точек должно быть от 2 до 6' + ) + }) + + it('должен выбросить ошибку, если dotsAmount равен 0', () => { + expect(() => calculateCoordinates(0, 0)).toThrow( + 'Количество точек должно быть от 2 до 6' + ) + }) + + it('должен выбросить ошибку, если dotsAmount отрицательный', () => { + expect(() => calculateCoordinates(-1, 0)).toThrow( + 'Количество точек должно быть от 2 до 6' + ) + }) + + it('должен выбросить ошибку, если dotsAmount больше 6', () => { + expect(() => calculateCoordinates(7, 0)).toThrow( + 'Количество точек должно быть от 2 до 6' + ) + }) + }) + + // Тесты на валидацию dotIndex + describe('Валидация dotIndex', () => { + it('должен выбросить ошибку, если dotIndex отрицательный', () => { + expect(() => calculateCoordinates(4, -1)).toThrow( + 'Индекс точки должен быть от 0 до 5' + ) + }) + + it('должен выбросить ошибку, если dotIndex больше 5', () => { + expect(() => calculateCoordinates(6, 6)).toThrow( + 'Индекс точки должен быть от 0 до 5' + ) + }) + + it('должен выбросить ошибку, если dotIndex больше или равен dotsAmount', () => { + expect(() => calculateCoordinates(3, 3)).toThrow( + 'Индекс точки должен быть меньше количества точек' + ) + }) + + it('должен выбросить ошибку, если dotIndex больше dotsAmount', () => { + expect(() => calculateCoordinates(2, 4)).toThrow( + 'Индекс точки должен быть меньше количества точек' + ) + }) + }) + + // Тесты на корректные вычисления координат + describe('Корректные вычисления координат', () => { + it('должен вернуть корректные координаты для 2 точек, индекс 0', () => { + const result = calculateCoordinates(2, 0) + expect(result.x).toBeCloseTo(CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть корректные координаты для 2 точек, индекс 1', () => { + const result = calculateCoordinates(2, 1) + expect(result.x).toBeCloseTo(-CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть корректные координаты для 4 точек, индекс 0', () => { + const result = calculateCoordinates(4, 0) + expect(result.x).toBeCloseTo(CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть корректные координаты для 4 точек, индекс 1', () => { + const result = calculateCoordinates(4, 1) + expect(result.x).toBeCloseTo(0, 5) + expect(result.y).toBeCloseTo(CIRCLE_RADIUS, 5) + }) + + it('должен вернуть корректные координаты для 4 точек, индекс 2', () => { + const result = calculateCoordinates(4, 2) + expect(result.x).toBeCloseTo(-CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть корректные координаты для 4 точек, индекс 3', () => { + const result = calculateCoordinates(4, 3) + expect(result.x).toBeCloseTo(0, 5) + expect(result.y).toBeCloseTo(-CIRCLE_RADIUS, 5) + }) + + it('должен вернуть корректные координаты для 6 точек, индекс 0', () => { + const result = calculateCoordinates(6, 0) + expect(result.x).toBeCloseTo(CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть корректные координаты для 6 точек, индекс 3', () => { + const result = calculateCoordinates(6, 3) + expect(result.x).toBeCloseTo(-CIRCLE_RADIUS, 5) + expect(result.y).toBeCloseTo(0, 5) + }) + + it('должен вернуть координаты с правильной структурой объекта', () => { + const result = calculateCoordinates(3, 0) + expect(result).toHaveProperty('x') + expect(result).toHaveProperty('y') + expect(typeof result.x).toBe('number') + expect(typeof result.y).toBe('number') + }) + }) + + // Граничные случаи + describe('Граничные случаи', () => { + it('должен работать с минимальным количеством точек (2)', () => { + expect(() => calculateCoordinates(2, 0)).not.toThrow() + expect(() => calculateCoordinates(2, 1)).not.toThrow() + }) + + it('должен работать с максимальным количеством точек (6)', () => { + expect(() => calculateCoordinates(6, 0)).not.toThrow() + expect(() => calculateCoordinates(6, 5)).not.toThrow() + }) + + it('должен работать с последним допустимым индексом для каждого количества точек', () => { + expect(() => calculateCoordinates(2, 1)).not.toThrow() + expect(() => calculateCoordinates(3, 2)).not.toThrow() + expect(() => calculateCoordinates(4, 3)).not.toThrow() + expect(() => calculateCoordinates(5, 4)).not.toThrow() + expect(() => calculateCoordinates(6, 5)).not.toThrow() + }) + }) + + // Тесты на математическую корректность + describe('Математическая корректность', () => { + it('должен расположить точки на окружности заданного радиуса', () => { + const result = calculateCoordinates(4, 1) + const distanceFromCenter = Math.sqrt(result.x ** 2 + result.y ** 2) + expect(distanceFromCenter).toBeCloseTo(CIRCLE_RADIUS, 5) + }) + + it('должен равномерно распределять точки по окружности', () => { + const dotsAmount = 4 + const results = [] + for (let i = 0; i < dotsAmount; i++) { + results.push(calculateCoordinates(dotsAmount, i)) + } + + // Проверяем, что все точки на одинаковом расстоянии от центра + const distances = results.map((r) => Math.sqrt(r.x ** 2 + r.y ** 2)) + const firstDistance = distances[0] + distances.forEach((distance) => { + expect(distance).toBeCloseTo(firstDistance, 5) + }) + }) + }) +}) diff --git a/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.ts b/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.ts new file mode 100644 index 0000000..122ce53 --- /dev/null +++ b/src/widgets/TimeFrameSlider/lib/utils/calculateCoordinates/calculateCoordinates.ts @@ -0,0 +1,57 @@ +import { + CIRCLE_RADIUS, + FULL_CIRCLE_DEGREES, + HALF_CIRCLE_DEGREES, +} from '@/widgets/TimeFrameSlider/model' + +/** + * Интерфейс для координат точки. + */ +export interface DotCoordinates { + /** + * X координата точки. + */ + x: number + /** + * Y координата точки. + */ + y: number +} + +/** + * Функция для вычисления координат точки на круге исходя из общего количества точек и индекса текущей точки. + * @param {number} dotsAmount - Количество точек (от 2 до 6). + * @param {number} dotIndex - Индекс текущей точки. + * @returns {DotCoordinates} Координаты точки. + */ +export function calculateCoordinates( + dotsAmount: number, + dotIndex: number +): DotCoordinates { + // Валидация dotsAmount + if (dotsAmount < 2 || dotsAmount > 6) { + throw new Error('Количество точек должно быть от 2 до 6') + } + + // Валидация dotIndex + if (dotIndex < 0 || dotIndex > 5) { + throw new Error('Индекс точки должен быть от 0 до 5') + } + + // Дополнительная проверка: dotIndex не должен превышать dotsAmount - 1 + if (dotIndex >= dotsAmount) { + throw new Error('Индекс точки должен быть меньше количества точек') + } + + // Угол для текущей точки (в градусах) + const angle = (FULL_CIRCLE_DEGREES / dotsAmount) * dotIndex + + // Конвертация в радианы для тригонометрических функций + const radian = (angle * Math.PI) / HALF_CIRCLE_DEGREES + + // Вычисление координат на круге + const x = CIRCLE_RADIUS * Math.cos(radian) + const y = CIRCLE_RADIUS * Math.sin(radian) + + return { x, y } +}