This commit is contained in:
gili8420 2026-08-14 00:01:49 +09:00
commit b43955e0d9
162 changed files with 15425 additions and 0 deletions

87
tests/unit/curve.test.ts Normal file
View file

@ -0,0 +1,87 @@
import { describe, it, expect } from 'vitest';
import {
normalizeCurve,
validateCurve,
impliedRates,
parseCurveCsv,
curveToCsv
} from '$domain';
import type { RateCurvePoint } from '$domain';
const curve: RateCurvePoint[] = [
{ order: 0, time_constraint: 0, alt_constraint: 0, rate: 0 },
{ order: 1, time_constraint: 100, alt_constraint: 500, rate: 0 },
{ order: 2, time_constraint: 200, alt_constraint: 1500, rate: 0 }
];
describe('normalizeCurve', () => {
it('sorts by order and renumbers 0..n-1', () => {
const shuffled: RateCurvePoint[] = [
{ order: 7, time_constraint: 200, alt_constraint: 1500, rate: 0 },
{ order: 2, time_constraint: 0, alt_constraint: 0, rate: 0 }
];
expect(normalizeCurve(shuffled).map((p) => [p.order, p.time_constraint])).toEqual([
[0, 0],
[1, 200]
]);
});
});
describe('validateCurve', () => {
it('accepts a strictly increasing curve', () => {
expect(validateCurve(curve)).toEqual([]);
});
it('rejects fewer than two points', () => {
expect(validateCurve([curve[0]])).toContain('curve.errTooFewPoints');
});
it('rejects non-monotonic time (equal or backwards steps)', () => {
const flat = [...curve, { order: 3, time_constraint: 200, alt_constraint: 1600, rate: 0 }];
expect(validateCurve(flat)).toContain('curve.errNonMonotonicTime');
});
it('rejects negative time and altitude', () => {
const bad: RateCurvePoint[] = [
{ order: 0, time_constraint: -10, alt_constraint: -5, rate: 0 },
{ order: 1, time_constraint: 10, alt_constraint: 100, rate: 0 }
];
const problems = validateCurve(bad);
expect(problems).toContain('curve.errNegativeTime');
expect(problems).toContain('curve.errNegativeAltitude');
});
});
describe('impliedRates', () => {
it('derives the vertical rate of each segment', () => {
const rates = impliedRates(curve);
expect(rates).toHaveLength(2);
expect(rates[0].rate).toBeCloseTo(5, 6); // 500 m / 100 s
expect(rates[1].rate).toBeCloseTo(10, 6); // 1000 m / 100 s
});
it('skips zero-length segments instead of dividing by zero', () => {
const flat: RateCurvePoint[] = [
{ order: 0, time_constraint: 0, alt_constraint: 0, rate: 0 },
{ order: 1, time_constraint: 0, alt_constraint: 100, rate: 0 }
];
expect(impliedRates(flat)).toEqual([]);
});
});
describe('parseCurveCsv', () => {
it('parses rows, tolerating a header, blank lines and semicolons', () => {
const csv = 'time_s,altitude_m,rate_ms\n0,0,0\n\n100;500;5\n';
const points = parseCurveCsv(csv);
expect(points).toHaveLength(2);
expect(points[1]).toMatchObject({ order: 1, time_constraint: 100, alt_constraint: 500, rate: 5 });
});
it('returns nothing for text with no numeric rows', () => {
expect(parseCurveCsv('hello\nworld')).toEqual([]);
});
it('round-trips through curveToCsv', () => {
expect(parseCurveCsv(curveToCsv(curve))).toHaveLength(curve.length);
});
});