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((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(); }); });