deadhead-space-freight-tycoon/apps/web/test/hq.test.tsx

93 lines
3.2 KiB
TypeScript

import type { Dispatch, Ship } from "@deadhead/schema";
import { act, screen, within } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useClock } from "../src/clock/clock";
import { tutorialGoalCost } from "../src/screens/HQ";
import { fakeApi, renderApp, samplePlayerState } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const ship = (id: string, status: Ship["status"]): Ship => ({
id,
modelId: "courier",
name: id,
status,
locationId: status === "idle" ? "earth" : null,
dispatchId: status === "idle" ? null : `d-${id}`,
pilotId: null,
});
const dispatch = (shipId: string, arrivalAt: number): Dispatch => ({
id: `d-${shipId}`,
shipId,
pilotId: "avatar",
contract: null,
originId: "earth",
destinationId: "moon",
tutorial: false,
departureAt: NOW - 60_000,
arrivalAt,
distanceMillionKm: 0.39,
fuelCost: 50,
expectedPayout: 0,
seed: 1,
status: "in-flight",
resolvedAt: null,
});
describe("HQ screen", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows idle and in-flight counts and counts down to the next arrival", async () => {
const state = samplePlayerState({
tutorial: null,
ships: [ship("a", "idle"), ship("b", "in-flight"), ship("c", "in-flight")],
activeDispatches: [dispatch("b", NOW + 125_000), dispatch("c", NOW + 65_000)],
});
renderApp(fakeApi({ getPlayerState: () => state }));
await screen.findByRole("heading", { level: 1, name: "HQ" });
const statValue = (label: string) => screen.getByText(label).nextElementSibling;
expect(statValue("Ships idle")).toHaveTextContent("1");
expect(statValue("Ships in flight")).toHaveTextContent("2");
const next = screen.getByRole("heading", { name: "Next arrival" }).parentElement!;
expect(within(next).getByText("1m 05s")).toBeInTheDocument();
act(() => vi.advanceTimersByTime(10_000));
expect(within(next).getByText("55s")).toBeInTheDocument();
expect(screen.queryByText(/Save up for your first ship/)).not.toBeInTheDocument();
});
it("shows tutorial goal progress as a percentage of a ship plus an Earth bay", async () => {
const goal = tutorialGoalCost();
const state = samplePlayerState({
company: { id: "c", name: "D", credits: Math.round(goal * 0.4), loanBalance: 0 },
});
renderApp(fakeApi({ getPlayerState: () => state }));
expect(
await screen.findByText("Save up for your first ship and an Earth bay"),
).toBeInTheDocument();
expect(screen.getByRole("progressbar", { name: "Tutorial goal" })).toHaveAttribute(
"aria-valuenow",
"40",
);
expect(screen.getByText("40%")).toBeInTheDocument();
expect(screen.getByText("Nothing in flight.")).toBeInTheDocument();
});
it("caps tutorial progress at 100%", async () => {
const state = samplePlayerState({
company: { id: "c", name: "D", credits: tutorialGoalCost() * 3, loanBalance: 0 },
});
renderApp(fakeApi({ getPlayerState: () => state }));
expect(await screen.findByText("100%")).toBeInTheDocument();
});
});