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); }); });