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