/** * Unit tests for the #1746 OSM utility-infrastructure layer pure logic: kind * classification, geometry_geojson parsing (the three-geometry-type contract + * graceful-skip), and grouping. * * The layer renders Leaflet / (jsdom can't mount those * meaningfully), so we test the data-shaping that decides WHAT gets drawn and * HOW it's colored — that's where the bugs live (power-lines must NOT be * skipped; [lon,lat]→[lat,lat] swap; bad parse → crash or ocean geometry). */ import type { UtilityInfrastructureFeature } from "@/hooks/useUtilityInfrastructure"; import { classifyUtilityKind, groupUtilityByKind, parseUtilityGeometry, } from "../UtilityInfrastructureLayer"; function feat( partial: Partial, ): UtilityInfrastructureFeature { return { osm_id: 1, osm_type: "way", infrastructure_kind: "power", name: null, source_tag: null, distance_m: 0, geometry_geojson: {}, ...partial, }; } // geometry_geojson приходит уже распарсенным объектом (ST_AsGeoJSON на бэке). const POINT_GEOM = { type: "Point", coordinates: [60.6, 56.83] }; const LINESTRING_GEOM = { type: "LineString", coordinates: [ [60.6, 56.83], [60.61, 56.84], ], }; const POLYGON_GEOM = { type: "Polygon", coordinates: [ [ [60.6, 56.83], [60.61, 56.83], [60.61, 56.84], [60.6, 56.83], ], ], }; describe("classifyUtilityKind", () => { it("passes through known kinds", () => { expect(classifyUtilityKind("power")).toBe("power"); expect(classifyUtilityKind("water")).toBe("water"); expect(classifyUtilityKind("gas")).toBe("gas"); expect(classifyUtilityKind("heat")).toBe("heat"); expect(classifyUtilityKind("communication")).toBe("communication"); expect(classifyUtilityKind("sewage")).toBe("sewage"); }); it("maps unknown / null kind to 'other' (graceful)", () => { expect(classifyUtilityKind("telecom")).toBe("other"); expect(classifyUtilityKind(null)).toBe("other"); expect(classifyUtilityKind(undefined)).toBe("other"); }); }); describe("parseUtilityGeometry", () => { it("parses Point geometry (towers / nodes)", () => { expect(parseUtilityGeometry(POINT_GEOM)?.type).toBe("Point"); }); it("parses LineString geometry (power lines — the majority, must NOT skip)", () => { expect(parseUtilityGeometry(LINESTRING_GEOM)?.type).toBe("LineString"); }); it("parses Polygon geometry (substations)", () => { expect(parseUtilityGeometry(POLYGON_GEOM)?.type).toBe("Polygon"); }); it("parses MultiLineString / MultiPolygon geometry", () => { expect( parseUtilityGeometry({ type: "MultiLineString", coordinates: [LINESTRING_GEOM.coordinates], })?.type, ).toBe("MultiLineString"); expect( parseUtilityGeometry({ type: "MultiPolygon", coordinates: [POLYGON_GEOM.coordinates], })?.type, ).toBe("MultiPolygon"); }); it("tolerates a GeoJSON string (future contract change)", () => { expect(parseUtilityGeometry(JSON.stringify(POINT_GEOM))?.type).toBe( "Point", ); }); it("returns null for missing / empty / invalid input (graceful skip)", () => { expect(parseUtilityGeometry(null)).toBeNull(); expect(parseUtilityGeometry(undefined)).toBeNull(); expect(parseUtilityGeometry("")).toBeNull(); expect(parseUtilityGeometry("{not json")).toBeNull(); expect(parseUtilityGeometry({})).toBeNull(); expect(parseUtilityGeometry({ type: "Point" })).toBeNull(); expect( parseUtilityGeometry({ type: "GeometryCollection", geometries: [] }), ).toBeNull(); }); }); describe("groupUtilityByKind", () => { it("groups all three geometry types and keeps power lines", () => { const grouped = groupUtilityByKind([ feat({ infrastructure_kind: "power", geometry_geojson: LINESTRING_GEOM }), feat({ infrastructure_kind: "power", geometry_geojson: POINT_GEOM }), feat({ infrastructure_kind: "power", geometry_geojson: POLYGON_GEOM }), feat({ infrastructure_kind: "water", geometry_geojson: POINT_GEOM }), feat({ infrastructure_kind: "gas", geometry_geojson: LINESTRING_GEOM }), ]); expect(grouped.get("power")).toHaveLength(3); expect(grouped.get("water")).toHaveLength(1); expect(grouped.get("gas")).toHaveLength(1); }); it("drops features without valid geometry", () => { const grouped = groupUtilityByKind([ feat({ infrastructure_kind: "power", geometry_geojson: LINESTRING_GEOM }), feat({ infrastructure_kind: "power", geometry_geojson: {} }), ]); expect(grouped.get("power")).toHaveLength(1); }); it("buckets unknown kinds under 'other'", () => { const grouped = groupUtilityByKind([ feat({ infrastructure_kind: "telecom", geometry_geojson: POINT_GEOM }), ]); expect(grouped.get("other")).toHaveLength(1); }); it("returns empty buckets (not undefined) when features is empty", () => { const grouped = groupUtilityByKind([]); expect(grouped.get("power")).toEqual([]); expect(grouped.get("other")).toEqual([]); }); });