deadhead-space-freight-tycoon/apps/web/test/pilots-duty.test.tsx

97 lines
3.3 KiB
TypeScript

import { pilotExtras } from "./fixtures";
import type { Pilot, PlayerState } from "@deadhead/schema";
import { screen, waitFor, 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 { fakeApi, renderApp, samplePlayerState } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const pilot = (overrides: Partial<Pilot>): Pilot => ({
id: "p",
name: "Pilot",
isAvatar: false,
isVeteran: false,
xp: 0,
level: 0,
skills: {},
skillPointsAvailable: 0,
assignedShipId: null,
status: "idle",
locationId: "earth",
dutyHours: 0,
dutyLimit: 12,
resting: false,
restEndsAt: null,
...pilotExtras,
...overrides,
});
const state = (): PlayerState =>
samplePlayerState({
tutorial: null,
bays: [{ id: "b1", locationId: "earth" }],
pilots: [
pilot({ id: "p-rae", name: "Rae Okafor", dutyHours: 9, dutyLimit: 14 }),
pilot({
id: "p-jun",
name: "Jun Sato",
resting: true,
restEndsAt: NOW + 25 * 60_000,
...pilotExtras,
}),
],
});
const card = async (name: string) => (await screen.findByText(name)).closest("li")!;
describe("pilot duty on the Pilots screen (Step 10D.6)", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows a duty bar matching the pilot's hours and limit", async () => {
renderApp(fakeApi({ getPlayerState: state }), "/pilots");
const rae = await card("Rae Okafor");
const bar = within(rae).getByRole("progressbar", { name: "Rae Okafor duty" });
expect(bar).toHaveAttribute("aria-valuenow", "9");
expect(bar).toHaveAttribute("aria-valuemax", "14");
expect(rae).toHaveTextContent("9.0 h of 14 h");
});
it("shows a resting pilot's countdown and no Rest now action", async () => {
renderApp(fakeApi({ getPlayerState: state }), "/pilots");
const jun = await card("Jun Sato");
expect(jun).toHaveTextContent("Resting · wakes in 25m 00s");
expect(within(jun).queryByRole("button", { name: "Rest now" })).not.toBeInTheDocument();
expect(within(jun).queryByRole("progressbar")).not.toBeInTheDocument();
});
it("rests a pilot after confirming that rest lasts at least one board refresh", async () => {
const api = fakeApi({
getPlayerState: state,
restPilot: ({ pilotId }) => ({
pilot: pilot({
id: pilotId,
name: "Rae Okafor",
resting: true,
restEndsAt: NOW + 3_600_000,
}),
}),
});
renderApp(api, "/pilots");
const rae = await card("Rae Okafor");
await userEvent.click(within(rae).getByRole("button", { name: "Rest now" }));
const confirm = within(rae).getByRole("group", { name: "Rest Rae Okafor?" });
expect(confirm).toHaveTextContent("at least one board refresh");
expect(api.call).not.toHaveBeenCalledWith("restPilot", expect.anything());
await userEvent.click(within(confirm).getByRole("button", { name: "Rest Rae Okafor" }));
await waitFor(() => expect(api.call).toHaveBeenCalledWith("restPilot", { pilotId: "p-rae" }));
});
});