139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
import type { Dispatch, PlayerState, Ship } from "@deadhead/schema";
|
|
import { dispatchProgress } from "@deadhead/sim";
|
|
import { act, 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 { fakeApi, renderApp, samplePlayerState } from "./render";
|
|
|
|
const NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
|
|
const flight: Dispatch = {
|
|
id: "d1",
|
|
shipId: "s-fly",
|
|
pilotId: "p2",
|
|
contract: null,
|
|
originId: "earth",
|
|
destinationId: "moon",
|
|
tutorial: false,
|
|
departureAt: NOW - 60_000,
|
|
arrivalAt: NOW + 180_000,
|
|
distanceMillionKm: 0.39,
|
|
fuelCost: 50,
|
|
expectedPayout: 0,
|
|
seed: 1,
|
|
status: "in-flight",
|
|
resolvedAt: null,
|
|
};
|
|
|
|
const ship = (overrides: Partial<Ship>): Ship => ({
|
|
id: "s",
|
|
modelId: "courier",
|
|
name: "Ship",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
dispatchId: null,
|
|
lastArrivalAt: 0,
|
|
pilotId: null,
|
|
...overrides,
|
|
});
|
|
|
|
function fleetState(overrides: Partial<PlayerState> = {}): PlayerState {
|
|
const base = samplePlayerState({ tutorial: null });
|
|
return {
|
|
...base,
|
|
bays: [
|
|
{ id: "b1", locationId: "earth" },
|
|
{ id: "b2", locationId: "moon" },
|
|
{ id: "b3", locationId: "mars" },
|
|
],
|
|
ships: [
|
|
ship({ id: "s-idle", name: "Dusty Mule", pilotId: "avatar" }),
|
|
ship({
|
|
id: "s-fly",
|
|
name: "Slow Ox",
|
|
status: "in-flight",
|
|
locationId: null,
|
|
dispatchId: "d1",
|
|
pilotId: "p2",
|
|
}),
|
|
],
|
|
pilots: [
|
|
{ ...base.pilots[0]!, assignedShipId: "s-idle" },
|
|
{
|
|
...base.pilots[0]!,
|
|
id: "p2",
|
|
name: "Rae Okafor",
|
|
isAvatar: false,
|
|
assignedShipId: "s-fly",
|
|
status: "in-flight",
|
|
locationId: null,
|
|
},
|
|
],
|
|
activeDispatches: [flight],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("fleet screen", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
});
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
it("shows an in-flight ship's progress from the sim, and it advances", async () => {
|
|
renderApp(fakeApi({ getPlayerState: () => fleetState() }), "/fleet");
|
|
const bar = await screen.findByRole("progressbar", { name: "Slow Ox progress" });
|
|
expect(bar).toHaveAttribute(
|
|
"aria-valuenow",
|
|
String(Math.round(dispatchProgress(flight, NOW) * 100)),
|
|
);
|
|
expect(bar).toHaveAttribute("aria-valuenow", "25");
|
|
|
|
act(() => vi.advanceTimersByTime(60_000));
|
|
expect(bar).toHaveAttribute("aria-valuenow", "50");
|
|
expect(screen.getByText("Earth → Moon", { exact: false })).toBeInTheDocument();
|
|
expect(screen.getByText("2m 00s")).toBeInTheDocument();
|
|
});
|
|
|
|
it("lists each ship with model, location and pilot", async () => {
|
|
renderApp(fakeApi({ getPlayerState: () => fleetState() }), "/fleet");
|
|
const idle = (await screen.findByText("Dusty Mule")).closest("li")!;
|
|
expect(idle).toHaveTextContent("Courier");
|
|
expect(idle).toHaveTextContent("At Earth");
|
|
expect(idle).toHaveTextContent("Captain");
|
|
});
|
|
|
|
it("repositions to a destination chosen from the player's other bays only", async () => {
|
|
const api = fakeApi({ getPlayerState: () => fleetState() });
|
|
renderApp(api, "/fleet");
|
|
const idle = (await screen.findByText("Dusty Mule")).closest("li")!;
|
|
await userEvent.click(within(idle).getByRole("button", { name: "Reposition" }));
|
|
|
|
const picker = screen.getByRole("dialog", { name: "Reposition Dusty Mule" });
|
|
const options = within(picker)
|
|
.getAllByRole("button")
|
|
.map((b) => b.textContent)
|
|
.filter((t) => t !== "Cancel");
|
|
expect(options).toEqual(["Moon", "Mars"]);
|
|
|
|
await userEvent.click(within(picker).getByRole("button", { name: "Moon" }));
|
|
await waitFor(() =>
|
|
expect(api.call).toHaveBeenCalledWith("createDispatch", {
|
|
kind: "reposition",
|
|
shipId: "s-idle",
|
|
pilotId: "avatar",
|
|
destinationId: "moon",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("does not offer reposition to in-flight ships", async () => {
|
|
renderApp(fakeApi({ getPlayerState: () => fleetState() }), "/fleet");
|
|
const flying = (await screen.findByText("Slow Ox")).closest("li")!;
|
|
expect(within(flying).queryByRole("button", { name: "Reposition" })).not.toBeInTheDocument();
|
|
});
|
|
});
|