deadhead-space-freight-tycoon/apps/web/test/low-fuel.test.tsx

90 lines
3.8 KiB
TypeScript

import { content } from "@deadhead/content";
import type { PlayerState, Ship } from "@deadhead/schema";
import { tripFuel } from "@deadhead/sim";
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const [earth, mars] = [content.location("earth")!, content.location("mars")!];
const clipper = content.shipModel("clipper")!;
const marsLegFu = tripFuel(clipper, earth, mars, content.config);
const ship = (fuelFu: number): Ship => ({
id: "s1",
name: "Swift Heron",
modelId: "clipper",
status: "idle",
locationId: "earth",
dispatchId: null,
lastArrivalAt: 0,
...shipExtras("clipper"),
fuelFu,
fuelAvgPrice: 8,
refuelPolicy: "manual",
pilotId: null,
});
/** A Clipper at Earth with Mars discovered, `fuelFu` aboard; refuels fill the tank. */
function company(fuelFu: number, discovered = ["earth", "moon", "mars"]) {
let state: PlayerState = samplePlayerState({
tutorial: null,
company: { id: "c", name: "D", credits: 100_000, loanBalance: 0 },
bays: [{ id: "b1", locationId: "earth" }],
ships: [ship(fuelFu)],
locations: content.locations.map((l) => ({ id: l.id, discovered: discovered.includes(l.id) })),
});
const refuelShip = vi.fn(() => {
const refuelled = { ...state.ships[0]!, fuelFu: clipper.tankCapacityFu };
state = { ...state, ships: [refuelled] };
return { ship: refuelled };
});
return fakeApi({ getPlayerState: () => state, refuelShip });
}
describe("low-fuel warning (Step 18.4, decision 0.5.113)", () => {
it("warns on a Clipper at Earth that can't fly to Mars, and clears once refuelled", async () => {
renderApp(company(marsLegFu - 1), "/fleet");
const warning = await screen.findByLabelText("Swift Heron low fuel");
expect(warning).toHaveTextContent("Low fuel");
// Earth's longest leg for a Clipper among the places known here is Mars.
expect(warning).toHaveTextContent(
`Mars needs ${Math.round(marsLegFu).toLocaleString("en-US")} fu`,
);
await userEvent.click(screen.getByRole("button", { name: "Fuel for Swift Heron" }));
const sheet = screen.getByRole("dialog", { name: "Swift Heron" });
await userEvent.click(within(sheet).getByRole("button", { name: /^Fill/ }));
await waitFor(() =>
expect(screen.queryByLabelText("Swift Heron low fuel")).not.toBeInTheDocument(),
);
});
it("doesn't warn with enough for the longest leg", async () => {
renderApp(company(marsLegFu + 1), "/fleet");
await screen.findByRole("progressbar", { name: "Swift Heron fuel" });
expect(screen.queryByLabelText("Swift Heron low fuel")).not.toBeInTheDocument();
});
it("measures only legs to discovered places", async () => {
// Mars undiscovered: the Moon is the longest leg, and a little fuel covers it.
renderApp(company(marsLegFu - 1, ["earth", "moon"]), "/fleet");
await screen.findByRole("progressbar", { name: "Swift Heron fuel" });
expect(screen.queryByLabelText("Swift Heron low fuel")).not.toBeInTheDocument();
});
it("shows Manual selected, as the default, in a new ship's fuel sheet", async () => {
renderApp(company(clipper.tankCapacityFu), "/fleet");
await userEvent.click(await screen.findByRole("button", { name: "Fuel for Swift Heron" }));
const policy = within(screen.getByRole("dialog")).getByRole("group", { name: "Refuel policy" });
const radios = within(policy).getAllByRole("radio");
// Manual comes first and is checked.
expect(radios.map((r) => r.parentElement!.textContent)).toEqual([
"Manual refuel",
"Auto top-up",
]);
expect(within(policy).getByRole("radio", { name: "Manual refuel" })).toBeChecked();
expect(policy).toHaveTextContent("The default");
});
});