97 lines
3.7 KiB
TypeScript
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);
|
|
});
|
|
});
|