Queue UI fixes: disable would-409 controls, hover trace from landing airport, grounded queues draw dotted lines (ISS-077, v0.41.0)
CI / build + unit + e2e (pull_request) Successful in 9m11s
CI / build + unit + e2e (pull_request) Successful in 9m11s
This commit is contained in:
+39
-4
@@ -140,14 +140,40 @@ describe('routesGeoJson (v0.12.0 — one line per flying plane\'s current leg)',
|
||||
});
|
||||
});
|
||||
|
||||
describe('queuedRoutesGeoJson (ISS-043 v0.26.0 — dotted queue line: dest → queued legs only)', () => {
|
||||
it('draws nothing for a grounded plane, even with a non-empty queue', () => {
|
||||
describe('queuedRoutesGeoJson (ISS-043 v0.26.0 — dotted queue line: dest → queued legs only; ISS-077 v0.41.0 — grounded queues too)', () => {
|
||||
it('grounded plane with a non-empty queue → one feature chaining locationIcao → queued legs (ISS-077)', () => {
|
||||
const fc = queuedRoutesGeoJson(
|
||||
[
|
||||
// Pre-built route while grounded: the line starts at the plane's current airport, not at any dest.
|
||||
{ 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, health: 100, repairUntilSimTime: null, queue: ['EHAM'] },
|
||||
],
|
||||
AIRPORTS,
|
||||
);
|
||||
expect(fc.features).toHaveLength(1);
|
||||
expect(fc.features[0].properties).toEqual({ planeId: 'p1' });
|
||||
const coords = fc.features[0].geometry.coordinates;
|
||||
expect(coords.length).toBe(ROUTE_ARC_SEGMENTS + 1); // one arc, exact endpoints
|
||||
expect(coords[0]).toEqual([8.56, 50.03]); // starts at the plane's airport (EDDF)
|
||||
expect(coords[coords.length - 1]).toEqual([4.76, 52.31]); // ends at the queued airport (EHAM)
|
||||
});
|
||||
|
||||
it('skips a grounded plane whose locationIcao is missing from the airport index', () => {
|
||||
const fc = queuedRoutesGeoJson(
|
||||
[
|
||||
{ id: 'p1', model: 'atr72', status: 'available', fuel: 1, maxFuel: 2, locationIcao: 'ZZZZ', locationName: '?', destIcao: null, destName: null, departureSimTime: null, lat: 0, lon: 0, health: 100, repairUntilSimTime: null, queue: ['EHAM'] },
|
||||
],
|
||||
AIRPORTS,
|
||||
);
|
||||
expect(fc.features).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('draws nothing for a repairing plane, even with a non-empty queue', () => {
|
||||
const fc = queuedRoutesGeoJson(
|
||||
[
|
||||
{ id: 'p1', model: 'atr72', status: 'repairing', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'F', destIcao: null, destName: null, departureSimTime: null, lat: 50.03, lon: 8.56, health: 0, repairUntilSimTime: 1000, queue: ['EHAM'] },
|
||||
],
|
||||
AIRPORTS,
|
||||
);
|
||||
expect(fc.features).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -190,7 +216,7 @@ describe('queuedRoutesGeoJson (ISS-043 v0.26.0 — dotted queue line: dest → q
|
||||
expect(fc.features).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('one feature per qualifying plane — grounded and empty-queue planes draw nothing', () => {
|
||||
it('one feature per qualifying plane — flying and grounded queues draw; empty-queue planes do not', () => {
|
||||
const fc = queuedRoutesGeoJson(
|
||||
[
|
||||
{ id: 'p1', model: 'atr72', status: 'flying', fuel: 1, maxFuel: 2, locationIcao: 'EDDF', locationName: 'F', destIcao: 'EHAM', destName: 'A', departureSimTime: 0, lat: 51, lon: 9, health: 100, repairUntilSimTime: null, queue: ['LEMD'] },
|
||||
@@ -199,7 +225,10 @@ describe('queuedRoutesGeoJson (ISS-043 v0.26.0 — dotted queue line: dest → q
|
||||
],
|
||||
AIRPORTS,
|
||||
);
|
||||
expect(fc.features.map((f) => f.properties.planeId)).toEqual(['p1']);
|
||||
expect(fc.features.map((f) => f.properties.planeId)).toEqual(['p1', 'p3']); // p2 has an empty queue
|
||||
const p3 = fc.features[1].geometry.coordinates;
|
||||
expect(p3[0]).toEqual([9.68, 52.46]); // grounded chain starts at the plane's airport (EDDV)
|
||||
expect(p3[p3.length - 1]).toEqual([-3.56, 40.47]); // …and ends at the queued airport (LEMD)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,6 +283,12 @@ describe('hoverTraceGeoJson (ISS-040 v0.23.2 — on-board package trace from the
|
||||
it('returns an empty collection when the dest airport is missing from the dataset', () => {
|
||||
expect(hoverTraceGeoJson({ lat: 0, lon: 0 }, 'ZZZZ', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
|
||||
});
|
||||
|
||||
// ISS-077 (v0.41.0): a flying carrier's trace starts at its landing airport — when that IS the
|
||||
// package's destination there is nothing to draw (the solid active-leg layer covers it).
|
||||
it('returns an empty collection when the start point equals the dest airport', () => {
|
||||
expect(hoverTraceGeoJson(AIRPORTS.get('EHAM')!, 'EHAM', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('greatCircleArc (v0.12.2 — curved route traces)', () => {
|
||||
|
||||
+18
-8
@@ -116,7 +116,10 @@ export function hoverRouteGeoJson(
|
||||
// ISS-040 (v0.23.2): the on-board package hover trace — same shape as hoverRouteGeoJson but the
|
||||
// start is a live COORDINATE (the carrier plane's current position), not an airport ICAO: once the
|
||||
// plane has departed with cargo still loaded, the preview must begin where it actually is. Empty
|
||||
// collection when the destination is missing from the dataset — never guessed.
|
||||
// collection when the destination is missing from the dataset — never guessed. ISS-077 (v0.41.0):
|
||||
// also empty when the start point IS the destination airport itself (a flying carrier's trace now
|
||||
// starts at its landing airport, which can equal the package's dest) — nothing to draw there; the
|
||||
// solid active-leg layer covers that case.
|
||||
export function hoverTraceGeoJson(
|
||||
from: AirportCoord,
|
||||
toIcao: string,
|
||||
@@ -124,6 +127,7 @@ export function hoverTraceGeoJson(
|
||||
): FeatureCollection<MapLineFeature> {
|
||||
const to = airportsByIcao.get(toIcao);
|
||||
if (!to) return EMPTY_LINES;
|
||||
if (from.lat === to.lat && from.lon === to.lon) return EMPTY_LINES; // start == dest — zero-length trace
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
@@ -166,19 +170,25 @@ export function routesGeoJson(
|
||||
return { type: 'FeatureCollection', features };
|
||||
}
|
||||
|
||||
// ISS-043 (v0.26.0): the dotted queue line — one LineString per FLYING plane with a non-empty
|
||||
// queue, chaining dest → queue[0] → … → queue[n−1]: ONLY the not-yet-started legs (the active
|
||||
// leg locationIcao→dest stays in routesGeoJson's solid layer). Arcs are concatenated with the
|
||||
// shared joint point dropped once. A plane is skipped — never guessed — when its dest or any
|
||||
// queued ICAO is missing from the airport index; empty collection when nothing qualifies.
|
||||
// ISS-043 (v0.26.0): the dotted queue line — one LineString per plane with a non-empty queue,
|
||||
// chaining the not-yet-started legs: a FLYING plane starts at its active leg's destination
|
||||
// (dest → queue[0] → …; the active leg locationIcao→dest stays in routesGeoJson's solid layer),
|
||||
// and ISS-077 (v0.41.0) extends it to GROUNDED ('available') planes, which start at their current
|
||||
// airport (locationIcao → queue[0] → …). 'repairing' planes draw nothing — they can't fly. Arcs are
|
||||
// concatenated with the shared joint point dropped once. A plane is skipped — never guessed — when
|
||||
// its first point or any queued ICAO is missing from the airport index; empty collection when
|
||||
// nothing qualifies.
|
||||
export function queuedRoutesGeoJson(
|
||||
planes: PlaneState[],
|
||||
airportsByIcao: ReadonlyMap<string, AirportCoord>,
|
||||
): FeatureCollection<MapLineFeature> {
|
||||
const features: MapLineFeature[] = [];
|
||||
for (const p of planes) {
|
||||
if (p.status !== 'flying' || !p.destIcao || p.queue.length === 0) continue;
|
||||
const pts = [p.destIcao, ...p.queue].map((icao) => airportsByIcao.get(icao));
|
||||
if (p.queue.length === 0) continue;
|
||||
// First point of the chain: a flying plane's landing airport, a grounded one's current airport.
|
||||
const firstIcao = p.status === 'flying' ? p.destIcao : p.status === 'available' ? p.locationIcao : null;
|
||||
if (!firstIcao) continue;
|
||||
const pts = [firstIcao, ...p.queue].map((icao) => airportsByIcao.get(icao));
|
||||
if (pts.some((c) => c === undefined)) continue; // an endpoint is missing from the dataset — skip
|
||||
const coords: [number, number][] = [];
|
||||
for (let i = 0; i < pts.length - 1; i++) {
|
||||
|
||||
Reference in New Issue
Block a user