import { renderHook } from "@testing-library/react"; import type { Map as MapLibreMap } from "maplibre-gl"; import { describe, expect, it, vi } from "vitest"; import { useMapInteractions, clearMapFeatureInteractionState, isPointOccludedByCityBuilding, setMapFeatureInteractionState, toMapFeatureReference } from "./use-map-interactions"; import { INTERACTIVE_HIT_LAYER_IDS } from "../map/layers"; describe("map feature interaction state", () => { const feature = { source: "junctions", sourceLayer: "junctions", id: "junction-7" } as const; type CityOcclusionMap = Parameters[0]; it("maps selected business features to promoted vector feature references", () => { expect(toMapFeatureReference({ id: "junction-7", layer: "junctions" })).toEqual(feature); expect(toMapFeatureReference(null)).toBeNull(); }); it("sets hover and selected through feature-state", () => { const map = { setFeatureState: vi.fn(), removeFeatureState: vi.fn() }; setMapFeatureInteractionState(map, feature, { selected: true, hovered: false }); expect(map.setFeatureState).toHaveBeenCalledWith( { source: "junctions", sourceLayer: "junctions", id: "junction-7" }, { selected: true, hovered: false } ); }); it("clears only the requested state key", () => { const map = { setFeatureState: vi.fn(), removeFeatureState: vi.fn() }; clearMapFeatureInteractionState(map, feature, "selected"); expect(map.removeFeatureState).toHaveBeenCalledWith( { source: "junctions", sourceLayer: "junctions", id: "junction-7" }, "selected" ); }); it("uses transparent hit layers for pointer interactions", () => { expect(INTERACTIVE_HIT_LAYER_IDS).toEqual(expect.arrayContaining([ "pipes-hit", "junctions-hit", "scada-pressure-hit", "scada-flow-hit", "water_quality_monitoring_points-hit", "fx_20200423_pipes-hit" ])); }); it("blocks pointer interactions where a 3D building is rendered", () => { const queryRenderedFeatures = vi.fn(() => [{ id: "building-1" }]); const map = { getLayer: vi.fn(() => ({ id: "fengxian-city-buildings" })), getPitch: vi.fn(() => 55), queryRenderedFeatures }; expect(isPointOccludedByCityBuilding(map as unknown as CityOcclusionMap, [120, 80])).toBe(true); expect(queryRenderedFeatures).toHaveBeenCalledWith([120, 80], { layers: ["fengxian-city-buildings"] }); }); it("keeps 2D pointer interactions independent of the optional city scene", () => { const queryRenderedFeatures = vi.fn(); const map = { getLayer: vi.fn(() => ({ id: "fengxian-city-buildings" })), getPitch: vi.fn(() => 0), queryRenderedFeatures }; expect(isPointOccludedByCityBuilding(map as unknown as CityOcclusionMap, [120, 80])).toBe(false); expect(queryRenderedFeatures).not.toHaveBeenCalled(); }); }); it("suspends feature picking while the map is used to look up a place", () => { const canvas = document.createElement("canvas"); const map = { on: vi.fn(), off: vi.fn(), getLayer: vi.fn(() => ({})), getCanvas: () => canvas }; const props = { enabled: false, mapRef: { current: map as unknown as MapLibreMap }, availableSourceIds: ["pipes"] as const, mapReady: true, onSelectFeature: vi.fn(), selectedFeature: null }; const { rerender } = renderHook(useMapInteractions, { initialProps: props }); expect(map.on).not.toHaveBeenCalled(); rerender({ ...props, enabled: true }); expect(map.on).toHaveBeenCalledWith("click", expect.any(Array), expect.any(Function)); rerender(props); expect(map.off).toHaveBeenCalledWith("click", expect.any(Array), expect.any(Function)); });