deadhead-space-freight-tycoon/apps/web/test/ready-ships.test.tsx

97 lines
3.7 KiB
TypeScript

import type { Pilot, 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 { tabAttention } from "../src/layout/attention";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const ship = (id: string, pilotId: string): Ship => ({
id,
modelId: "courier",
name: id,
status: "idle",
locationId: "earth",
dispatchId: null,
lastArrivalAt: NOW - 60_000,
...shipExtras("courier"),
pilotId,
});
const base = samplePlayerState();
const pilot = (id: string, shipId: string, overrides: Partial<Pilot> = {}): Pilot => ({
...base.pilots[0]!,
id,
name: id,
isAvatar: id === "avatar",
assignedShipId: shipId,
locationId: null,
...overrides,
});
const resting = { resting: true, restEndsAt: NOW + 2 * 60 * 60_000 };
/** Two ships at Earth: the avatar's and Rosa's, with the pilots as given. */
const owner = (avatar: Partial<Pilot>, rosa: Partial<Pilot>): PlayerState =>
samplePlayerState({
tutorial: null,
bays: [{ id: "b1", locationId: "earth" }],
ships: [ship("s1", "avatar"), ship("s2", "rosa")],
pilots: [pilot("avatar", "s1", avatar), pilot("Rosa", "s2", { id: "rosa", ...rosa })],
});
const show = (state: PlayerState) =>
renderApp(
fakeApi({
getPlayerState: () => state,
getContractBoard: () => ({ contracts: [], periodEndsAt: NOW + 30 * 60_000 }),
}),
"/contracts?location=earth",
);
describe("ready ships on the Contracts screen (decision 0.5.134)", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("doesn't count a ship whose pilot is resting", async () => {
show(owner({}, resting));
const group = await screen.findByRole("group", { name: "Location" });
const earth = within(group).getByRole("button", { name: /Earth/ });
expect(within(earth).getByLabelText("1 ship ready")).toHaveTextContent("1");
});
it("doesn't count an Independent pilot's ship", async () => {
show(owner({}, { mode: "independent" }));
const group = await screen.findByRole("group", { name: "Location" });
expect(within(group).getByLabelText("1 ship ready")).toBeInTheDocument();
});
it("says who is resting and when they're ready when no pilot here can fly", async () => {
show(owner(resting, { ...resting, restEndsAt: NOW + 30 * 60_000 }));
const group = await screen.findByRole("group", { name: "Location" });
expect(within(group).queryByLabelText(/ready/)).not.toBeInTheDocument();
const status = await screen.findByText(/No pilot here is ready to fly/);
// The first to wake.
expect(status).toHaveTextContent("Rosa is resting, ready in 30:00");
expect(screen.queryByText(/No idle ships here/)).not.toBeInTheDocument();
});
it("counts a pilot once their rest is over, without waiting for a new state", async () => {
show(owner({ resting: true, restEndsAt: NOW - 1 }, resting));
const group = await screen.findByRole("group", { name: "Location" });
expect(within(group).getByLabelText("1 ship ready")).toBeInTheDocument();
});
it("leaves resting and Independent pilots' ships out of the Jobs badge", () => {
expect(tabAttention(owner({}, {}))["/contracts"]).toBe(2);
expect(tabAttention(owner({}, resting))["/contracts"]).toBe(1);
expect(tabAttention(owner({}, { mode: "independent" }))["/contracts"]).toBe(1);
expect(tabAttention(owner(resting, resting))["/contracts"]).toBe(0);
});
});