134 lines
4.0 KiB
TypeScript
134 lines
4.0 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Contract, Pilot, Ship } from "@deadhead/schema";
|
|
import { realToGame, routeDistance, travelTimeGameHours } 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 { formatGameHours } from "../src/format";
|
|
import { fakeApi, renderApp, samplePlayerState } from "./render";
|
|
|
|
const NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
|
|
const ship = (id: string, name: string, pilotId: string): Ship => ({
|
|
id,
|
|
name,
|
|
modelId: "courier",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
pilotId,
|
|
});
|
|
|
|
const pilot = (id: string, name: string, shipId: string, extra: Partial<Pilot>): Pilot => ({
|
|
id,
|
|
name,
|
|
isAvatar: false,
|
|
xp: 0,
|
|
level: 0,
|
|
skills: {},
|
|
skillPointsAvailable: 0,
|
|
assignedShipId: shipId,
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dutyHours: 0,
|
|
dutyLimit: 12,
|
|
resting: false,
|
|
restEndsAt: null,
|
|
...extra,
|
|
});
|
|
|
|
const moonJob: Contract = {
|
|
id: "c-moon",
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
cargoTypeId: "standard",
|
|
tier: 0,
|
|
massTonnes: 10,
|
|
payout: 900,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: NOW,
|
|
expiresAt: NOW + 3_600_000,
|
|
firstContact: false,
|
|
};
|
|
|
|
const state = samplePlayerState({
|
|
tutorial: null,
|
|
company: { id: "c", name: "D", credits: 100_000, loanBalance: 0 },
|
|
bays: [{ id: "b1", locationId: "earth" }],
|
|
ships: [
|
|
ship("s-rest", "Sleepy Gull", "p-rest"),
|
|
ship("s-tired", "Tired Heron", "p-tired"),
|
|
ship("s-fresh", "Fresh Lark", "p-fresh"),
|
|
],
|
|
pilots: [
|
|
pilot("p-rest", "Ana Resting", "s-rest", {
|
|
resting: true,
|
|
restEndsAt: NOW + 12 * 60_000 + 5_000,
|
|
}),
|
|
pilot("p-tired", "Bo Tired", "s-tired", { dutyHours: 11 }),
|
|
pilot("p-fresh", "Cy Fresh", "s-fresh", { dutyHours: 1 }),
|
|
],
|
|
});
|
|
|
|
/** The Courier's game hours for the Moon job, straight from the sim. */
|
|
const tripHours = () =>
|
|
travelTimeGameHours(
|
|
content.shipModel("courier")!,
|
|
routeDistance(
|
|
content.location("earth")!,
|
|
content.location("moon")!,
|
|
realToGame(NOW, content.config),
|
|
),
|
|
moonJob.massTonnes,
|
|
content.config,
|
|
);
|
|
|
|
async function openSheet() {
|
|
renderApp(
|
|
fakeApi({
|
|
getPlayerState: () => state,
|
|
getContractBoard: () => ({ contracts: [moonJob], periodEndsAt: NOW + 3_600_000 }),
|
|
}),
|
|
"/contracts?location=earth",
|
|
);
|
|
await userEvent.click(await screen.findByRole("button", { name: "Dispatch" }));
|
|
return screen.getByRole("dialog");
|
|
}
|
|
const option = (dialog: HTMLElement, shipName: string) =>
|
|
within(dialog).getByText(shipName).closest("li")!;
|
|
|
|
describe("duty in the dispatch sheet (Step 10D.7)", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
});
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it("disables a resting pilot's pair with the time left", async () => {
|
|
const resting = option(await openSheet(), "Sleepy Gull");
|
|
expect(within(resting).getByRole("radio")).toBeDisabled();
|
|
expect(resting).toHaveTextContent("Resting — 12:05");
|
|
});
|
|
|
|
it("warns when the trip takes the pilot to the limit, with the sim's duty numbers", async () => {
|
|
const dialog = await openSheet();
|
|
const tired = option(dialog, "Tired Heron");
|
|
expect(tired).toHaveTextContent(
|
|
`Duty after${formatGameHours(11 + tripHours())} of ${formatGameHours(12)}`,
|
|
);
|
|
expect(tired).toHaveTextContent("Will need rest on arrival");
|
|
});
|
|
|
|
it("shows no warning when the pilot stays under the limit", async () => {
|
|
const fresh = option(await openSheet(), "Fresh Lark");
|
|
expect(fresh).toHaveTextContent(
|
|
`Duty after${formatGameHours(1 + tripHours())} of ${formatGameHours(12)}`,
|
|
);
|
|
expect(fresh).not.toHaveTextContent("Will need rest");
|
|
});
|
|
});
|