TypeScript 87.7%
CSS 12.3%
1/**2 * Author: Simon-Pierre Boucher3 * Contact: contact@spboucher.ai4 * Project: Groupe Ka / Ka Maps5 */67import { describe, expect, it } from "vitest";8import {9 bboxContains,10 bboxContainsPoint,11 bboxToString,12 expandBBox,13 hashId,14 haversineMeters,15 isValidCoordinate,16 parseBBox,17 propertiesToGeoJSON,18} from "../src/utils/geo.js";19import type { MapProperty } from "../src/types/index.js";2021const QC = { west: -71.4, south: 46.7, east: -71.1, north: 46.9 };2223describe("isValidCoordinate", () => {24 it("accepts Quebec coordinates", () => {25 expect(isValidCoordinate(46.8139, -71.208)).toBe(true);26 });27 it("rejects (0,0), NaN, strings and out-of-range", () => {28 expect(isValidCoordinate(0, 0)).toBe(false);29 expect(isValidCoordinate(Number.NaN, -71)).toBe(false);30 expect(isValidCoordinate("46" as unknown, -71)).toBe(false);31 expect(isValidCoordinate(95, -71)).toBe(false);32 expect(isValidCoordinate(46, -190)).toBe(false);33 });34});3536describe("bbox round-trip", () => {37 it("serializes west,south,east,north and parses back", () => {38 const text = bboxToString(QC);39 expect(text).toBe("-71.4,46.7,-71.1,46.9");40 expect(parseBBox(text)).toEqual(QC);41 });42 it("rejects malformed strings", () => {43 expect(parseBBox("1,2,3")).toBeNull();44 expect(parseBBox("a,b,c,d")).toBeNull();45 expect(parseBBox("-71.1,46.9,-71.4,46.7")).toBeNull(); // inverted46 });47});4849describe("bbox math", () => {50 it("expands symmetrically", () => {51 const e = expandBBox(QC, 0.5);52 expect(e.west).toBeCloseTo(-71.55);53 expect(e.east).toBeCloseTo(-70.95);54 expect(e.south).toBeCloseTo(46.6);55 expect(e.north).toBeCloseTo(47.0);56 });57 it("contains inner boxes and points", () => {58 expect(bboxContains(expandBBox(QC, 0.2), QC)).toBe(true);59 expect(bboxContains(QC, expandBBox(QC, 0.2))).toBe(false);60 expect(bboxContainsPoint(QC, 46.8, -71.2)).toBe(true);61 expect(bboxContainsPoint(QC, 45.5, -73.6)).toBe(false);62 });63});6465describe("haversineMeters", () => {66 it("measures Québec→Montréal at ~233 km", () => {67 const d = haversineMeters(46.8139, -71.208, 45.5019, -73.5674);68 expect(d).toBeGreaterThan(220_000);69 expect(d).toBeLessThan(245_000);70 });71});7273describe("propertiesToGeoJSON", () => {74 const base: MapProperty = {75 id: "lou:1",76 appSource: "lou-ka",77 latitude: 46.81,78 longitude: -71.21,79 kind: "listing",80 listingType: "rent",81 price: 1450,82 };8384 it("builds point features with labelValue = price for listings", () => {85 const fc = propertiesToGeoJSON([base]);86 expect(fc.features).toHaveLength(1);87 const f = fc.features[0]!;88 expect(f.geometry.coordinates).toEqual([-71.21, 46.81]);89 expect(f.properties?.["labelValue"]).toBe(1450);90 expect(f.id).toBe(hashId("lou:1"));91 });9293 it("uses estimatedValue as labelValue for valuations", () => {94 const fc = propertiesToGeoJSON([95 {96 ...base,97 id: "vp:9",98 appSource: "vrai-prix",99 kind: "valuation",100 price: undefined,101 estimatedValue: 512_000,102 },103 ]);104 expect(fc.features[0]!.properties?.["labelValue"]).toBe(512_000);105 });106107 it("drops invalid coordinates silently", () => {108 const fc = propertiesToGeoJSON([109 { ...base, id: "bad", latitude: 0, longitude: 0 },110 base,111 ]);112 expect(fc.features).toHaveLength(1);113 });114});115