/** * Unit tests for the #255 ЗОУИТ-layer pure logic: severity classification, * geom_geojson parsing (the graceful-skip contract), and grouping. * * The layer renders Leaflet (jsdom can't mount that meaningfully), so * we test the data-shaping that decides WHAT gets drawn and HOW it's colored — * that's where the bugs live (wrong severity → wrong color; bad parse → ocean * polygon or crash). */ import type { NspdZouitOverlap } from "@/types/nspd"; import { classifyZouit, groupZouitBySeverity, parseZouitGeometry, } from "../ZouitLayer"; function overlap(partial: Partial): NspdZouitOverlap { return { group_key: "cad_zouit", layer: "", subcategory: null, name: null, ...partial, }; } // A minimal valid GeoJSON Polygon string (как ST_AsGeoJSON отдаёт). const POLYGON_STR = JSON.stringify({ type: "Polygon", coordinates: [ [ [60.6, 56.83], [60.61, 56.83], [60.61, 56.84], [60.6, 56.83], ], ], }); describe("classifyZouit", () => { it("classifies pipeline / electricity / gas zones as blocker", () => { expect( classifyZouit(overlap({ type_zone: "Охранная зона трубопровода" })), ).toBe("blocker"); expect( classifyZouit( overlap({ type_zone: "Охранная зона электросетевого хозяйства" }), ), ).toBe("blocker"); expect( classifyZouit(overlap({ type_zone: "Охранная зона газопровода" })), ).toBe("blocker"); }); it("classifies СЗЗ and heat-network zones as warning", () => { expect( classifyZouit( overlap({ type_zone: "Санитарно-защитная зона предприятия" }), ), ).toBe("warning"); expect( classifyZouit(overlap({ type_zone: "Охранная зона тепловой сети" })), ).toBe("warning"); }); it("classifies servitude / unknown zones as info", () => { expect( classifyZouit( overlap({ type_zone: "Публичный сервитут в целях размещения сети" }), ), ).toBe("info"); expect(classifyZouit(overlap({ type_zone: null, name: null }))).toBe( "info", ); }); it("falls back to name when type_zone is absent (nspd-dump path)", () => { expect( classifyZouit(overlap({ name: "Охранная зона трубопровода с кад. №…" })), ).toBe("blocker"); }); }); describe("parseZouitGeometry", () => { it("parses a valid GeoJSON Polygon string", () => { const geom = parseZouitGeometry(POLYGON_STR); expect(geom?.type).toBe("Polygon"); }); it("returns null for missing / empty / invalid input (graceful skip)", () => { expect(parseZouitGeometry(null)).toBeNull(); expect(parseZouitGeometry(undefined)).toBeNull(); expect(parseZouitGeometry("")).toBeNull(); expect(parseZouitGeometry("{not json")).toBeNull(); }); it("returns null for non-polygon geometry (Point/LineString not drawn)", () => { expect( parseZouitGeometry( JSON.stringify({ type: "Point", coordinates: [60.6, 56.83] }), ), ).toBeNull(); }); it("accepts an already-parsed object (contract tolerance)", () => { // Контракт — строка (ST_AsGeoJSON), но parseZouitGeometry терпит и объект на // случай смены контракта. Прогоняем object-путь через unknown-cast. const obj = JSON.parse(POLYGON_STR) as unknown; expect(parseZouitGeometry(obj as string)?.type).toBe("Polygon"); }); }); describe("groupZouitBySeverity", () => { it("groups only mappable overlaps and drops geometry-less ones", () => { const grouped = groupZouitBySeverity([ overlap({ type_zone: "Охранная зона газопровода", geom_geojson: POLYGON_STR, }), overlap({ type_zone: "Охранная зона тепловой сети", geom_geojson: POLYGON_STR, }), // Без geom_geojson — в карту не идёт. overlap({ type_zone: "Публичный сервитут", geom_geojson: null }), ]); expect(grouped.get("blocker")).toHaveLength(1); expect(grouped.get("warning")).toHaveLength(1); expect(grouped.get("info")).toHaveLength(0); }); it("returns empty buckets (not undefined) when overlaps is empty", () => { const grouped = groupZouitBySeverity([]); expect(grouped.get("blocker")).toEqual([]); expect(grouped.get("warning")).toEqual([]); expect(grouped.get("info")).toEqual([]); }); });