141 lines
5.0 KiB
TypeScript
141 lines
5.0 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/Dashboard";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } 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,
|
|
...shipExtras("courier"),
|
|
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/);
|
|
});
|
|
});
|