deadhead-space-freight-tycoon/apps/web/test/dispatch-problems.test.tsx

140 lines
4.9 KiB
TypeScript

import type { Contract, PlayerState } from "@deadhead/schema";
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 { tutorialHint } from "../src/screens/HQ";
import { fakeApi, renderApp, samplePlayerState } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const job = (
id: string,
massTonnes: number,
skill: { skillId: string; level: number } | null,
): Contract => ({
id,
originId: "earth",
destinationId: "moon",
cargoTypeId: skill ? "high-value" : "standard",
goodId: skill ? "medical-supplies" : "consumer-goods",
shipper: "Pacific Orbital Logistics",
priority: false,
deliveryWindowGameHours: null,
tier: skill?.level ?? 0,
massTonnes,
payout: 1000,
requiredSkill: skill,
requiredLongHaulLevel: 0,
offeredAt: NOW,
expiresAt: NOW + 3_600_000,
firstContact: false,
});
/** Just after the deal: one crewed Courier at Earth, as in the user's report. */
function afterDeal(): PlayerState {
const base = samplePlayerState({ tutorial: null });
return {
...base,
company: { ...base.company, credits: 1652, loanBalance: 5000 },
bays: [{ id: "b1", locationId: "earth" }],
ships: [
{
id: "s1",
modelId: "courier",
name: "Copper Wren",
status: "idle",
locationId: "earth",
dispatchId: null,
lastArrivalAt: 0,
pilotId: "avatar",
},
],
pilots: [{ ...base.pilots[0]!, assignedShipId: "s1" }],
};
}
describe("dispatch sheet explains each job", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows each job's own details and every problem, so different jobs look different", async () => {
const board = [
job("skill", 228, { skillId: "expensive-cargo", level: 2 }),
job("heavy", 69, null),
];
renderApp(
fakeApi({
getPlayerState: afterDeal,
getContractBoard: () => ({ contracts: board, periodEndsAt: NOW + 3_600_000 }),
}),
"/contracts",
);
const [skillJob, heavyJob] = await screen.findAllByRole("button", { name: "Dispatch" });
await userEvent.click(skillJob!);
let sheet = screen.getByRole("dialog");
expect(within(sheet).getByLabelText("Job details")).toHaveTextContent(
"High-Value (tier 2) · 228 t",
);
expect(within(sheet).getByLabelText("Job details")).toHaveTextContent(
"needs Expensive Cargo 2",
);
const problems = within(sheet).getByRole("list", { name: "Problems" });
expect(problems).toHaveTextContent("Too heavy: 228 t (holds 20 t)");
expect(problems).toHaveTextContent("Needs Expensive Cargo 2 (pilot has 0)");
await userEvent.click(within(sheet).getByRole("button", { name: "Cancel" }));
await userEvent.click(heavyJob!);
sheet = screen.getByRole("dialog");
expect(within(sheet).getByLabelText("Job details")).toHaveTextContent("Standard · 69 t");
expect(within(sheet).getByLabelText("Job details")).toHaveTextContent("no skills needed");
const heavyProblems = within(sheet).getByRole("list", { name: "Problems" });
expect(heavyProblems).toHaveTextContent("Too heavy: 69 t (holds 20 t)");
expect(heavyProblems).not.toHaveTextContent("Expensive Cargo");
});
it("lets a job the Courier can carry be confirmed", async () => {
renderApp(
fakeApi({
getPlayerState: afterDeal,
getContractBoard: () => ({
contracts: [job("ok", 15, null)],
periodEndsAt: NOW + 3_600_000,
}),
}),
"/contracts",
);
await userEvent.click(await screen.findByRole("button", { name: "Dispatch" }));
const sheet = screen.getByRole("dialog");
expect(within(sheet).queryByRole("list", { name: "Problems" })).not.toBeInTheDocument();
expect(within(sheet).getByRole("button", { name: "Confirm" })).toBeEnabled();
});
});
describe("tutorial hint on HQ", () => {
const tutorial = (runsCompleted: number, loaner: "earth" | "moon", extra = {}) => ({
loanerLocationId: loaner,
runInFlight: false,
runsCompleted,
dealAvailable: false,
...extra,
});
it("asks for one more run back to Earth when enough runs are done at the Moon", () => {
expect(tutorialHint(tutorial(4, "moon"), 4)).toMatch(
/One more run: bring the ship back to Earth/,
);
expect(tutorialHint(tutorial(4, "moon", { runInFlight: true }), 4)).toMatch(/land at Earth/);
});
it("says keep flying before enough runs, and points at the offer when it is ready", () => {
expect(tutorialHint(tutorial(2, "earth"), 4)).toMatch(/Keep flying/);
expect(tutorialHint(tutorial(4, "earth", { dealAvailable: true }), 4)).toMatch(/offer/);
});
});