Files
Flight-Ops/lib/mapData.test.ts
T

140 lines
5.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { StateSnapshot } from './game/state';
import { planesGeoJson, routesGeoJson, type AirportCoord } from './mapData';
const AIRPORTS = new Map<string, AirportCoord>([
['EDDF', { lat: 50.03, lon: 8.56 }],
['EDDV', { lat: 52.46, lon: 9.68 }],
['EHAM', { lat: 52.31, lon: 4.76 }],
]);
function snapshot(overrides: Partial<StateSnapshot> = {}): StateSnapshot {
return {
account: { cash: 0, reputation: 0 },
simState: { clockMs: 0, timeScale: 1, paused: false },
planes: [],
packages: [],
catalog: [
{ model: 'atr72', label: 'ATR 72-600', price: 200_000, maxFuel: 6300, burnPerKm: 1.75, speedKmh: 510, capacity: 6 },
],
fuelPricePerL: 0.8,
sellFraction: 0.5,
...overrides,
};
}
describe('planesGeoJson', () => {
it('returns an empty collection for a null snapshot', () => {
expect(planesGeoJson(null)).toEqual({ type: 'FeatureCollection', features: [] });
});
it('emits one Point per plane at its polled lat/lon with popup properties', () => {
const fc = planesGeoJson(
snapshot({
planes: [
{ id: 'plane-starter', model: 'atr72', status: 'available', fuel: 6300, maxFuel: 6300, locationIcao: 'EDDF', locationName: 'Frankfurt Main Airport', destIcao: null, destName: null, departureSimTime: null, lat: 50.03, lon: 8.56 },
{ id: 'plane-2', model: 'atr72', status: 'flying', fuel: 4000, maxFuel: 6300, locationIcao: 'EDDF', locationName: 'Frankfurt Main Airport', destIcao: 'EDDV', destName: 'Hannover Airport', departureSimTime: 1000, lat: 51.2, lon: 9.1 },
],
}),
);
expect(fc.features).toHaveLength(2);
const grounded = fc.features[0];
expect(grounded.geometry.coordinates).toEqual([8.56, 50.03]); // [lon, lat]
expect(grounded.properties.flying).toBe(false);
expect(grounded.properties.modelLabel).toBe('ATR 72-600'); // catalog label, not raw model id
expect(grounded.properties.fuelText).toBe('6300 / 6300 L');
expect(grounded.properties.routeText).toBe('');
const flying = fc.features[1];
expect(flying.geometry.coordinates).toEqual([9.1, 51.2]); // live polled position
expect(flying.properties.flying).toBe(true);
expect(flying.properties.status).toBe('flying');
expect(flying.properties.fuelText).toBe('4000 / 6300 L');
expect(flying.properties.routeText).toBe('Frankfurt Main Airport → Hannover Airport'); // from the plane's own dest fields (v0.12.0)
});
it('falls back to the raw model id when the catalog has no entry', () => {
const fc = planesGeoJson(
snapshot({
catalog: [],
planes: [
{ id: 'p1', model: 'mystery', status: 'available', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'x', destIcao: null, destName: null, departureSimTime: null, lat: 0, lon: 0 },
],
}),
);
expect(fc.features[0].properties.modelLabel).toBe('mystery');
});
it('leaves routeText empty for a flying plane without a destination name', () => {
const fc = planesGeoJson(
snapshot({
planes: [
{ id: 'p1', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'x', destIcao: null, destName: null, departureSimTime: 0, lat: 0, lon: 0 },
],
}),
);
expect(fc.features[0].properties.routeText).toBe('');
});
});
describe('routesGeoJson (v0.12.0 — one line per flying plane\'s current leg)', () => {
it('returns an empty collection for a null snapshot', () => {
expect(routesGeoJson(null, AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
});
it('draws nothing for grounded planes, whatever their packages', () => {
const fc = routesGeoJson(
snapshot({
planes: [
{ id: 'p1', model: 'atr72', status: 'available', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'F', destIcao: null, destName: null, departureSimTime: null, lat: 50.03, lon: 8.56 },
],
}),
AIRPORTS,
);
expect(fc.features).toHaveLength(0);
});
it('flying plane → one line origin→dest_icao with the plane id', () => {
const fc = routesGeoJson(
snapshot({
planes: [
{ id: 'p1', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'F', destIcao: 'EHAM', destName: 'A', departureSimTime: 0, lat: 51, lon: 9 },
],
}),
AIRPORTS,
);
expect(fc.features).toHaveLength(1);
expect(fc.features[0].properties).toEqual({ planeId: 'p1' });
expect(fc.features[0].geometry.coordinates).toEqual([
[8.56, 50.03], // EDDF [lon, lat]
[4.76, 52.31], // EHAM
]);
});
it('skips a flying plane whose endpoint is missing from the airport dataset', () => {
const fc = routesGeoJson(
snapshot({
planes: [
{ id: 'p1', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'ZZZZ', locationName: '?', destIcao: 'EDDV', destName: 'H', departureSimTime: 0, lat: 0, lon: 0 },
],
}),
AIRPORTS,
);
expect(fc.features).toHaveLength(0);
});
it('one line per flying plane — multiple planes draw multiple legs', () => {
const fc = routesGeoJson(
snapshot({
planes: [
{ id: 'p1', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'F', destIcao: 'EDDV', destName: 'H', departureSimTime: 0, lat: 51, lon: 9 },
{ id: 'p2', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'EDDV', locationName: 'H', destIcao: 'EHAM', destName: 'A', departureSimTime: 0, lat: 52.4, lon: 9.6 },
],
}),
AIRPORTS,
);
expect(fc.features.map((f) => f.properties.planeId).sort()).toEqual(['p1', 'p2']);
});
});