deadhead-space-freight-tycoon/apps/web/test/shop-rest.test.tsx

83 lines
3.1 KiB
TypeScript

import type { Pilot, PlayerState, Ship } from "@deadhead/schema";
import { act, screen, within } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useClock } from "../src/clock/clock";
import { independentStatus } from "../src/components/IndependentPilots";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const SHOP_ENDS = NOW + 12 * 60_000 + 34_000;
const ship: Ship = {
id: "s1",
modelId: "courier",
name: "Wren",
status: "idle",
locationId: "earth",
dispatchId: null,
pilotId: "p1",
lastArrivalAt: NOW - 60_000,
...shipExtras("courier"),
inShopUntil: SHOP_ENDS,
};
const base = samplePlayerState();
const ada = (overrides: Partial<Pilot>): Pilot => ({
...base.pilots[0]!,
id: "p1",
name: "Ada",
isAvatar: false,
dutyHours: 11,
...overrides,
});
/** Ada waiting with the ship in the shop, or unassigned and left at the HQ. */
const inShop = ada({ assignedShipId: "s1", locationId: "earth", restingInShopUntil: SHOP_ENDS });
const unassigned = ada({ assignedShipId: null, locationId: "earth", restingInShopUntil: null });
const owner = (pilot: Pilot): PlayerState =>
samplePlayerState({
tutorial: null,
bays: [{ id: "b1", locationId: "earth" }],
ships: [{ ...ship, pilotId: pilot.assignedShipId ? "p1" : null }],
pilots: [base.pilots[0]!, pilot],
});
describe("resting while in the shop (Step 23.3, decision 0.5.135)", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows the shop countdown as rest, and drops it once the pilot is unassigned", async () => {
let state = owner(inShop);
const { queryClient } = renderApp(fakeApi({ getPlayerState: () => state }), "/pilots");
const card = (await screen.findByText("Ada")).closest("li")!;
expect(within(card).getByLabelText("Ada rest")).toHaveTextContent(
"Resting while in the shop — 12:34",
);
// No duty bar while the shop counts as rest.
expect(within(card).queryByRole("progressbar", { name: "Ada duty" })).not.toBeInTheDocument();
state = owner(unassigned);
await act(() => queryClient.invalidateQueries());
const after = (await screen.findByText("Ada")).closest("li")!;
expect(after).not.toHaveTextContent(/Resting/);
expect(within(after).getByRole("progressbar", { name: "Ada duty" })).toBeInTheDocument();
});
it("counts down with the clock", async () => {
renderApp(fakeApi({ getPlayerState: () => owner(inShop) }), "/pilots");
const card = (await screen.findByText("Ada")).closest("li")!;
act(() => useClock.setState({ now: NOW + 4 * 60_000 }));
expect(within(card).getByLabelText("Ada rest")).toHaveTextContent("08:34");
});
it("says so in an Independent pilot's status line", () => {
const state = owner({ ...inShop, mode: "independent" });
expect(independentStatus(state, state.pilots[1]!)).toMatch(/^Resting while in the shop until /);
});
});