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

142 lines
4.9 KiB
TypeScript

import { content } from "@deadhead/content";
import type { Pilot, PlayerState, Ship } from "@deadhead/schema";
import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { levelProgress } from "../src/screens/Pilots";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const ship = (overrides: Partial<Ship>): Ship => ({
id: "s",
modelId: "courier",
name: "Ship",
status: "idle",
locationId: "moon",
dispatchId: null,
lastArrivalAt: 0,
...shipExtras("courier"),
pilotId: null,
...overrides,
});
const rae: Pilot = {
id: "p-rae",
name: "Rae Okafor",
isAvatar: false,
isVeteran: false,
xp: 260,
level: 2,
skills: { "heavy-cargo": 1 },
skillPointsAvailable: 3,
assignedShipId: null,
status: "idle",
locationId: "moon",
dutyHours: 0,
dutyLimit: 12,
resting: false,
restEndsAt: null,
};
function pilotsState(overrides: Partial<PlayerState> = {}): PlayerState {
const base = samplePlayerState({ tutorial: null });
return {
...base,
company: { ...base.company, credits: 50_000 },
bays: [
{ id: "b1", locationId: "earth" },
{ id: "b2", locationId: "moon" },
],
pilots: [{ ...base.pilots[0]!, assignedShipId: "s-earth" }, rae],
ships: [
ship({ id: "s-earth", name: "Earth Crewed", locationId: "earth", pilotId: "avatar" }),
ship({ id: "s-moon-free", name: "Moon Free" }),
ship({ id: "s-moon-crewed", name: "Moon Crewed", pilotId: "someone" }),
ship({ id: "s-earth-free", name: "Earth Free", locationId: "earth" }),
ship({ id: "s-flying", name: "Flying", status: "in-flight", locationId: null }),
],
...overrides,
};
}
describe("pilots screen", () => {
it("shows level, XP progress, skills and points", async () => {
renderApp(fakeApi({ getPlayerState: () => pilotsState() }), "/pilots");
const card = (await screen.findByText("Rae Okafor")).closest("li")!;
const { xp, needed } = levelProgress(260, 2)!;
expect(card).toHaveTextContent("Level 2");
expect(card).toHaveTextContent(`${xp} / ${needed} XP to next level`);
expect(card).toHaveTextContent("Heavy Cargo 1/5");
expect(within(card).getByLabelText("Rae Okafor skill points")).toHaveTextContent("3");
});
it("spends a skill point through the endpoint and shows the updated pilot", async () => {
let spent = false;
const api = fakeApi({
getPlayerState: () =>
pilotsState({
pilots: [
pilotsState().pilots[0]!,
spent ? { ...rae, skills: { "heavy-cargo": 2 }, skillPointsAvailable: 2 } : rae,
],
}),
spendSkillPoint: ({ pilotId, skillId }) => {
spent = true;
return {
pilot: { ...rae, id: pilotId, skills: { [skillId]: 2 }, skillPointsAvailable: 2 },
};
},
});
renderApp(api, "/pilots");
await userEvent.click(
await screen.findByRole("button", { name: "Raise Heavy Cargo for Rae Okafor" }),
);
expect(api.call).toHaveBeenCalledWith("spendSkillPoint", {
pilotId: "p-rae",
skillId: "heavy-cargo",
});
const card = screen.getByText("Rae Okafor").closest("li")!;
await waitFor(() => expect(card).toHaveTextContent("Heavy Cargo 2/5"));
expect(within(card).getByLabelText("Rae Okafor skill points")).toHaveTextContent("2");
});
it("disables hiring when the player cannot afford a pilot", async () => {
const cost = content.config.pilotHireCost;
renderApp(
fakeApi({
getPlayerState: () =>
pilotsState({ company: { id: "c", name: "D", credits: cost - 1, loanBalance: 0 } }),
}),
"/pilots",
);
const hire = await screen.findByRole("button", { name: /Hire pilot/ });
expect(hire).toBeDisabled();
expect(hire).toHaveTextContent(`${cost.toLocaleString("en-US")} cr`);
});
it("hires at the chosen bay", async () => {
const api = fakeApi({ getPlayerState: () => pilotsState() });
renderApp(api, "/pilots");
await userEvent.selectOptions(await screen.findByLabelText("Hire at"), "moon");
await userEvent.click(screen.getByRole("button", { name: /Hire pilot/ }));
expect(api.call).toHaveBeenCalledWith("hirePilot", { locationId: "moon" });
});
it("offers only idle, uncrewed ships at the pilot's location for assignment", async () => {
const api = fakeApi({ getPlayerState: () => pilotsState() });
renderApp(api, "/pilots");
const select = await screen.findByLabelText("Assign Rae Okafor to");
const options = within(select)
.getAllByRole("option")
.map((o) => o.textContent);
expect(options).toEqual(["Choose a ship…", "Moon Free (Courier)"]);
await userEvent.selectOptions(select, "s-moon-free");
await userEvent.click(screen.getByRole("button", { name: "Assign" }));
expect(api.call).toHaveBeenCalledWith("assignPilot", {
pilotId: "p-rae",
shipId: "s-moon-free",
});
});
});