import { focusManager } from "@tanstack/react-query"; import { act, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it } from "vitest"; import { SCREENS } from "../src/routes"; import { fakeApi, renderApp, samplePlayerState } from "./render"; const networkDown = () => { throw new TypeError("Failed to fetch"); }; const afterTutorial = samplePlayerState({ tutorial: null }); describe("load errors", () => { afterEach(() => focusManager.setFocused(undefined)); it.each(SCREENS)( "$title: a failed first load offers Retry, which recovers", async ({ path, title }) => { let online = false; renderApp( fakeApi({ getPlayerState: () => (online ? afterTutorial : networkDown()), getContractBoard: () => ({ contracts: [], periodEndsAt: 0 }), }), path, ); const alert = await screen.findByRole("alert"); expect(alert).toHaveTextContent("Could not load the game."); online = true; await userEvent.click(within(alert).getByRole("button", { name: "Retry" })); expect(await screen.findByRole("heading", { level: 1, name: title })).toBeInTheDocument(); }, ); it.each(SCREENS)( "$title: a failed refresh keeps the screen and shows an error banner", async ({ path, title }) => { let online = true; renderApp( fakeApi({ getPlayerState: () => (online ? afterTutorial : networkDown()), getContractBoard: () => ({ contracts: [], periodEndsAt: 0 }), }), path, ); await screen.findByRole("heading", { level: 1, name: title }); online = false; act(() => { focusManager.setFocused(false); focusManager.setFocused(true); }); const banner = await screen.findByText("Couldn't refresh the game."); expect(screen.getByRole("heading", { level: 1, name: title })).toBeInTheDocument(); online = true; await userEvent.click(within(banner.parentElement!).getByRole("button", { name: "Retry" })); await act(async () => {}); expect(screen.queryByText("Couldn't refresh the game.")).not.toBeInTheDocument(); }, ); it("Contracts: a failed board load offers Retry", async () => { let online = false; renderApp( fakeApi({ getPlayerState: () => ({ ...afterTutorial, bays: [{ id: "b", locationId: "earth" }] }), getContractBoard: () => (online ? { contracts: [], periodEndsAt: 0 } : networkDown()), }), "/contracts", ); const alert = await screen.findByText("Could not load contracts.", { exact: false }); online = true; await userEvent.click(within(alert).getByRole("button", { name: "Retry" })); expect( await screen.findByText(/No jobs here your fleet can take right now/), ).toBeInTheDocument(); }); }); describe("empty states", () => { const show = (path: string, state = afterTutorial, extra = {}) => renderApp(fakeApi({ getPlayerState: () => state, ...extra }), path); it("HQ: no ships points to the Market", async () => { show("/"); expect(await screen.findByText("No ships yet — buy one in the Market.")).toBeInTheDocument(); expect(screen.getByText("Nothing in flight.")).toBeInTheDocument(); }); it("Fleet: no ships links to the Market", async () => { show("/fleet"); expect(await screen.findByRole("link", { name: "visit the Market" })).toHaveAttribute( "href", "/market", ); }); it("Contracts: no idle ships explains how to take the jobs", async () => { show("/contracts"); expect(await screen.findByText(/No idle ships here/)).toBeInTheDocument(); }); it("Contracts: an empty board says no job fits the fleet, and when new ones come (CR-019)", async () => { show( "/contracts", { ...afterTutorial, bays: [{ id: "b", locationId: "earth" }] }, { getContractBoard: () => ({ contracts: [], periodEndsAt: 0 }) }, ); expect( await screen.findByText(/No jobs here your fleet can take right now/), ).toBeInTheDocument(); }); it("Contracts: an empty tutorial board says to check back", async () => { show("/contracts", samplePlayerState(), { getTutorialBoard: () => ({ contracts: [], periodEndsAt: 0 }), }); expect(await screen.findByText(/No company runs left this period/)).toBeInTheDocument(); }); it("Pilots: no hired pilots suggests hiring", async () => { show("/pilots"); expect(await screen.findByText(/No hired pilots yet/)).toBeInTheDocument(); expect(screen.getByText("Lease a bay to hire pilots there.")).toBeInTheDocument(); }); it("Market and Locations always have content for a new player", async () => { show("/market"); expect(await screen.findByRole("button", { name: "Buy Courier" })).toBeInTheDocument(); show("/locations"); expect((await screen.findAllByText("Earth")).length).toBeGreaterThan(0); }); });