100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Contract, PlayerState } from "@deadhead/schema";
|
|
import { checkRoute, gameToReal, realToGame, wouldStrand } from "@deadhead/sim";
|
|
import { screen, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { useClock } from "../src/clock/clock";
|
|
import { pairOptions } from "../src/screens/DispatchSheet";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
|
|
|
const DAY = 24 * 3_600_000;
|
|
const gameStart = realToGame(Date.parse(content.config.epoch.realStart), content.config);
|
|
const clipper = content.shipModel("clipper")!;
|
|
const [moon, mars] = [content.location("moon")!, content.location("mars")!];
|
|
const discovered = new Set(["earth", "moon", "mars"]);
|
|
|
|
/** A date on which a Clipper can hop Moon→Mars but couldn't leave Mars (CR-012). */
|
|
const NOW = (() => {
|
|
for (let d = 0; d < 16 * 365; d += 5) {
|
|
const t = gameStart + d * DAY;
|
|
if (
|
|
checkRoute(clipper, moon, mars, t).ok &&
|
|
wouldStrand(clipper, moon, mars, 10, t, discovered, content)
|
|
) {
|
|
return gameToReal(t, content.config);
|
|
}
|
|
}
|
|
throw new Error("no stranding date");
|
|
})();
|
|
const STRANDED = "Would be stranded — can't fly out of Mars from there";
|
|
|
|
/** A crewed Clipper at the Moon, with Mars discovered. */
|
|
function state(): PlayerState {
|
|
const base = samplePlayerState({ tutorial: null });
|
|
return {
|
|
...base,
|
|
company: { ...base.company, credits: 100_000 },
|
|
bays: [{ id: "b1", locationId: "moon" }],
|
|
locations: base.locations.map((l) => ({ ...l, discovered: discovered.has(l.id) })),
|
|
ships: [
|
|
{
|
|
id: "s1",
|
|
modelId: "clipper",
|
|
name: "Swift Heron",
|
|
status: "idle",
|
|
locationId: "moon",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
...shipExtras("clipper"),
|
|
pilotId: "avatar",
|
|
},
|
|
],
|
|
pilots: [{ ...base.pilots[0]!, assignedShipId: "s1", locationId: "moon" }],
|
|
};
|
|
}
|
|
|
|
const toMars: Contract = {
|
|
id: "c1",
|
|
originId: "moon",
|
|
destinationId: "mars",
|
|
cargoTypeId: "standard",
|
|
goodId: "consumer-goods",
|
|
shipper: "Pacific Orbital Logistics",
|
|
priority: false,
|
|
deliveryWindowGameHours: null,
|
|
tier: 0,
|
|
massTonnes: 10,
|
|
payout: 50_000,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: NOW,
|
|
expiresAt: NOW + 3_600_000,
|
|
firstContact: false,
|
|
};
|
|
|
|
describe("stranding reason (Step 10L.9)", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
});
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it("marks a would-strand ship and pilot pair with the reason", () => {
|
|
const [pair] = pairOptions(state(), toMars, NOW);
|
|
expect(pair!.problems).toEqual([STRANDED]);
|
|
});
|
|
|
|
it("disables a would-strand deadhead destination with the reason", async () => {
|
|
renderApp(fakeApi({ getPlayerState: state }), "/fleet");
|
|
const card = (await screen.findByText("Swift Heron")).closest("li")!;
|
|
await userEvent.click(within(card).getByRole("button", { name: /Deadhead/ }));
|
|
const dialog = screen.getByRole("dialog", { name: "Deadhead Swift Heron" });
|
|
const option = within(dialog).getByRole("button", { name: "Mars" });
|
|
expect(option).toBeDisabled();
|
|
expect(option).toHaveTextContent(STRANDED);
|
|
expect(within(dialog).getByRole("button", { name: "Earth" })).toBeEnabled();
|
|
});
|
|
});
|