gendesign/frontend/src/components/site-finder/__tests__/ZouitLayer.test.ts
Light1YT 0b20d98b45
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 2m49s
Deploy / deploy (push) Successful in 1m6s
feat(site-finder): ЗОУИТ visual layer на Leaflet карте (#255)
ZouitLayer (GeoJSON-полигоны из geom_geojson, severity blocker/warning/info по keyword,
popup тип+имя) + ZouitLayerControlPanel (collapsible toggle per-severity, счётчик mappable)
+ SiteMap integration (default blocker+warning ON, info OFF, фон под POI). Graceful: нет
geom_geojson → слой/панель скрыты, текстовый NspdZouitOverlapsBlock остаётся. 10 vitest.

Closes #255
2026-06-13 17:41:08 +00:00

138 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Unit tests for the #255 ЗОУИТ-layer pure logic: severity classification,
* geom_geojson parsing (the graceful-skip contract), and grouping.
*
* The layer renders Leaflet <GeoJSON> (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>): 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([]);
});
});