162 lines
5.7 KiB
TypeScript
162 lines
5.7 KiB
TypeScript
import type { Dispatch, PlayerState } from "@deadhead/schema";
|
|
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 { DEFAULT_CAMERA, toScreen } from "../src/map/camera";
|
|
import { hitTest } from "../src/map/hitTest";
|
|
import { buildScene } from "../src/map/scene";
|
|
import { fakeApi, renderApp, samplePlayerState, shipExtras } from "./render";
|
|
|
|
const NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
|
|
const flight: Dispatch = {
|
|
id: "d1",
|
|
shipId: "s1",
|
|
pilotId: "p-rae",
|
|
contract: null,
|
|
originId: "earth",
|
|
destinationId: "mars",
|
|
tutorial: false,
|
|
departureAt: NOW - 3_600_000,
|
|
arrivalAt: NOW + 5 * 60_000,
|
|
distanceMillionKm: 100,
|
|
fuelCost: 0,
|
|
expectedPayout: 0,
|
|
seed: 1,
|
|
status: "in-flight",
|
|
resolvedAt: null,
|
|
};
|
|
|
|
function mapState(): PlayerState {
|
|
const base = samplePlayerState({ tutorial: null });
|
|
return {
|
|
...base,
|
|
bays: [{ id: "b1", locationId: "moon" }],
|
|
ships: [
|
|
{
|
|
id: "s1",
|
|
modelId: "freighter",
|
|
name: "Slow Ox",
|
|
status: "in-flight",
|
|
locationId: null,
|
|
dispatchId: "d1",
|
|
lastArrivalAt: 0,
|
|
...shipExtras("freighter"),
|
|
pilotId: "p-rae",
|
|
},
|
|
],
|
|
pilots: [
|
|
...base.pilots,
|
|
{
|
|
...base.pilots[0]!,
|
|
id: "p-rae",
|
|
name: "Rae Okafor",
|
|
isAvatar: false,
|
|
assignedShipId: "s1",
|
|
status: "in-flight",
|
|
locationId: null,
|
|
},
|
|
],
|
|
activeDispatches: [flight],
|
|
};
|
|
}
|
|
|
|
describe("map hit-testing", () => {
|
|
const viewport = { width: 375, height: 690 };
|
|
|
|
it("selects the location under a tap, and nothing on empty space", () => {
|
|
const scene = buildScene(mapState(), NOW);
|
|
// Ceres: nothing else is near it (the test flight is almost at Mars).
|
|
const ceres = scene.locations.find((l) => l.id === "ceres-station")!;
|
|
const p = toScreen(ceres.point, viewport, DEFAULT_CAMERA);
|
|
expect(hitTest(scene, { x: p.x + 5, y: p.y - 5 }, viewport, DEFAULT_CAMERA)).toEqual({
|
|
kind: "location",
|
|
id: "ceres-station",
|
|
});
|
|
expect(hitTest(scene, { x: 2, y: 2 }, viewport, DEFAULT_CAMERA)).toBeNull();
|
|
});
|
|
|
|
it("selects a ship marker under a tap", () => {
|
|
const scene = buildScene(mapState(), NOW);
|
|
const p = toScreen(scene.flights[0]!.marker, viewport, DEFAULT_CAMERA);
|
|
expect(hitTest(scene, p, viewport, DEFAULT_CAMERA)).toEqual({
|
|
kind: "flight",
|
|
dispatchId: "d1",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("map panels", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers({ toFake: ["Date", "setInterval", "clearInterval"] });
|
|
vi.setSystemTime(NOW);
|
|
useClock.setState({ offsetMs: 0, synced: true, now: NOW });
|
|
});
|
|
afterEach(() => vi.useRealTimers());
|
|
|
|
const openMap = async () => {
|
|
renderApp(fakeApi({ getPlayerState: mapState }), "/map");
|
|
return screen.findByTestId("map-canvas");
|
|
};
|
|
|
|
it("tapping a location with a bay shows its details, its contracts link and the HQ", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Moon" }));
|
|
const panel = screen.getByRole("dialog", { name: "Moon" });
|
|
expect(panel).toHaveTextContent("Airless body");
|
|
expect(panel).toHaveTextContent(/from Earth/);
|
|
expect(within(panel).getByRole("link", { name: "View contracts" })).toHaveAttribute(
|
|
"href",
|
|
"/contracts?location=moon",
|
|
);
|
|
expect(panel).toHaveTextContent("Your HQ is here.");
|
|
});
|
|
|
|
it("tapping a discovered location without a bay still links to its contracts", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Earth" }));
|
|
const panel = screen.getByRole("dialog", { name: "Earth" });
|
|
expect(panel).toHaveTextContent("No HQ here.");
|
|
expect(within(panel).getByRole("link", { name: "View contracts" })).toHaveAttribute(
|
|
"href",
|
|
"/contracts?location=earth",
|
|
);
|
|
});
|
|
|
|
it("tapping an undiscovered location hides its details", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Mars (locked)" }));
|
|
expect(screen.getByRole("dialog", { name: "Mars" })).toHaveTextContent(
|
|
"Undiscovered — deliver here to unlock.",
|
|
);
|
|
});
|
|
|
|
it("tapping a ship shows its route, pilot and ETA", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap ship s1" }));
|
|
const panel = screen.getByRole("dialog", { name: "Slow Ox" });
|
|
expect(panel).toHaveTextContent("Earth → Mars");
|
|
expect(panel).toHaveTextContent("Pilot: Rae Okafor");
|
|
expect(panel).toHaveTextContent("Arrives in 5m 00s");
|
|
});
|
|
|
|
it("tapping empty space or Close dismisses the panel", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Moon" }));
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap empty space" }));
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Earth" }));
|
|
await userEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("the board link opens Contracts with that location selected", async () => {
|
|
const canvas = await openMap();
|
|
await userEvent.click(within(canvas).getByRole("button", { name: "Tap Moon" }));
|
|
await userEvent.click(screen.getByRole("link", { name: "View contracts" }));
|
|
expect(await screen.findByRole("button", { name: /Moon/, pressed: true })).toBeInTheDocument();
|
|
});
|
|
});
|