import { pilotExtras } from "./fixtures"; import { content } from "@deadhead/content"; import type { Contract, Pilot, ResolutionSummary, Ship } from "@deadhead/schema"; import { pilotShare, tripFuel } 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 { formatCredits } from "../src/format"; import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render"; const NOW = Date.parse("2100-01-01T00:00:00Z"); const ship = (id: string, name: string, pilotId: string): Ship => ({ id, name, modelId: "courier", status: "idle", locationId: "earth", dispatchId: null, pilotId, lastArrivalAt: 0, ...shipExtras("courier"), }); const pilot = (id: string, name: string, shipId: string, isAvatar: boolean): Pilot => ({ id, name, isAvatar, isVeteran: !isAvatar, xp: 0, level: 0, skills: {}, skillPointsAvailable: 0, assignedShipId: shipId, status: "idle", locationId: "earth", dutyHours: 0, dutyLimit: 12, resting: false, restEndsAt: null, ...pilotExtras, }); const job: Contract = { id: "c1", originId: "earth", destinationId: "moon", cargoTypeId: "standard", goodId: "consumer-goods", shipper: "Pacific Orbital Logistics", priority: false, deliveryWindowGameHours: null, tier: 0, massTonnes: 10, payout: 900, requiredSkill: null, requiredLongHaulLevel: 0, offeredAt: NOW, expiresAt: NOW + 30 * 60_000, firstContact: false, }; const summary: ResolutionSummary = { dispatchId: "d1", shipId: "s-vet", pilotId: "p-vet", originId: "earth", destinationId: "moon", tutorial: false, deadhead: false, fuelCost: 150, topUp: null, goodId: "consumer-goods", massTonnes: 10, shipper: "Pacific Orbital Logistics", credits: 600, loanRepayment: 0, pilotPay: 63, pilotXp: 10, discoveredLocationId: null, scriptedId: null, restEndsAt: null, resolvedAt: NOW, }; const state = (summaries: ResolutionSummary[] = []) => samplePlayerState({ tutorial: null, company: { id: "c", name: "D", credits: 100_000, loanBalance: 0 }, bays: [{ id: "b", locationId: "earth" }], ships: [ship("s-me", "My Lark", "avatar"), ship("s-vet", "Rosa's Lark", "p-vet")], pilots: [pilot("avatar", "Captain", "s-me", true), pilot("p-vet", "Rosa Kell", "s-vet", false)], summaries, }); describe("pilot share in the client (Step 10J.6)", () => { beforeEach(() => { vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] }); vi.setSystemTime(NOW); useClock.setState({ offsetMs: 0, synced: true, now: NOW }); }); afterEach(() => vi.useRealTimers()); it("shows the hired pilot's share and the net profit after it, and no share for the avatar", async () => { renderApp( fakeApi({ getPlayerState: () => state(), getContractBoard: () => ({ contracts: [job], periodEndsAt: NOW + 600_000 }), }), "/contracts?location=earth", ); await userEvent.click(await screen.findByRole("button", { name: "Dispatch" })); const dialog = screen.getByRole("dialog"); // The fixtures' tanks were filled at 16 cr/fu (CR-015). const fuel = Math.round( tripFuel( content.shipModel("courier")!, content.location("earth")!, content.location("moon")!, content.config, ) * 16, ); const share = pilotShare(job.payout, fuel, content.config); const hired = within(dialog).getByText("Rosa's Lark").closest("li")!; expect(hired).toHaveTextContent(`Pilot share${formatCredits(share)}`); expect(hired).toHaveTextContent(`Net profit${formatCredits(job.payout - fuel - share)}`); const mine = within(dialog).getByText("My Lark").closest("li")!; expect(mine).not.toHaveTextContent("Pilot share"); expect(mine).toHaveTextContent(`Net profit${formatCredits(job.payout - fuel)}`); }); it("lists the pilot's share in a delivery summary", async () => { renderApp(fakeApi({ getPlayerState: () => state([summary]) }), "/"); const dialog = await screen.findByRole("dialog", { name: "While you were away" }); expect(dialog).toHaveTextContent(`${formatCredits(63)} pilot share to Rosa Kell`); }); });