import type { Contract } from "@deadhead/schema"; import { screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useClock } from "../src/clock/clock"; import { fakeApi, renderApp, samplePlayerState } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); const contract = (overrides: Partial): Contract => ({ id: "c", originId: "earth", destinationId: "moon", cargoTypeId: "standard", tier: 0, massTonnes: 50, payout: 2000, requiredSkill: null, requiredLongHaulLevel: 0, offeredAt: NOW, expiresAt: NOW + 30 * 60_000, firstContact: false, ...overrides, }); const boards: Record = { earth: [ contract({ id: "e1", payout: 1999 }), contract({ id: "e2", destinationId: "mars", cargoTypeId: "heavy", tier: 2, requiredSkill: { skillId: "heavy-cargo", level: 2 }, requiredLongHaulLevel: 1, firstContact: true, payout: 88_000, }), ], moon: [contract({ id: "m1", originId: "moon", destinationId: "earth", payout: 1234 })], }; const owner = () => samplePlayerState({ tutorial: null, bays: [ { id: "b1", locationId: "earth" }, { id: "b2", locationId: "moon" }, ], }); describe("bay contract boards", () => { beforeEach(() => { vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] }); vi.setSystemTime(NOW); useClock.setState({ offsetMs: 0, synced: true, now: NOW }); }); afterEach(() => vi.useRealTimers()); it("shows the first bay's board, and switching bays shows that bay's board", async () => { const api = fakeApi({ getPlayerState: owner, getContractBoard: ({ locationId }) => ({ contracts: boards[locationId] ?? [], periodEndsAt: NOW + 30 * 60_000, }), }); renderApp(api, "/contracts"); expect(await screen.findByText("1,999 cr")).toBeInTheDocument(); expect(screen.queryByText("1,234 cr")).not.toBeInTheDocument(); await userEvent.selectOptions(screen.getByLabelText("Bay"), "moon"); expect(await screen.findByText("1,234 cr")).toBeInTheDocument(); expect(screen.getByText("Moon → Earth")).toBeInTheDocument(); expect(screen.queryByText("1,999 cr")).not.toBeInTheDocument(); }); it("shows contract details, requirements, expiry and the first-contact badge", async () => { const api = fakeApi({ getPlayerState: owner, getContractBoard: () => ({ contracts: boards.earth!, periodEndsAt: NOW + 30 * 60_000 }), }); renderApp(api, "/contracts"); const items = await screen.findAllByRole("listitem"); const [plain, special] = items; expect(plain).toHaveTextContent("Standard · 50 t"); expect(plain).toHaveTextContent("No skills needed"); expect(plain).toHaveTextContent("expires in 30m 00s"); expect(within(plain!).queryByText("First contact")).not.toBeInTheDocument(); expect(special).toHaveTextContent("Earth → Mars"); expect(special).toHaveTextContent("Heavy (tier 2)"); expect(special).toHaveTextContent("Needs Heavy Cargo 2, Long Haul 1"); expect(special).toHaveTextContent(/M km/); expect(within(special!).getByText("First contact")).toBeInTheDocument(); }); it("explains how to get contracts when the player has no bays", async () => { renderApp( fakeApi({ getPlayerState: () => samplePlayerState({ tutorial: null }) }), "/contracts", ); expect(await screen.findByText(/You have no bays yet/)).toBeInTheDocument(); }); });