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 => ({ 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, ...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, }), ], }); 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" })); }); });