deadhead-space-freight-tycoon/apps/web/test/pilot-share.test.tsx

142 lines
4.4 KiB
TypeScript

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, waitFor, 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, comeBack } 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,
independent: 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,
event: 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("takes the pilot's share out of a delivery's net in the summary", async () => {
comeBack();
renderApp(fakeApi({ getPlayerState: () => state([summary]) }), "/");
const dialog = await screen.findByRole("dialog", { name: /While you were away/ });
// 600 cr after the share, less 150 cr of fuel (CR-025); the share itself is in the Logbook.
await waitFor(() => expect(dialog).toHaveTextContent(`+${formatCredits(450)}`));
});
});