deadhead-space-freight-tycoon/apps/web/test/skill-point-prompt.test.tsx

118 lines
4.7 KiB
TypeScript

import { content } from "@deadhead/content";
import type { PlayerState } from "@deadhead/schema";
import { act, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import { usePreferences } from "../src/state/preferences";
import { fakeApi, renderApp, samplePlayerState, sampleTutorialBoard } from "./render";
const POP_UP = "Your pilots are ready to train";
const veteran = content.veteranPilot;
const base = samplePlayerState();
const avatar = base.pilots[0]!;
/** The tutorial with the deal on offer; the avatar already has a point from five runs. */
const offered = (): PlayerState =>
samplePlayerState({
tutorial: {
loanerLocationId: "moon",
runInFlight: false,
runsCompleted: 5,
dealAvailable: true,
},
pilots: [{ ...avatar, skillPointsAvailable: 1 }],
});
/** After the deal: You with `you` points, Rosa with `rosa`. */
const afterDeal = (you = 1, rosa = 2): PlayerState =>
samplePlayerState({
tutorial: null,
bays: [{ id: "b1", locationId: "earth" }],
pilots: [
{ ...avatar, skillPointsAvailable: you },
{
...avatar,
id: "veteran",
name: veteran.name,
isAvatar: false,
isVeteran: true,
skillPointsAvailable: rosa,
},
],
});
/** A permission prompt the test answers. */
function pendingPrompt() {
let answer!: () => void;
const answered = new Promise<void>((resolve) => (answer = resolve));
return { answered, answer };
}
describe("skill point pop-up (Step 17.6, decision 0.5.109)", () => {
it("appears only after both the deal and the permission prompt, listing each pilot's points", async () => {
let accepted = false;
const prompt = pendingPrompt();
const api = Object.assign(
fakeApi({
getPlayerState: () => (accepted ? afterDeal() : offered()),
getTutorialBoard: () => sampleTutorialBoard(),
acceptDeal: () => {
accepted = true;
return afterDeal();
},
}),
{ notificationPrompt: () => (accepted ? prompt.answered : Promise.resolve()) },
);
renderApp(api, "/contracts");
// During the tutorial the avatar's point doesn't bring it up.
await screen.findByRole("dialog", { name: "An offer from Halvorsen" });
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Shake on it" }));
await screen.findByRole("heading", { level: 1, name: "Dashboard" });
// The deal is done, but the permission prompt is still open.
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument();
act(() => prompt.answer());
const dialog = await screen.findByRole("dialog", { name: POP_UP });
expect(dialog).toHaveTextContent("You and Rosa both have skill points to spend.");
expect(screen.getByLabelText("Unspent skill points")).toHaveTextContent(
"You: 1 point · Rosa: 2 points",
);
});
it("Go to Pilots opens the Pilots tab and the pop-up doesn't come back", async () => {
renderApp(fakeApi({ getPlayerState: () => afterDeal() }), "/");
await userEvent.click(await screen.findByRole("button", { name: "Go to Pilots" }));
await screen.findByRole("heading", { level: 1, name: "Pilots" });
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument();
});
it("Later dismisses it until tips are shown again", async () => {
renderApp(fakeApi({ getPlayerState: () => afterDeal(0, 2) }), "/");
const dialog = await screen.findByRole("dialog", { name: POP_UP });
// Only the pilots with points are listed.
expect(dialog).toHaveTextContent("Rosa has skill points to spend.");
expect(screen.getByLabelText("Unspent skill points")).toHaveTextContent(/^Rosa: 2 points$/);
await userEvent.click(screen.getByRole("button", { name: "Later" }));
await waitFor(() =>
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument(),
);
await screen.findByRole("heading", { level: 1, name: "Dashboard" });
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument();
act(() => usePreferences.getState().showTipsAgain());
expect(await screen.findByRole("dialog", { name: POP_UP })).toBeInTheDocument();
});
it("doesn't appear with no unspent points", async () => {
renderApp(fakeApi({ getPlayerState: () => afterDeal(0, 0) }), "/");
await screen.findByRole("heading", { level: 1, name: "Dashboard" });
// Give the prompt check a turn to settle.
await act(async () => {});
expect(screen.queryByRole("dialog", { name: POP_UP })).not.toBeInTheDocument();
});
});