SPB Git forge

spb/ka-maps

Public
6commits 1branches 0releases
448.0 KBsize
maindefault branch
29 days agolast push
TypeScript 87.7% CSS 12.3%
3.3 KB · 115 lines typescript
Raw Blame History
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