107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Contract, Pilot, Ship } from "@deadhead/schema";
|
|
import { boardPeriodIndex, boardPeriodMs } from "@deadhead/sim";
|
|
import { screen, waitFor, 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 { formatClock } from "../src/format";
|
|
import { fakeApi, renderApp, samplePlayerState } from "./render";
|
|
|
|
const period = boardPeriodMs(content.config);
|
|
// A quarter of the way into a board period (derived from config, whatever its length).
|
|
const NOW = Math.ceil(Date.parse("2100-01-01T00:00:00Z") / period) * period + period / 4;
|
|
const periodEndsAt = (boardPeriodIndex(NOW, content.config) + 1) * period;
|
|
|
|
const courier: Ship = {
|
|
id: "s1",
|
|
modelId: "courier",
|
|
name: "Dusty Mule",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
pilotId: "p1",
|
|
};
|
|
const rae: Pilot = {
|
|
id: "p1",
|
|
name: "Rae Okafor",
|
|
isAvatar: false,
|
|
xp: 0,
|
|
level: 0,
|
|
skills: {},
|
|
skillPointsAvailable: 0,
|
|
assignedShipId: "s1",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dutyHours: 3,
|
|
dutyLimit: 12,
|
|
resting: false,
|
|
restEndsAt: null,
|
|
};
|
|
/** Too heavy for a Courier. */
|
|
const heavyJob: Contract = {
|
|
id: "c-heavy",
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
cargoTypeId: "heavy",
|
|
tier: 0,
|
|
massTonnes: 300,
|
|
payout: 9000,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: NOW,
|
|
expiresAt: periodEndsAt,
|
|
firstContact: false,
|
|
};
|
|
|
|
const api = (contracts: Contract[]) =>
|
|
fakeApi({
|
|
getPlayerState: () =>
|
|
samplePlayerState({
|
|
tutorial: null,
|
|
company: { id: "c", name: "D", credits: 10_000, loanBalance: 0 },
|
|
bays: [{ id: "b1", locationId: "earth" }],
|
|
ships: [courier],
|
|
pilots: [rae],
|
|
}),
|
|
getContractBoard: () => ({ contracts, periodEndsAt }),
|
|
restPilot: () => ({ pilot: { ...rae, resting: true, restEndsAt: NOW + period } }),
|
|
});
|
|
|
|
describe("board refresh and the nothing-here case (Step 10D.8)", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
});
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it("counts down to the next board period", async () => {
|
|
renderApp(api([heavyJob]), "/contracts?location=earth");
|
|
expect(await screen.findByLabelText("Board refresh")).toHaveTextContent(
|
|
`New contracts in ${formatClock(periodEndsAt - NOW)}`,
|
|
);
|
|
});
|
|
|
|
it("offers rest or a deadhead when no contract fits the idle ship, and each one acts", async () => {
|
|
const client = api([heavyJob]);
|
|
renderApp(client, "/contracts?location=earth");
|
|
const prompt = await screen.findByRole("group", { name: "Nothing for Dusty Mule" });
|
|
|
|
await userEvent.click(
|
|
within(prompt).getByRole("button", { name: "Rest Rae Okafor — wakes to a new board" }),
|
|
);
|
|
await waitFor(() => expect(client.call).toHaveBeenCalledWith("restPilot", { pilotId: "p1" }));
|
|
|
|
await userEvent.click(within(prompt).getByRole("button", { name: "Deadhead (fly empty)" }));
|
|
expect(screen.getByRole("dialog", { name: "Deadhead Dusty Mule" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not prompt when the ship has a job it can take", async () => {
|
|
renderApp(api([{ ...heavyJob, id: "c-light", massTonnes: 10 }]), "/contracts?location=earth");
|
|
await screen.findByLabelText("Board refresh");
|
|
expect(screen.queryByRole("group", { name: /Nothing for/ })).not.toBeInTheDocument();
|
|
});
|
|
});
|