125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Contract } from "@deadhead/schema";
|
|
import { screen, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { useClock } from "../src/clock/clock";
|
|
import { layoutScene } from "../src/map/layout";
|
|
import { reachersByOrigin } from "../src/map/reach";
|
|
import { buildScene } from "../src/map/scene";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
|
|
|
const NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
const DAY = 24 * 3_600_000;
|
|
const view = { width: 1280, height: 720 };
|
|
const camera = { zoom: 1.5, panX: 40, panY: -25 };
|
|
/** Date words the fixed map has no use for (CR-015 §2). */
|
|
const DATED = /\bnow\b|right now|other dates|on some dates|closest|farthest|opposition/i;
|
|
|
|
describe("the fixed map (Step 15.8, decision 0.5.93)", () => {
|
|
it("keeps every body in the same place on screen as the clock advances", () => {
|
|
const state = samplePlayerState();
|
|
const at = (t: number) =>
|
|
layoutScene(buildScene(state, t), view, camera).locations.map((l) => ({
|
|
id: l.id,
|
|
center: l.center,
|
|
}));
|
|
const first = at(NOW);
|
|
// Real minutes, game years: before CR-015 the planets moved over these.
|
|
for (const t of [NOW + 60_000, NOW + 30 * DAY, NOW + 400 * DAY]) {
|
|
expect(at(t)).toEqual(first);
|
|
}
|
|
});
|
|
|
|
it("draws an orbit ring for every body but those riding with a parent (Moon, Io, Europa)", () => {
|
|
const scene = buildScene(samplePlayerState(), NOW);
|
|
const riders = ["moon", "io-sulfur-works", "europa-ice-station"];
|
|
for (const loc of scene.locations) {
|
|
if (riders.includes(loc.id)) expect(loc.orbitRadius, loc.id).toBeNull();
|
|
else expect(loc.orbitRadius, loc.id).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("reach wording without dates (Step 15.8)", () => {
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it("says only where each ship can fly from, in the Market", async () => {
|
|
renderApp(
|
|
fakeApi({
|
|
getPlayerState: () => samplePlayerState({ bays: [{ id: "b", locationId: "earth" }] }),
|
|
}),
|
|
"/market",
|
|
);
|
|
const clipper = (await screen.findByRole("button", { name: "Buy Clipper" })).closest("li")!;
|
|
const reach = within(clipper).getByLabelText("Reach").textContent ?? "";
|
|
expect(reach).toBe(
|
|
"Reach From Earth: Moon, Mars, Venus Cloud City · By hopping: Ceres Station",
|
|
);
|
|
expect(reach).not.toMatch(DATED);
|
|
});
|
|
|
|
it("names who can reach an undiscovered place, per base, on the map", () => {
|
|
const lines = reachersByOrigin(
|
|
samplePlayerState({ bays: [{ id: "b", locationId: "earth" }] }),
|
|
"mars",
|
|
);
|
|
expect(lines).toEqual([
|
|
{ originId: "earth", reachers: ["Clipper", "Freighter", "Bulk Hauler"] },
|
|
]);
|
|
});
|
|
|
|
it("gives an out-of-range pair its reach without a date", async () => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
const marsJob: Contract = {
|
|
id: "c-mars",
|
|
originId: "earth",
|
|
destinationId: "mars",
|
|
cargoTypeId: "standard",
|
|
goodId: "consumer-goods",
|
|
shipper: "Pacific Orbital Logistics",
|
|
priority: false,
|
|
deliveryWindowGameHours: null,
|
|
tier: 0,
|
|
massTonnes: 5,
|
|
payout: 50_000,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: NOW,
|
|
expiresAt: NOW + 3_600_000,
|
|
firstContact: false,
|
|
};
|
|
const state = samplePlayerState({
|
|
tutorial: null,
|
|
ships: [
|
|
{
|
|
id: "s1",
|
|
name: "Dusty Mule",
|
|
modelId: "courier",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
pilotId: "avatar",
|
|
...shipExtras("courier"),
|
|
},
|
|
],
|
|
pilots: [{ ...samplePlayerState().pilots[0]!, assignedShipId: "s1" }],
|
|
locations: content.locations.map((l) => ({ id: l.id, discovered: true })),
|
|
});
|
|
renderApp(
|
|
fakeApi({
|
|
getPlayerState: () => state,
|
|
getContractBoard: () => ({ contracts: [marsJob], periodEndsAt: NOW + 3_600_000 }),
|
|
}),
|
|
"/contracts",
|
|
);
|
|
await userEvent.click(await screen.findByRole("button", { name: "Dispatch" }));
|
|
const problems = within(screen.getByRole("dialog")).getByLabelText("Problems");
|
|
expect(problems).toHaveTextContent(/Out of range \(Courier reaches \d+M km from Earth\)/);
|
|
expect(problems.textContent).not.toMatch(DATED);
|
|
});
|
|
});
|