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 } from "./render"; const ship = (overrides: Partial): Ship => ({ id: "s", modelId: "courier", name: "Ship", status: "idle", locationId: "moon", dispatchId: null, lastArrivalAt: 0, pilotId: null, ...overrides, }); const rae: Pilot = { id: "p-rae", name: "Rae Okafor", isAvatar: false, xp: 260, level: 2, skills: { "heavy-cargo": 1 }, skillPointsAvailable: 3, assignedShipId: null, status: "idle", locationId: "moon", }; function pilotsState(overrides: Partial = {}): 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", }); }); });