deadhead-space-freight-tycoon/apps/web/test/stranding-reason.test.tsx

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();
});
});