/** * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * Project: Groupe Ka / Ka Maps */ import { describe, expect, it } from "vitest"; import { bboxContains, bboxContainsPoint, bboxToString, expandBBox, hashId, haversineMeters, isValidCoordinate, parseBBox, propertiesToGeoJSON, } from "../src/utils/geo.js"; import type { MapProperty } from "../src/types/index.js"; const QC = { west: -71.4, south: 46.7, east: -71.1, north: 46.9 }; describe("isValidCoordinate", () => { it("accepts Quebec coordinates", () => { expect(isValidCoordinate(46.8139, -71.208)).toBe(true); }); it("rejects (0,0), NaN, strings and out-of-range", () => { expect(isValidCoordinate(0, 0)).toBe(false); expect(isValidCoordinate(Number.NaN, -71)).toBe(false); expect(isValidCoordinate("46" as unknown, -71)).toBe(false); expect(isValidCoordinate(95, -71)).toBe(false); expect(isValidCoordinate(46, -190)).toBe(false); }); }); describe("bbox round-trip", () => { it("serializes west,south,east,north and parses back", () => { const text = bboxToString(QC); expect(text).toBe("-71.4,46.7,-71.1,46.9"); expect(parseBBox(text)).toEqual(QC); }); it("rejects malformed strings", () => { expect(parseBBox("1,2,3")).toBeNull(); expect(parseBBox("a,b,c,d")).toBeNull(); expect(parseBBox("-71.1,46.9,-71.4,46.7")).toBeNull(); // inverted }); }); describe("bbox math", () => { it("expands symmetrically", () => { const e = expandBBox(QC, 0.5); expect(e.west).toBeCloseTo(-71.55); expect(e.east).toBeCloseTo(-70.95); expect(e.south).toBeCloseTo(46.6); expect(e.north).toBeCloseTo(47.0); }); it("contains inner boxes and points", () => { expect(bboxContains(expandBBox(QC, 0.2), QC)).toBe(true); expect(bboxContains(QC, expandBBox(QC, 0.2))).toBe(false); expect(bboxContainsPoint(QC, 46.8, -71.2)).toBe(true); expect(bboxContainsPoint(QC, 45.5, -73.6)).toBe(false); }); }); describe("haversineMeters", () => { it("measures Québec→Montréal at ~233 km", () => { const d = haversineMeters(46.8139, -71.208, 45.5019, -73.5674); expect(d).toBeGreaterThan(220_000); expect(d).toBeLessThan(245_000); }); }); describe("propertiesToGeoJSON", () => { const base: MapProperty = { id: "lou:1", appSource: "lou-ka", latitude: 46.81, longitude: -71.21, kind: "listing", listingType: "rent", price: 1450, }; it("builds point features with labelValue = price for listings", () => { const fc = propertiesToGeoJSON([base]); expect(fc.features).toHaveLength(1); const f = fc.features[0]!; expect(f.geometry.coordinates).toEqual([-71.21, 46.81]); expect(f.properties?.["labelValue"]).toBe(1450); expect(f.id).toBe(hashId("lou:1")); }); it("uses estimatedValue as labelValue for valuations", () => { const fc = propertiesToGeoJSON([ { ...base, id: "vp:9", appSource: "vrai-prix", kind: "valuation", price: undefined, estimatedValue: 512_000, }, ]); expect(fc.features[0]!.properties?.["labelValue"]).toBe(512_000); }); it("drops invalid coordinates silently", () => { const fc = propertiesToGeoJSON([ { ...base, id: "bad", latitude: 0, longitude: 0 }, base, ]); expect(fc.features).toHaveLength(1); }); });