101 lines
4.8 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|