deadhead-space-freight-tycoon/apps/web/test/find-work.test.tsx

111 lines
4.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 { pilotExtras } from "./fixtures";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const MINUTE = 60_000;
const ship = (over: Partial<Ship> = {}): Ship => ({
id: "s1",
name: "Wren",
modelId: "courier",
status: "idle",
locationId: "earth",
dispatchId: null,
pilotId: "p1",
lastArrivalAt: 0,
...shipExtras("courier"),
...over,
});
const pilot = (over: Partial<Pilot> = {}): Pilot => ({
...samplePlayerState().pilots[0]!,
id: "p1",
name: "Rosa",
isAvatar: false,
assignedShipId: "s1",
locationId: null,
...pilotExtras,
...over,
});
const state = (s: Ship, pilots: Pilot[]): PlayerState =>
samplePlayerState({
tutorial: null,
company: { id: "c", name: "D", credits: 50_000, loanBalance: 0 },
ships: [s],
pilots: [samplePlayerState().pilots[0]!, ...pilots],
});
async function card(s: Ship, pilots: Pilot[] = [pilot()]) {
renderApp(fakeApi({ getPlayerState: () => state(s, pilots) }), "/fleet");
return (await screen.findByText("Wren")).closest("li")!;
}
const resting = (minutes: number) => pilot({ resting: true, restEndsAt: NOW + minutes * MINUTE });
describe('"Find work here" when the ship can\'t go (decision 0.5.164)', () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("links to the board when the ship can be sent", async () => {
const li = await card(ship());
expect(within(li).getByRole("link", { name: "Find work here" })).toHaveAttribute(
"href",
"/contracts?location=earth",
);
});
it("is disabled with the rest countdown while the pilot rests", async () => {
const li = await card(ship(), [resting(4.2)]);
const button = within(li).getByRole("button", { name: /^Find work here: Resting/ });
expect(button).toBeDisabled();
expect(button).toHaveTextContent("Resting · 04:12");
expect(within(li).queryByRole("link", { name: "Find work here" })).not.toBeInTheDocument();
});
it("is disabled with the shop countdown while the ship is in the shop", async () => {
const li = await card(ship({ inShopUntil: NOW + 12.5 * MINUTE }));
expect(
within(li).getByRole("button", { name: /^Find work here: In the shop/ }),
).toHaveTextContent("In the shop · 12:30");
});
it("counts down to the later of the two when both apply", async () => {
const shopFirst = await card(ship({ inShopUntil: NOW + 5 * MINUTE }), [resting(9)]);
expect(within(shopFirst).getByRole("button", { name: /Find work here/ })).toHaveTextContent(
"Resting · 09:00",
);
});
it("uses the shop's end when it outlasts the rest, as with service overlapping rest", async () => {
const li = await card(ship({ inShopUntil: NOW + 20 * MINUTE }), [resting(10)]);
expect(within(li).getByRole("button", { name: /Find work here/ })).toHaveTextContent(
"In the shop · 20:00",
);
});
it("says No pilot assigned for an uncrewed ship", async () => {
const li = await card(ship({ pilotId: null }), []);
const button = within(li).getByRole("button", { name: /Find work here/ });
expect(button).toBeDisabled();
expect(button).toHaveTextContent("No pilot assigned");
});
it("covers an Independent pilot's ship too", async () => {
const li = await card(ship(), [{ ...resting(3), mode: "independent" }]);
expect(within(li).getByRole("button", { name: /Find work here/ })).toBeDisabled();
});
it("re-enables on its own when the countdown reaches zero", async () => {
const li = await card(ship(), [resting(1)]);
expect(within(li).getByRole("button", { name: /Find work here/ })).toBeDisabled();
act(() => useClock.setState({ now: NOW + MINUTE }));
expect(within(li).getByRole("link", { name: "Find work here" })).toBeInTheDocument();
});
});