import { content } from "@deadhead/content"; import { checkRoute, gameToReal, realToGame } from "@deadhead/sim"; import { screen, within } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { useClock } from "../src/clock/clock"; import { fakeApi, renderApp, samplePlayerState } from "./render"; const MONTH = 30.44 * 24 * 3_600_000; const gameStart = realToGame(Date.parse(content.config.epoch.realStart), content.config); const clipper = content.shipModel("clipper")!; const [earth, mars] = [content.location("earth")!, content.location("mars")!]; /** The first monthly date on which Earth→Mars is (or isn't) in a Clipper's range. */ function dateWhenMars(inRange: boolean): number { for (let i = 0; i <= 16 * 12; i++) { const t = gameStart + i * MONTH; if (checkRoute(clipper, earth, mars, t).ok === inRange) return gameToReal(t, content.config); } throw new Error("no such date"); } async function reachOf(modelName: string, now: number) { vi.useFakeTimers({ toFake: ["Date"] }); vi.setSystemTime(now); useClock.setState({ offsetMs: 0, synced: true, now }); renderApp( fakeApi({ getPlayerState: () => samplePlayerState({ bays: [{ id: "b-earth", locationId: "earth" }] }), }), "/market", ); const card = (await screen.findByRole("button", { name: `Buy ${modelName}` })).closest("li")!; return within(card).getByLabelText("Reach").textContent ?? ""; } describe("market roles and reach (Step 10L.7)", () => { afterEach(() => vi.useRealTimers()); it("shows each model's role", async () => { renderApp(fakeApi({ getPlayerState: () => samplePlayerState() }), "/market"); for (const model of content.purchasableShipModels) { expect(await screen.findByText(model.role)).toBeInTheDocument(); } }); it("lists Mars from Earth for a Clipper only when Mars is in range, and Ceres by hopping", async () => { const near = await reachOf("Clipper", dateWhenMars(true)); expect(near).toContain("From Earth now: Moon, Mars"); expect(near).toContain("By hopping: Ceres Station"); }); it("puts Mars under other dates for a Clipper when it is out of range", async () => { const far = await reachOf("Clipper", dateWhenMars(false)); expect(far).toContain("From Earth now: Moon"); expect(far).not.toContain("From Earth now: Moon, Mars"); expect(far).toContain("Other dates: Mars"); expect(far).toContain("By hopping: Ceres Station"); }); it("lists only the Moon for a Lighter", async () => { const reach = await reachOf("Lighter", dateWhenMars(true)); expect(reach).toBe("Reach From Earth now: Moon"); }); });