import { content } from "@deadhead/content"; import type { Contract, Pilot, Ship } from "@deadhead/schema"; import { contractTripGameHours, priorityWindow } from "@deadhead/sim"; 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 { formatWindow } from "../src/components/Goods"; import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); const earth = content.location("earth")!; const moon = content.location("moon")!; const ship = (id: string, name: string, modelId: string, pilotId: string): Ship => ({ id, name, modelId, status: "idle", locationId: "earth", dispatchId: null, lastArrivalAt: 0, ...shipExtras(modelId), pilotId, }); const pilot = (id: string, name: string, shipId: string): Pilot => ({ id, name, isAvatar: false, isVeteran: false, xp: 0, level: 0, skills: {}, skillPointsAvailable: 0, assignedShipId: shipId, status: "idle", locationId: "earth", dutyHours: 0, dutyLimit: 12, resting: false, restEndsAt: null, }); /** Priority mail to the Moon, with the Freighter-reference window the sim would set. */ const rush: Contract = { id: "c-rush", originId: "earth", destinationId: "moon", cargoTypeId: "standard", goodId: "mail", shipper: "Pacific Orbital Logistics", priority: true, deliveryWindowGameHours: priorityWindow(earth, moon, 6, NOW, content), tier: 0, massTonnes: 6, payout: 1_500, requiredSkill: null, requiredLongHaulLevel: 0, offeredAt: NOW, expiresAt: NOW + 3_600_000, firstContact: false, }; const trip = (modelId: string) => contractTripGameHours(content.shipModel(modelId)!, rush, NOW, content); async function openSheet() { renderApp( fakeApi({ getPlayerState: () => samplePlayerState({ tutorial: null, company: { id: "c", name: "D", credits: 10_000, loanBalance: 0 }, bays: [{ id: "b", locationId: "earth" }], ships: [ ship("s-fast", "Quick Finch", "courier", "p-fast"), ship("s-slow", "Old Barge", "bulk-hauler", "p-slow"), ], pilots: [pilot("p-fast", "Ana Ruiz", "s-fast"), pilot("p-slow", "Bo Lind", "s-slow")], }), getContractBoard: () => ({ contracts: [rush], periodEndsAt: NOW + 3_600_000 }), }), "/contracts?location=earth", ); await userEvent.click(await screen.findByRole("button", { name: "Dispatch" })); return screen.getByRole("dialog"); } const option = (dialog: HTMLElement, shipName: string) => within(dialog).getByText(shipName).closest("li")!; describe("Priority in the dispatch sheet (CR-007)", () => { beforeEach(() => { vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] }); vi.setSystemTime(NOW); useClock.setState({ offsetMs: 0, synced: true, now: NOW }); }); afterEach(() => vi.useRealTimers()); it("enables a ship fast enough for the window and shows its margin", async () => { const window = rush.deliveryWindowGameHours!; const fast = option(await openSheet(), "Quick Finch"); expect(within(fast).getByRole("radio")).toBeEnabled(); expect(within(fast).getByLabelText("Priority margin")).toHaveTextContent( `Priority: ${formatWindow(trip("courier"))} trip, ${formatWindow(window - trip("courier"))} to spare in the ${formatWindow(window)} window`, ); }); it("disables a too-slow ship with its trip and the window from the sim", async () => { const window = rush.deliveryWindowGameHours!; expect(trip("bulk-hauler")).toBeGreaterThan(window); const slow = option(await openSheet(), "Old Barge"); expect(within(slow).getByRole("radio")).toBeDisabled(); expect(within(slow).getByRole("list", { name: "Problems" })).toHaveTextContent( `Too slow — ${formatWindow(trip("bulk-hauler"))} vs ${formatWindow(window)} window`, ); }); });