110 lines
3.6 KiB
TypeScript
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();
|
|
});
|
|
});
|