deadhead-space-freight-tycoon/apps/web/test/job-hours.test.tsx

110 lines
3.6 KiB
TypeScript

import { content } from "@deadhead/content";
import type { Contract, Pilot, Ship } from "@deadhead/schema";
import { contractTripGameHours, effectiveStats } from "@deadhead/sim";
import { screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useClock } from "../src/clock/clock";
import { formatGameHours } from "../src/format";
import { jobGameHours } from "../src/game";
import { pilotExtras } from "./fixtures";
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const job = (over: Partial<Contract> = {}): Contract => ({
id: "j1",
originId: "earth",
destinationId: "moon",
cargoTypeId: "standard",
goodId: "consumer-goods",
shipper: "Pacific Orbital Logistics",
priority: false,
deliveryWindowGameHours: null,
tier: 0,
massTonnes: 12,
payout: 1_500,
requiredSkill: null,
requiredLongHaulLevel: 0,
offeredAt: NOW,
expiresAt: NOW + 30 * 60_000,
firstContact: false,
...over,
});
const ship = (id: string, modelId: string, locationId: string, pilotId: string | null): Ship => ({
id,
modelId,
name: id,
status: "idle",
locationId,
dispatchId: null,
pilotId,
lastArrivalAt: 0,
...shipExtras(modelId),
});
const pilot = (id: string, shipId: string): Pilot => ({
...samplePlayerState().pilots[0]!,
id,
name: id,
isAvatar: id === "avatar",
assignedShipId: shipId,
locationId: null,
...pilotExtras,
});
const hoursFor = (modelId: string, contract: Contract) =>
contractTripGameHours(
effectiveStats(content.shipModel(modelId)!, {}, content),
contract,
content,
);
describe("job length on the board", () => {
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
vi.setSystemTime(NOW);
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
});
afterEach(() => vi.useRealTimers());
it("shows the game hours beside the weight and distance", async () => {
const contract = job();
const state = samplePlayerState({
tutorial: null,
bays: [{ id: "b", locationId: "earth" }],
ships: [ship("s1", "courier", "earth", "avatar")],
pilots: [pilot("avatar", "s1")],
});
renderApp(
fakeApi({
getPlayerState: () => state,
getContractBoard: () => ({ contracts: [contract], periodEndsAt: NOW + 30 * 60_000 }),
}),
"/contracts?location=earth",
);
const hours = formatGameHours(hoursFor("courier", contract));
const time = await screen.findByLabelText(`Takes ${hours} of game time`);
const row = time.closest("tr")!;
expect(row).toHaveTextContent("Pacific Orbital Logistics");
expect(row).toHaveTextContent("0.38M km");
expect(time.parentElement).toHaveTextContent(`12 t · ${hours}`);
});
it("times the job for the fastest ship that could take it, preferring one at the origin", () => {
const contract = job();
const ships = [
ship("slow-here", "freighter", "earth", "p1"),
ship("fast-away", "clipper", "moon", "p2"),
];
const pilots = [pilot("p1", "slow-here"), pilot("p2", "fast-away")];
expect(jobGameHours({ ships, pilots }, contract)).toBeCloseTo(hoursFor("freighter", contract));
// Nobody at Earth: the fastest anywhere.
const away = ships.map((s) => ({ ...s, locationId: "moon" }));
expect(jobGameHours({ ships: away, pilots }, contract)).toBeCloseTo(
Math.min(hoursFor("freighter", contract), hoursFor("clipper", contract)),
);
// No crewed ship that could take it: no time shown.
expect(jobGameHours({ ships, pilots: [] }, contract)).toBeNull();
});
});