115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
import type { 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 { tabAttention } from "../src/layout/attention";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
|
|
|
const ship = (overrides: Partial<Ship>): Ship => ({
|
|
id: "s",
|
|
modelId: "courier",
|
|
name: "Ship",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
...shipExtras("courier"),
|
|
pilotId: null,
|
|
...overrides,
|
|
});
|
|
|
|
const base = samplePlayerState();
|
|
const avatar = base.pilots[0]!;
|
|
|
|
const owner = (overrides: Partial<PlayerState>) =>
|
|
samplePlayerState({ tutorial: null, bays: [{ id: "b", locationId: "earth" }], ...overrides });
|
|
|
|
describe("navigation attention badges (Step 10E.3)", () => {
|
|
it("counts ready goals, ships ready for a job and unspent skill points", () => {
|
|
const state = owner({
|
|
goals: [
|
|
{ id: "a", status: "ready" },
|
|
{ id: "b", status: "incomplete" },
|
|
{ id: "c", status: "ready" },
|
|
],
|
|
ships: [
|
|
ship({ id: "s1", pilotId: "avatar" }), // ready
|
|
ship({ id: "s2", pilotId: "p2" }), // pilot resting
|
|
ship({ id: "s3", pilotId: null }), // uncrewed
|
|
ship({ id: "s4", pilotId: "p3", status: "in-flight", locationId: null }),
|
|
],
|
|
pilots: [
|
|
{ ...avatar, assignedShipId: "s1", skillPointsAvailable: 2 },
|
|
{
|
|
...avatar,
|
|
id: "p2",
|
|
isAvatar: false,
|
|
isVeteran: false,
|
|
assignedShipId: "s2",
|
|
resting: true,
|
|
restEndsAt: 1,
|
|
},
|
|
{ ...avatar, id: "p3", isAvatar: false, assignedShipId: "s4", skillPointsAvailable: 1 },
|
|
],
|
|
} as Partial<PlayerState>);
|
|
// Skill points: 2 + 0 + 1 across the three pilots (decision 0.5.109).
|
|
expect(tabAttention(state)).toEqual({ "/": 2, "/contracts": 1, "/pilots": 3 });
|
|
});
|
|
|
|
it("nudges toward Jobs during the tutorial until a run is in flight", () => {
|
|
expect(tabAttention(base)["/contracts"]).toBe(1);
|
|
const flying = samplePlayerState({ tutorial: { ...base.tutorial!, runInFlight: true } });
|
|
expect(tabAttention(flying)["/contracts"]).toBe(0);
|
|
});
|
|
|
|
it("shows a badge without changing the tab's accessible name", async () => {
|
|
renderApp(fakeApi({ getPlayerState: () => base }));
|
|
const nav = await screen.findByRole("navigation", { name: "Main" });
|
|
const jobs = within(nav).getByRole("link", { name: "Jobs" });
|
|
expect(within(jobs).getByTestId("badge-Jobs")).toHaveTextContent("1");
|
|
expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Pilots tab badge (Step 17.5, decision 0.5.109)", () => {
|
|
it("shows the total unspent skill points and disappears once all are spent", async () => {
|
|
// You with 2 points, Rosa with 1; each raise spends one.
|
|
const points: Record<string, number> = { avatar: 2, rosa: 1 };
|
|
const state = () =>
|
|
owner({
|
|
pilots: [
|
|
{ ...avatar, skillPointsAvailable: points.avatar! },
|
|
{
|
|
...avatar,
|
|
id: "rosa",
|
|
name: "Rosa Vance",
|
|
isAvatar: false,
|
|
isVeteran: true,
|
|
skillPointsAvailable: points.rosa!,
|
|
},
|
|
],
|
|
});
|
|
const api = fakeApi({
|
|
getPlayerState: state,
|
|
spendSkillPoint: ({ pilotId, skillId }) => {
|
|
points[pilotId]! -= 1;
|
|
const pilot = state().pilots.find((p) => p.id === pilotId)!;
|
|
return { pilot: { ...pilot, skills: { ...pilot.skills, [skillId]: 1 } } };
|
|
},
|
|
});
|
|
renderApp(api, "/pilots");
|
|
const nav = await screen.findByRole("navigation", { name: "Main" });
|
|
expect(await within(nav).findByTestId("badge-Pilots")).toHaveTextContent("3");
|
|
|
|
const raise = async (pilot: string) =>
|
|
userEvent.click(
|
|
(await screen.findAllByRole("button", { name: new RegExp(`for ${pilot}$`) }))[0]!,
|
|
);
|
|
await raise(avatar.name);
|
|
await waitFor(() => expect(within(nav).getByTestId("badge-Pilots")).toHaveTextContent("2"));
|
|
await raise(avatar.name);
|
|
await raise("Rosa Vance");
|
|
await waitFor(() => expect(within(nav).queryByTestId("badge-Pilots")).not.toBeInTheDocument());
|
|
});
|
|
});
|