stratoflights-web/tests/unit/ensemble.test.ts
2026-08-14 00:01:49 +09:00

49 lines
1.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { parseEnsemble, ellipseRing } from '$domain';
import type { EnsembleFootprint } from '$domain';
describe('parseEnsemble', () => {
it('narrows an ensemble payload', () => {
const parsed = parseEnsemble({
ensemble: { members: [{ member: 0, landing: { lat: 1, lng: 2, alt: 0 } }], failed: [] }
});
expect(parsed?.members).toHaveLength(1);
expect(parsed?.footprint).toBeNull();
});
it('rejects a plain single-run result', () => {
expect(parseEnsemble({ prediction: [] })).toBeNull();
expect(parseEnsemble(null)).toBeNull();
expect(parseEnsemble({ ensemble: {} })).toBeNull();
});
});
describe('ellipseRing', () => {
const footprint: EnsembleFootprint = {
count: 5,
mean: { lat: 62, lng: 129 },
radius_km: 10,
ellipse: { semi_major_km: 20, semi_minor_km: 10, bearing_deg: 0 }
};
it('returns a closed ring centred on the mean', () => {
const ring = ellipseRing(footprint, 32);
expect(ring).toHaveLength(33);
expect(ring[0][0]).toBeCloseTo(ring[32][0], 9);
expect(ring[0][1]).toBeCloseTo(ring[32][1], 9);
const lats = ring.map((p) => p[0]);
expect((Math.min(...lats) + Math.max(...lats)) / 2).toBeCloseTo(62, 6);
});
it('corrects longitude for latitude so the ellipse is not stretched', () => {
const ring = ellipseRing(footprint, 64);
const lats = ring.map((p) => p[0]);
const lngs = ring.map((p) => p[1]);
// 20 km north-south at bearing 0 => ~0.18° of latitude.
expect(Math.max(...lats) - 62).toBeCloseTo(20 / 111.32, 3);
// 10 km east-west spans more degrees at 62°N than it would at the equator.
const halfSpanDeg = Math.max(...lngs) - 129;
expect(halfSpanDeg).toBeGreaterThan(10 / 111.32);
expect(halfSpanDeg).toBeCloseTo(10 / (111.32 * Math.cos((62 * Math.PI) / 180)), 3);
});
});