deadhead-space-freight-tycoon/apps/web/test/jupiter-system.test.ts

101 lines
4.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { MAX_ZOOM, MIN_ZOOM } from "../src/map/camera";
import { childOffset, labelBox, layoutScene, markerAt, planLabels } from "../src/map/layout";
import { buildScene } from "../src/map/scene";
import { mapStyle } from "../src/map/style";
import { samplePlayerState } from "./render";
const NOW = Date.parse("2100-01-01T00:00:00Z");
const view = { width: 1280, height: 800 };
const scene = buildScene(samplePlayerState(), NOW);
/** Labels about as wide as the real ones: ~6.5 px a character at 11 px. */
const NAMES = new Map(scene.locations.map((l) => [l.id, l.name]));
const labelSize = (id: string) => ({ width: (NAMES.get(id) ?? id).length * 6.5, height: 14 });
const cameraAt = (zoom: number) => ({ zoom, panX: 0, panY: 0 });
const JUPITER = "jupiter-cloud-platform";
const CHILDREN = ["io-sulfur-works", "europa-ice-station"];
describe("the Jupiter system on the map (Step 16.8, decision 0.5.105)", () => {
it("draws Io and Europa exactly at their configured offsets from Jupiter, at any zoom", () => {
for (const zoom of [MIN_ZOOM, 1, 2.5, 10, MAX_ZOOM]) {
const layout = layoutScene(scene, view, cameraAt(zoom));
const at = (id: string) => layout.locations.find((l) => l.id === id)!.center;
for (const child of CHILDREN) {
const offset = childOffset(child, at(JUPITER), layout.sun.center);
expect(at(child).x).toBeCloseTo(at(JUPITER).x + offset.x, 9);
expect(at(child).y).toBeCloseTo(at(JUPITER).y + offset.y, 9);
expect(Math.hypot(offset.x, offset.y)).toBeCloseTo(
mapStyle.childOffsets[child]!.distancePx,
);
}
}
});
it('shows only Jupiter\'s label, with "+2", when zoomed out', () => {
const layout = layoutScene(scene, view, cameraAt(1));
const { decorations, markers } = planLabels(layout, labelSize, 1);
expect(decorations.get(JUPITER)!.shown).toBe(true);
for (const child of CHILDREN) expect(decorations.get(child)!.shown, child).toBe(false);
expect(markers).toEqual([expect.objectContaining({ parentId: JUPITER, count: 2 })]);
// The marker sits just right of Jupiter's label.
const label = labelBox(decorations.get(JUPITER)!.label, labelSize(JUPITER));
expect(markers[0]!.center.x).toBeGreaterThan(label.right);
});
it("shows all three labels, without overlapping, once zoomed past the threshold", () => {
const zoom = mapStyle.labelDeclutter[JUPITER]!.expandZoom;
const layout = layoutScene(scene, view, cameraAt(zoom));
const { decorations, markers } = planLabels(layout, labelSize, zoom);
expect(markers).toEqual([]);
const family = [JUPITER, ...CHILDREN];
for (const id of family) expect(decorations.get(id)!.shown, id).toBe(true);
const boxes = family.map((id) => labelBox(decorations.get(id)!.label, labelSize(id)));
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
const [a, b] = [boxes[i]!, boxes[j]!];
const overlap =
a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
expect(overlap, `${family[i]} / ${family[j]}`).toBe(false);
}
}
});
it('opens the folded labels when "+2" is tapped', () => {
const layout = layoutScene(scene, view, cameraAt(1));
const { markers } = planLabels(layout, labelSize, 1);
const marker = markerAt(markers, markers[0]!.center)!;
expect(marker.parentId).toBe(JUPITER);
expect(markerAt(markers, { x: -500, y: -500 })).toBeNull();
const opened = planLabels(layout, labelSize, 1, new Set([JUPITER]));
for (const child of CHILDREN) expect(opened.decorations.get(child)!.shown).toBe(true);
expect(opened.markers).toEqual([]);
});
it("keeps Earth and the Moon labelled while their labels don't collide", () => {
const layout = layoutScene(scene, view, cameraAt(1));
const { decorations, markers } = planLabels(layout, labelSize, 1);
expect(decorations.get("moon")!.shown).toBe(true);
expect(markers.some((m) => m.parentId === "earth")).toBe(false);
});
it("gives every place and the marker a tap target of at least 44 px", () => {
for (const zoom of [MIN_ZOOM, 1, MAX_ZOOM]) {
const layout = layoutScene(scene, view, cameraAt(zoom));
for (const l of layout.locations) expect(l.tap, l.id).toBeGreaterThanOrEqual(44);
for (const m of planLabels(layout, labelSize, zoom).markers) {
expect(m.tap).toBeGreaterThanOrEqual(44);
}
}
});
it("draws Venus at its own place, not beside another body", () => {
const layout = layoutScene(scene, view, cameraAt(1));
const venus = layout.locations.find((l) => l.id === "venus-cloud-city")!;
for (const other of layout.locations.filter((l) => l.id !== venus.id)) {
expect(
Math.hypot(venus.center.x - other.center.x, venus.center.y - other.center.y),
).toBeGreaterThan((venus.size + other.size) / 2);
}
});
});