deadhead-space-freight-tycoon/apps/web/test/map-interactions.test.tsx

164 lines
5.8 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,
fuelBurnedFu: 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,
isVeteran: 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();
});
});