116 lines
3.9 KiB
TypeScript
116 lines
3.9 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { Contract } from "@deadhead/schema";
|
|
import { MS_PER_HOUR, realToGame, routeDistance, travelTimeGameHours } from "@deadhead/sim";
|
|
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { ApiError } from "../src/api/client";
|
|
import { useClock } from "../src/clock/clock";
|
|
import { formatDuration } from "../src/format";
|
|
import { fakeApi, renderApp, samplePlayerState } from "./render";
|
|
|
|
const NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
|
|
const contract = (id: string, mass: number, payout: number): Contract => ({
|
|
id,
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
cargoTypeId: "standard",
|
|
tier: 0,
|
|
massTonnes: mass,
|
|
payout,
|
|
requiredSkill: null,
|
|
requiredLongHaulLevel: 0,
|
|
offeredAt: NOW,
|
|
expiresAt: NOW + 3_600_000,
|
|
firstContact: false,
|
|
});
|
|
|
|
const board = {
|
|
contracts: [contract("t1", 12, 230), contract("t2", 18, 345)],
|
|
periodEndsAt: NOW + 3_600_000,
|
|
};
|
|
|
|
/** The trip time the sim predicts for the Courier loaner, Earth→Moon, departing now. */
|
|
function expectedTripTime(now: number, massTonnes: number) {
|
|
const distance = routeDistance(
|
|
content.location("earth")!,
|
|
content.location("moon")!,
|
|
realToGame(now, content.config),
|
|
);
|
|
const hours = travelTimeGameHours(
|
|
content.shipModel("courier")!,
|
|
distance,
|
|
massTonnes,
|
|
content.config,
|
|
);
|
|
return formatDuration((hours * MS_PER_HOUR) / content.config.timeCompression);
|
|
}
|
|
|
|
describe("tutorial contract board", () => {
|
|
beforeEach(() => useClock.setState({ offsetMs: 0, synced: true, now: NOW }));
|
|
|
|
it("shows each contract with route, cargo, payout and the sim's trip time", async () => {
|
|
const api = fakeApi({
|
|
getPlayerState: () => samplePlayerState(),
|
|
getTutorialBoard: () => board,
|
|
});
|
|
renderApp(api, "/contracts");
|
|
|
|
const items = await screen.findAllByRole("listitem");
|
|
expect(items).toHaveLength(2);
|
|
expect(items[0]).toHaveTextContent("Earth → Moon");
|
|
expect(items[0]).toHaveTextContent("Standard · 12 t");
|
|
expect(items[0]).toHaveTextContent("230 cr");
|
|
expect(within(items[0]!).getByLabelText("Trip time")).toHaveTextContent(
|
|
expectedTripTime(useClock.getState().now, 12),
|
|
);
|
|
});
|
|
|
|
it("dispatches the tutorial run with Fly it", async () => {
|
|
const api = fakeApi({
|
|
getPlayerState: () => samplePlayerState(),
|
|
getTutorialBoard: () => board,
|
|
});
|
|
renderApp(api, "/contracts");
|
|
const [first] = await screen.findAllByRole("listitem");
|
|
await userEvent.click(within(first!).getByRole("button", { name: "Fly it" }));
|
|
await waitFor(() =>
|
|
expect(api.call).toHaveBeenCalledWith("createDispatch", {
|
|
kind: "tutorial",
|
|
contractId: "t1",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("disables Fly it while a tutorial run is in flight", async () => {
|
|
const api = fakeApi({
|
|
getPlayerState: () =>
|
|
samplePlayerState({ tutorial: { loanerLocationId: "earth", runInFlight: true } }),
|
|
getTutorialBoard: () => board,
|
|
});
|
|
renderApp(api, "/contracts");
|
|
await screen.findAllByRole("listitem");
|
|
for (const button of screen.getAllByRole("button", { name: "Fly it" })) {
|
|
expect(button).toBeDisabled();
|
|
}
|
|
expect(screen.getByText(/Your run is in flight/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the server's message when dispatch fails", async () => {
|
|
const api = fakeApi({
|
|
getPlayerState: () => samplePlayerState(),
|
|
getTutorialBoard: () => board,
|
|
createDispatch: () => {
|
|
throw new ApiError(409, "tutorial-run-in-flight", "Your tutorial run is still flying.");
|
|
},
|
|
});
|
|
renderApp(api, "/contracts");
|
|
const [first] = await screen.findAllByRole("listitem");
|
|
await userEvent.click(within(first!).getByRole("button", { name: "Fly it" }));
|
|
expect(await screen.findByRole("alert")).toHaveTextContent(
|
|
"Your tutorial run is still flying.",
|
|
);
|
|
});
|
|
});
|