Spaces:
Configuration error
Configuration error
| import { describe, expect, test } from "vitest"; | |
| import type { Pass } from "../PassPredictor"; | |
| import { passBand, passTimelineLayout } from "./passTimeline"; | |
| const T0 = Date.UTC(2026, 6, 1, 12, 0, 0); | |
| const MIN = 60 * 1000; | |
| const HOUR = 60 * MIN; | |
| function pass(startOffsetMs: number, lengthMs = 10 * MIN, maxElevation = 40): Pass { | |
| return { | |
| name: "ISS", | |
| start: T0 + startOffsetMs, | |
| end: T0 + startOffsetMs + lengthMs, | |
| duration: lengthMs, | |
| azimuthStart: 10, | |
| azimuthApex: 180, | |
| azimuthEnd: 200, | |
| maxElevation, | |
| groundStationName: "Munich", | |
| }; | |
| } | |
| describe("passBand", () => { | |
| test("splits at the elevations worth distinguishing", () => { | |
| // 0.5 and 0.222 of full quality are 45 and 20 degrees. | |
| expect(passBand(45 / 90)).toBe("high"); | |
| expect(passBand(44 / 90)).toBe("mid"); | |
| expect(passBand(20 / 90)).toBe("mid"); | |
| expect(passBand(19 / 90)).toBe("low"); | |
| }); | |
| }); | |
| describe("passTimelineLayout", () => { | |
| test("sizes the horizon to the passes rather than to a fixed window", () => { | |
| // Six passes an hour apart need about six hours; six a day apart need days. | |
| const tight = passTimelineLayout( | |
| [1, 2, 3, 4, 5, 6].map((h) => pass(h * HOUR)), | |
| T0, | |
| ); | |
| const sparse = passTimelineLayout( | |
| [1, 2, 3, 4, 5, 6].map((d) => pass(d * 24 * HOUR)), | |
| T0, | |
| ); | |
| // 7 h, not 6: the last block gets a tenth of headroom so it is not flush | |
| // with the right edge. | |
| expect(tight.horizonLabel).toBe("7 h"); | |
| expect(tight.blocks).toHaveLength(6); | |
| // A satellite with one pass a day cannot fit six inside the 48 h ceiling. The | |
| // strip shows what it can and the caption accounts for the rest. That is the | |
| // clamp working, not the sizing failing. | |
| expect(sparse.horizonLabel).toBe("2 d"); | |
| expect(sparse.blocks).toHaveLength(2); | |
| expect(sparse.beyond).toBe(4); | |
| }); | |
| test("clamps the horizon rather than following one distant pass anywhere", () => { | |
| const far = passTimelineLayout([pass(300 * 24 * HOUR)], T0); | |
| expect(far.horizonLabel).toBe("2 d"); | |
| expect(far.blocks).toHaveLength(0); | |
| expect(far.beyond).toBe(1); | |
| }); | |
| test("an empty list still lays out, at the minimum span", () => { | |
| const empty = passTimelineLayout([], T0); | |
| expect(empty.blocks).toEqual([]); | |
| expect(empty.horizonLabel).toBe("6 h"); | |
| expect(empty.beyond).toBe(0); | |
| }); | |
| test("now sits inside the strip, so its marker is a line on the track", () => { | |
| const layout = passTimelineLayout([pass(1 * HOUR)], T0); | |
| expect(layout.nowPct).toBeGreaterThan(0); | |
| expect(layout.nowPct).toBeLessThan(15); | |
| }); | |
| test("marks the pass happening now, and dims the one that has ended", () => { | |
| const layout = passTimelineLayout([pass(-5 * MIN), pass(-25 * MIN, 5 * MIN), pass(1 * HOUR)], T0); | |
| expect(layout.blocks.map((block) => [block.live, block.past])).toEqual([ | |
| [true, false], | |
| [false, true], | |
| [false, false], | |
| ]); | |
| }); | |
| test("gives a short pass a hittable width and a grazing one a visible height", () => { | |
| const layout = passTimelineLayout([pass(1 * HOUR, 30 * 1000, 1)], T0); | |
| expect(layout.blocks[0]!.widthPct).toBeGreaterThanOrEqual(1.2); | |
| expect(layout.blocks[0]!.heightPct).toBeGreaterThan(20); | |
| }); | |
| test("keeps every tick label clear of the right edge", () => { | |
| const layout = passTimelineLayout( | |
| [1, 2, 3, 4, 5, 6].map((h) => pass(h * HOUR)), | |
| T0, | |
| ); | |
| expect(layout.ticks.length).toBeGreaterThan(0); | |
| expect(Math.max(...layout.ticks.map((tick) => tick.pct))).toBeLessThanOrEqual(90); | |
| }); | |
| test("keys blocks by start time, so nothing has to stay index-aligned", () => { | |
| const layout = passTimelineLayout([pass(1 * HOUR), pass(2 * HOUR)], T0); | |
| expect(layout.blocks.map((block) => block.key)).toEqual([String(T0 + HOUR), String(T0 + 2 * HOUR)]); | |
| }); | |
| }); | |