Merge branch 'main' into docs/qa-gate-npm-alias-list

This commit is contained in:
Sameh Khamis committed 2026-10-17 11:30:29 -07:00
commit 7a63afe659
6 files changed
+310 -9

No files matched your search

@@ -0,0 +1,4 @@
- Keep missing launch-pad coordinates unknown so launches use the location
fallback or stay off the map instead of appearing at 0°N 0°E. Missing stage
flight numbers no longer show "FLIGHT 0"
(Rudy Celekli, @rudycelekli, #1186).
+5
View File
@@ -791,6 +791,11 @@ and `./layers/launches`. Each owns its catalog, render state, tracking, interact
and teardown. Source adapters retain the existing CelesTrak and Launch Library
endpoints; standalone entries provide scene services and the satellite dependency.
The mission factory separates paths, replay, camera, panel, overlays and placement.
Launch Library pad coordinates retain unknown values: missing fields use the
reported location coordinates when available, and launches without a known pad
position stay off the map. Genuine zero coordinates remain valid. Undisclosed
stage coordinates, downrange distance and flight numbers remain unknown; the
mission panel omits the flight-number label when no number was reported.
Pending mission requests abort on disable/destruction and malformed launch
snapshots preserve the last accepted display. Catalog groups, propagation cadence,
tracking intent, replay timing, controls and source attribution remain unchanged.
+1
View File
@@ -732,6 +732,7 @@
"src/voice/session.test.mjs",
"src/layers/alpr/records.js",
"src/layers/earthquakes/records.js",
"src/layers/earthquakes/records.test.mjs",
"src/sources/sourceSlot.js",
"src/data/feedState.js",
"src/app/localGeojsonServices.js",
+31
View File
@@ -677,6 +677,37 @@ test('uses Launch Library 2 pad latitude/longitude fields', () => {
assert.equal(launches[0].lon, -120.611);
});
test('null or blank pad coordinates stay unknown instead of becoming 0', () => {
const base = { net: '2026-07-20T10:00:00Z' };
for (const blank of [null, '', ' ', false]) {
assert.deepEqual(
normalizeRocketLaunches([{ ...base, id: 'blank', pad: { latitude: blank, longitude: blank } }], NOW),
[],
);
}
});
test('null pad coordinates use the location fallback instead of 0,0', () => {
const base = { net: '2026-07-20T10:00:00Z' };
const [fallback] = normalizeRocketLaunches([{ ...base, id: 'fallback',
pad: { latitude: null, longitude: null, location: { coordinates: '-80.6,28.6' } } }], NOW);
assert.equal(fallback.lat, 28.6);
assert.equal(fallback.lon, -80.6);
});
test('an undisclosed landing keeps top-level position and stage numbers unknown', () => {
const base = { net: '2026-07-20T10:00:00Z' };
const [launch] = normalizeRocketLaunches([{ ...base, id: 'landing', pad: { latitude: 1, longitude: 2 },
rocket: { launcher_stage: [{ launcher_flight_number: null,
landing: { attempt: true, latitude: null, longitude: null, downrange_distance: null,
landing_location: { latitude: null, longitude: null } } }] } }], NOW);
const [stage] = launch.recoveryStages;
assert.equal(stage.lat, null);
assert.equal(stage.lon, null);
assert.equal(stage.downrangeKm, null);
assert.equal(stage.flightNumber, null);
});
test('normalizes detailed payload and stage recovery records', () => {
const launches = normalizeRocketLaunches({ results: [{
id: 'recovery-details',
+261
View File
@@ -0,0 +1,261 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { normalizeEarthquakeSnapshot } from './records.js';
test('normalizeEarthquakeSnapshot parses valid GeoJSON earthquake features', () => {
const geojson = {
type: 'FeatureCollection',
features: [
{
id: 'us7000m123',
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-122.4194, 37.7749, 10.5],
},
properties: {
mag: 4.2,
place: 'San Francisco Bay Area, CA',
time: 1711929600000,
},
},
],
};
const rows = normalizeEarthquakeSnapshot(geojson);
assert.equal(Array.isArray(rows), true);
assert.equal(rows.length, 1);
assert.deepEqual(rows[0], {
stableId: 'us7000m123',
usgsId: 'us7000m123',
lon: -122.4194,
lat: 37.7749,
depthKm: 10.5,
mag: 4.2,
place: 'San Francisco Bay Area, CA',
time: 1711929600000,
});
});
test('normalizeEarthquakeSnapshot generates index-based stable IDs when id is missing or blank', () => {
const geojson = {
features: [
{
id: null,
geometry: { type: 'Point', coordinates: [139.6917, 35.6895, 25.0] },
properties: { mag: 3.5, place: 'Tokyo, Japan', time: 1711930000000 },
},
{
id: '',
geometry: { type: 'Point', coordinates: [-70.6693, -33.4489, 45.2] },
properties: { mag: 5.1, place: 'Santiago, Chile', time: 1711931000000 },
},
],
};
const rows = normalizeEarthquakeSnapshot(geojson);
assert.equal(rows.length, 2);
assert.equal(rows[0].stableId, 'event-1');
assert.equal(rows[0].usgsId, null);
assert.equal(rows[1].stableId, 'event-2');
assert.equal(rows[1].usgsId, '');
});
test('normalizeEarthquakeSnapshot filters events below M2.5 without dropping the feed', () => {
const geojson = {
features: [
{
id: 'ev-sub',
geometry: { type: 'Point', coordinates: [10, 20, 5] },
properties: { mag: 2.49, place: 'Minor tremor' },
},
{
id: 'ev-null-mag',
geometry: { type: 'Point', coordinates: [11, 21, 5] },
properties: { mag: null, place: 'Uncalculated' },
},
{
id: 'ev-threshold',
geometry: { type: 'Point', coordinates: [12, 22, 5] },
properties: { mag: 2.5, place: 'Threshold event' },
},
{
id: 'ev-strong',
geometry: { type: 'Point', coordinates: [13, 23, 5] },
properties: { mag: 6.8, place: 'Major event' },
},
],
};
const rows = normalizeEarthquakeSnapshot(geojson);
assert.equal(rows.length, 2);
assert.equal(rows[0].stableId, 'ev-threshold');
assert.equal(rows[0].mag, 2.5);
assert.equal(rows[1].stableId, 'ev-strong');
assert.equal(rows[1].mag, 6.8);
});
test('normalizeEarthquakeSnapshot normalizes optional fields to null when omitted or invalid', () => {
const geojson = {
features: [
{
id: 'ev-sparse',
geometry: { coordinates: [-100, 40] }, // depth omitted
properties: { mag: 3.0, place: 12345, time: 'invalid' }, // place not string, time not number
},
],
};
const rows = normalizeEarthquakeSnapshot(geojson);
assert.equal(rows.length, 1);
assert.equal(rows[0].depthKm, null);
assert.equal(rows[0].place, null);
assert.equal(rows[0].time, null);
});
test('normalizeEarthquakeSnapshot returns null for malformed structures or non-array features', () => {
assert.equal(normalizeEarthquakeSnapshot(null), null);
assert.equal(normalizeEarthquakeSnapshot(undefined), null);
assert.equal(normalizeEarthquakeSnapshot({}), null);
assert.equal(normalizeEarthquakeSnapshot({ features: 'not-an-array' }), null);
assert.equal(normalizeEarthquakeSnapshot({ features: null }), null);
});
test('normalizeEarthquakeSnapshot returns null for invalid geometries or out-of-range coordinates', () => {
const baseFeature = {
id: 'test',
geometry: { type: 'Point', coordinates: [0, 0, 0] },
properties: { mag: 3.0 },
};
// Non-Point geometry
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: {
type: 'LineString',
coordinates: [
[0, 0],
[1, 1],
],
},
},
],
}),
null,
);
// Missing or short coordinates
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{ ...baseFeature, geometry: { type: 'Point', coordinates: [0] } },
],
}),
null,
);
// Longitude out of range [-180, 180]
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: { type: 'Point', coordinates: [180.1, 0, 0] },
},
],
}),
null,
);
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: { type: 'Point', coordinates: [-180.1, 0, 0] },
},
],
}),
null,
);
// Latitude out of range [-90, 90]
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: { type: 'Point', coordinates: [0, 90.1, 0] },
},
],
}),
null,
);
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: { type: 'Point', coordinates: [0, -90.1, 0] },
},
],
}),
null,
);
// Non-finite depth
assert.equal(
normalizeEarthquakeSnapshot({
features: [
{
...baseFeature,
geometry: { type: 'Point', coordinates: [0, 0, NaN] },
},
],
}),
null,
);
// Magnitude > 10
assert.equal(
normalizeEarthquakeSnapshot({
features: [{ ...baseFeature, properties: { mag: 10.1 } }],
}),
null,
);
// Non-object or array properties
assert.equal(
normalizeEarthquakeSnapshot({
features: [{ ...baseFeature, properties: 'invalid' }],
}),
null,
);
assert.equal(
normalizeEarthquakeSnapshot({
features: [{ ...baseFeature, properties: [] }],
}),
null,
);
});
test('normalizeEarthquakeSnapshot returns null when feed contains duplicate IDs', () => {
const geojson = {
features: [
{
id: 'duplicate-id',
geometry: { type: 'Point', coordinates: [0, 0, 0] },
properties: { mag: 3.0 },
},
{
id: 'duplicate-id',
geometry: { type: 'Point', coordinates: [1, 1, 1] },
properties: { mag: 4.0 },
},
],
};
assert.equal(normalizeEarthquakeSnapshot(geojson), null);
});
+8 -9
View File
@@ -10,6 +10,9 @@ export const UNKNOWN_LAUNCH_STATUS = msg('Unknown');
export function createModel({ state: layerState, services, parts, source }) {
function finiteCoordinate(value) {
// Number(null), Number('') and Number(false) are 0: absent is not zero.
if (value == null || typeof value === 'boolean') return null;
if (typeof value === 'string' && value.trim() === '') return null;
const number = Number(value);
return Number.isFinite(number) ? number : null;
}
@@ -184,15 +187,11 @@ export function createModel({ state: layerState, services, parts, source }) {
const pad = launch.pad || {};
const location = pad.location || {};
const coordinates = location.coordinates || '';
const [coordinateLon, coordinateLat] = String(coordinates)
.split(',')
.map(Number);
const lat = Number.isFinite(Number(pad.latitude))
? Number(pad.latitude)
: coordinateLat;
const lon = Number.isFinite(Number(pad.longitude))
? Number(pad.longitude)
: coordinateLon;
const [coordinateLon, coordinateLat] = String(coordinates).split(',');
const lat =
finiteCoordinate(pad.latitude) ?? finiteCoordinate(coordinateLat);
const lon =
finiteCoordinate(pad.longitude) ?? finiteCoordinate(coordinateLon);
const payloads = normalizePayloadFlights(launch);
return {
id: String(