/** * Unit tests for the parcel-geometry narrowers in concept-api.ts. * * These guard the §7 «Размещение застройки» multi-part fix: * • `extractPolygon` (the /concepts REQUEST geometry) must collapse a * MultiPolygon to its LARGEST-area part, not `coordinates[0]` (which can be a * sliver), so the generated corpus sits on the dominant part. * • `extractParcelGeometry` (the SCENE geometry) must preserve EVERY part so the * outline / projection origin / OSM tile cover the whole parcel and stay * geo-aligned with backend-anchored neighbours. */ import type { MultiPolygon, Polygon, Position } from "geojson"; import { describe, expect, it } from "vitest"; import { extractParcelGeometry, extractPolygon } from "../concept-api"; // Part 0: a small sliver near the origin (≈55 m × 55 m). const SLIVER: Position[] = [ [60.6, 56.83], [60.6005, 56.83], [60.6005, 56.8305], [60.6, 56.8305], [60.6, 56.83], ]; // Part 1: a much larger square ~1 km east (the dominant part). const MAIN: Position[] = [ [60.616, 56.83], [60.62, 56.83], [60.62, 56.834], [60.616, 56.834], [60.616, 56.83], ]; const MULTI: MultiPolygon = { type: "MultiPolygon", coordinates: [[SLIVER], [MAIN]], }; describe("extractPolygon — /concepts request geometry", () => { it("returns a bare Polygon unchanged", () => { const poly: Polygon = { type: "Polygon", coordinates: [MAIN] }; expect(extractPolygon(poly)).toEqual(poly); }); it("unwraps a Feature wrapping a Polygon", () => { const result = extractPolygon({ type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [MAIN] }, }); expect(result).toEqual({ type: "Polygon", coordinates: [MAIN] }); }); it("collapses a MultiPolygon to its LARGEST part, not coordinates[0]", () => { const result = extractPolygon(MULTI); expect(result).not.toBeNull(); expect(result?.type).toBe("Polygon"); // Must pick MAIN (part 1), NOT the SLIVER at index 0. expect(result?.coordinates).toEqual([MAIN]); expect(result?.coordinates).not.toEqual([SLIVER]); }); it("returns null on a non-polygonal / malformed shape", () => { expect(extractPolygon(null)).toBeNull(); expect(extractPolygon({ type: "Point", coordinates: [1, 2] })).toBeNull(); expect(extractPolygon("nope")).toBeNull(); }); }); describe("extractParcelGeometry — whole scene geometry", () => { it("returns a bare Polygon unchanged", () => { const poly: Polygon = { type: "Polygon", coordinates: [MAIN] }; expect(extractParcelGeometry(poly)).toEqual(poly); }); it("preserves EVERY part of a MultiPolygon (both parts, not just part 0)", () => { const result = extractParcelGeometry(MULTI); expect(result).not.toBeNull(); expect(result?.type).toBe("MultiPolygon"); const mp = result as MultiPolygon; expect(mp.coordinates).toHaveLength(2); expect(mp.coordinates[0]).toEqual([SLIVER]); expect(mp.coordinates[1]).toEqual([MAIN]); }); it("unwraps a Feature wrapping a MultiPolygon", () => { const result = extractParcelGeometry({ type: "Feature", properties: {}, geometry: MULTI, }); expect(result?.type).toBe("MultiPolygon"); expect((result as MultiPolygon).coordinates).toHaveLength(2); }); it("returns null on a non-polygonal / malformed shape", () => { expect(extractParcelGeometry(null)).toBeNull(); expect( extractParcelGeometry({ type: "LineString", coordinates: [] }), ).toBeNull(); }); });