import { describe, expect, test } from "vitest"; import Orbit from "../Orbit"; import { formatEpoch, getSatelliteInfo } from "./entityInfo"; import { parseGpPayload, type GpRecord } from "./gp"; // The ISS: inclined enough to have nodes, and nowhere near sun-synchronous, so the // derived rows here are the apsides and nothing else. `orbitFacts.test.ts` covers // the derivation itself; these tests are about which rows appear and in what order. const ISS = new Orbit( "ISS (ZARYA)", parseGpPayload( "ISS (ZARYA)\n1 25544U 98067A 18342.69352573 .00002284 00000-0 41838-4 0 9992\n2 25544 51.6407 229.0798 0005166 124.8351 329.3296 15.54069892145658", )[0] as GpRecord, ); function labels(rows: [string, string][]): string[] { return rows.map(([label]) => label); } function valueOf(rows: [string, string][], label: string): string | undefined { return rows.find(([rowLabel]) => rowLabel === label)?.[1]; } describe("formatEpoch", () => { test("formats a julian date as UTC timestamp", () => { // JD 2460000.5 == 2023-02-25T00:00:00Z expect(formatEpoch(2460000.5)).toBe("2023-02-25 00:00:00"); }); }); describe("getSatelliteInfo", () => { test("reports the derived rows even with no metadata, class first", () => { // Derived before served, so the class is the first row whatever the record says. expect(labels(getSatelliteInfo(ISS, "LEO", {}))).toEqual(["Orbit", "Apogee / Perigee"]); expect(valueOf(getSatelliteInfo(ISS, "LEO", {}), "Orbit")).toBe("LEO"); }); test("omits every row the record does not carry, rather than showing defaults", () => { // A satellite absent from the satellite table still renders with a 200 km // swath and a 10° cone; showing those here would present a renderer fallback // as a fact about the satellite. expect(labels(getSatelliteInfo(ISS, "LEO", {}))).not.toContain("Swath"); expect(labels(getSatelliteInfo(ISS, "LEO", {}))).not.toContain("Sensor FOV"); }); test("shows a symmetric swath as a single total", () => { const rows = getSatelliteInfo(ISS, "LEO", { swathStarboardKm: 1175, swathPortKm: 1175 }); expect(valueOf(rows, "Swath")).toBe("2350 km"); }); test("spells out the sides when they differ", () => { const rows = getSatelliteInfo(ISS, "LEO", { swathStarboardKm: 1000, swathPortKm: 500 }); expect(valueOf(rows, "Swath")).toBe("1500 km (1000 stbd / 500 port)"); }); test("includes the display-only fields when present", () => { const rows = getSatelliteInfo(ISS, "LEO", { coneFovDeg: 45, operator: "ESA", missionType: "Earth observation" }); expect(valueOf(rows, "Sensor FOV")).toBe("45°"); expect(valueOf(rows, "Operator")).toBe("ESA"); expect(valueOf(rows, "Mission")).toBe("Earth observation"); }); describe("SATCAT fields", () => { test("resolves owner, launch and status codes to labels", () => { const rows = getSatelliteInfo(ISS, "LEO", { owner: "ISS", launchDate: "1998-11-20", launchSite: "TYMSC", opsStatus: "+" }); expect(valueOf(rows, "Owner")).toBe("International Space Station"); expect(valueOf(rows, "Launched")).toBe("1998-11-20 · Baikonur, Kazakhstan"); expect(valueOf(rows, "Status")).toBe("Operational"); }); test("falls back to the raw code for one it does not know", () => { // These tables go stale by design — a new nation reaches orbit and the // record arrives before the table knows about it. A code beats a blank. const rows = getSatelliteInfo(ISS, "LEO", { owner: "ZZZ", launchDate: "2026-01-01", launchSite: "QQQ", opsStatus: "!" }); expect(valueOf(rows, "Owner")).toBe("ZZZ"); expect(valueOf(rows, "Launched")).toBe("2026-01-01 · QQQ"); expect(valueOf(rows, "Status")).toBe("!"); }); test("shows the launch date alone when the site is missing", () => { expect(valueOf(getSatelliteInfo(ISS, "LEO", { launchDate: "1998-11-20" }), "Launched")).toBe("1998-11-20"); }); test("suppresses the ordinary orbit type but names the interesting ones", () => { // "Orbiting" is true of all but a handful of the satellites served, so it // would cost a row on every panel to say nothing. expect(labels(getSatelliteInfo(ISS, "LEO", { orbitType: "ORB" }))).not.toContain("Orbit type"); expect(valueOf(getSatelliteInfo(ISS, "LEO", { orbitType: "DOC" }), "Orbit type")).toBe("Docked"); expect(valueOf(getSatelliteInfo(ISS, "LEO", { orbitType: "IMP" }), "Orbit type")).toBe("Impacted"); }); test("reports a decay date only once there is one", () => { expect(labels(getSatelliteInfo(ISS, "LEO", { opsStatus: "+" }))).not.toContain("Decayed"); expect(valueOf(getSatelliteInfo(ISS, "LEO", { decayDate: "2026-08-03" }), "Decayed")).toBe("2026-08-03"); }); test("adds nothing for a satellite the catalog said nothing about", () => { // The derived rows are all that is left; no SATCAT label appears. expect(labels(getSatelliteInfo(ISS, "MEO", {}))).toEqual(["Orbit", "Apogee / Perigee"]); expect(valueOf(getSatelliteInfo(ISS, "MEO", {}), "Orbit")).toBe("MEO"); }); }); });