127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import type { Contract, Ship } from "@deadhead/schema";
|
|
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { usePreferences } from "../src/state/preferences";
|
|
import { pairOptions } from "../src/screens/DispatchSheet";
|
|
import { jobFigures } from "../src/screens/jobFigures";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
|
|
|
const NOW = Date.now();
|
|
|
|
const job = (overrides: Partial<Contract>): Contract =>
|
|
({
|
|
id: "j1",
|
|
locationId: "earth",
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
goodId: "consumer-goods",
|
|
cargoTypeId: "standard",
|
|
massTonnes: 10,
|
|
payout: 2000,
|
|
shipper: "Pacific Orbital Logistics",
|
|
offeredAt: NOW,
|
|
expiresAt: NOW + 3_600_000,
|
|
priority: false,
|
|
deliveryWindowGameHours: null,
|
|
tier: null,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
firstContact: false,
|
|
...overrides,
|
|
}) as Contract;
|
|
|
|
const ship = (id: string, overrides: Partial<Ship> = {}): Ship => ({
|
|
id,
|
|
modelId: "courier",
|
|
name: id,
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
...shipExtras("courier"),
|
|
pilotId: "avatar",
|
|
...overrides,
|
|
});
|
|
|
|
function owner(ships: Ship[]) {
|
|
const base = samplePlayerState();
|
|
return samplePlayerState({
|
|
tutorial: null,
|
|
bays: [{ id: "b", locationId: "earth" }],
|
|
ships,
|
|
pilots: [{ ...base.pilots[0]!, assignedShipId: ships[0]?.id ?? null }],
|
|
});
|
|
}
|
|
|
|
function show(contracts: Contract[], ships: Ship[]) {
|
|
renderApp(
|
|
fakeApi({
|
|
getPlayerState: () => owner(ships),
|
|
getContractBoard: () => ({ contracts, periodEndsAt: NOW + 3_600_000 }),
|
|
}),
|
|
"/contracts?location=earth",
|
|
);
|
|
}
|
|
|
|
const rowNames = () =>
|
|
screen
|
|
.getAllByRole("row")
|
|
.map((r) => r.getAttribute("aria-label"))
|
|
.filter(Boolean);
|
|
|
|
describe("jobs table (decision 0.5.169)", () => {
|
|
beforeEach(() => usePreferences.setState({ jobSort: "net" }));
|
|
|
|
it("shows the same net as the dispatch sheet", () => {
|
|
const state = owner([ship("s1")]);
|
|
const contract = job({});
|
|
const figures = jobFigures(state, contract, NOW);
|
|
expect(figures.ready).toBe(true);
|
|
expect(figures.estimate).toBe(false);
|
|
expect(figures.net).toBe(pairOptions(state, contract, NOW)[0]!.netProfit);
|
|
expect(figures.perHour).toBe(Math.round(figures.net! / figures.hours!));
|
|
});
|
|
|
|
it("sorts by net, then by credits per hour, and remembers the choice", async () => {
|
|
const big = job({
|
|
id: "big",
|
|
goodId: "packaged-food",
|
|
payout: 3000,
|
|
destinationId: "moon",
|
|
massTonnes: 18,
|
|
});
|
|
const small = job({ id: "small", goodId: "mail", payout: 2000, massTonnes: 1 });
|
|
show([small, big], [ship("s1")]);
|
|
await screen.findByRole("table");
|
|
expect(rowNames()).toEqual(["Packaged Food", "Mail & Parcels"]);
|
|
const net = screen.getByRole("columnheader", { name: /Net/ });
|
|
expect(net).toHaveAttribute("aria-sort", "descending");
|
|
|
|
await userEvent.click(within(screen.getByRole("table")).getByRole("button", { name: /cr\/h/ }));
|
|
expect(screen.getByRole("columnheader", { name: /cr\/h/ })).toHaveAttribute(
|
|
"aria-sort",
|
|
"descending",
|
|
);
|
|
expect(net).toHaveAttribute("aria-sort", "none");
|
|
expect(usePreferences.getState().jobSort).toBe("rate");
|
|
});
|
|
|
|
it("keeps jobs it can't take below, with the reason and an estimate", async () => {
|
|
// The only ship is away: the figures are an estimate, and the row says why.
|
|
const away = ship("s1", { status: "in-flight", locationId: null, dispatchId: "d" });
|
|
show([job({})], [away]);
|
|
await screen.findByRole("table");
|
|
expect(screen.getByText("Can't take yet")).toBeInTheDocument();
|
|
const row = screen.getByRole("row", { name: "Consumer Goods" });
|
|
expect(row).toHaveTextContent("No idle ship at Earth");
|
|
expect(within(row).getByLabelText("Net, estimated")).toHaveTextContent(/^≈/);
|
|
});
|
|
|
|
it("opens the job from its row", async () => {
|
|
show([job({})], [ship("s1")]);
|
|
await userEvent.click(await screen.findByRole("button", { name: "Dispatch" }));
|
|
await waitFor(() => expect(screen.getByRole("dialog")).toBeInTheDocument());
|
|
});
|
|
});
|