deadhead-space-freight-tycoon/apps/web/test/board-cards.test.tsx

102 lines
3.3 KiB
TypeScript

import type { Contract, PlayerState, Ship } from "@deadhead/schema";
import { screen, within } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useClock } from "../src/clock/clock";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const PERIOD_ENDS = NOW + 4 * 60_000 + 5_000;
const job = (overrides: Partial<Contract>): Contract => ({
id: "j",
originId: "earth",
destinationId: "moon",
cargoTypeId: "standard",
goodId: "consumer-goods",
shipper: "Pacific Orbital Logistics",
priority: false,
deliveryWindowGameHours: null,
tier: 0,
massTonnes: 6,
payout: 1_000,
requiredSkill: null,
requiredLongHaulLevel: 0,
offeredAt: NOW,
expiresAt: PERIOD_ENDS,
firstContact: false,
...overrides,
});
const courier: Ship = {
id: "s1",
modelId: "courier",
name: "Dusty Mule",
status: "idle",
locationId: "earth",
dispatchId: null,
pilotId: "avatar",
lastArrivalAt: 0,
...shipExtras("courier"),
};
function owner(): PlayerState {
const base = samplePlayerState({ tutorial: null });
return {
...base,
bays: [{ id: "b", locationId: "earth" }],
ships: [courier],
pilots: [{ ...base.pilots[0]!, assignedShipId: "s1", locationId: null }],
};
}
function show(contracts: Contract[]) {
renderApp(
fakeApi({
getPlayerState: owner,
getContractBoard: () => ({ contracts, periodEndsAt: PERIOD_ENDS }),
}),
"/contracts?location=earth",
);
}
describe("board cards and the empty board (Step 19.4, decision 0.5.114)", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows no permanent-problem badge on any card", async () => {
// Jobs a lone level-0 Courier couldn't take: the engine filters them out,
// and even if one reached a card it carries no problem badge.
show([
job({ id: "heavy", massTonnes: 300, payout: 9_001 }),
job({ id: "rushed", priority: true, deliveryWindowGameHours: 0.01, payout: 9_002 }),
job({
id: "skilled",
cargoTypeId: "heavy",
goodId: "machine-parts",
tier: 3,
requiredSkill: { skillId: "heavy-cargo", level: 3 },
payout: 9_003,
}),
]);
for (const payout of ["9,001 cr", "9,002 cr", "9,003 cr"]) {
const card = (await screen.findByText(payout)).closest("li")!;
expect(card).not.toHaveTextContent(/too heavy/i);
expect(card).not.toHaveTextContent(/too slow/i);
expect(card).not.toHaveTextContent(/skill too low/i);
}
});
it("says no job fits the fleet, with the refresh countdown, Rest and Deadhead", async () => {
show([]);
const empty = await screen.findByText(/No jobs here your fleet can take right now/);
expect(empty).toHaveTextContent("New contracts in 04:05.");
const stuck = screen.getByRole("group", { name: "Nothing for Dusty Mule" });
expect(within(stuck).getByRole("button", { name: /^Rest / })).toBeInTheDocument();
expect(within(stuck).getByRole("button", { name: "Deadhead (fly empty)" })).toBeInTheDocument();
});
});