e9ad92de01
feat: nav export + conflict guard + UAT seed (GRO-2157, GRO-2225, GRO-2235)
Squash-merges PR #192: uat→main PROD promotion.
Freezes at validated SHA 4868f18 (UAT regression GRO-2261 11/11 PASS).
Bundles: GRO-2157 (nav export), GRO-2225 (UAT seed), GRO-2235 (conflict guard).
CTO-reviewed and approved (review #4542).
Co-authored-by: Flea Flicker <22+gb_flea@noreply.git.farh.net>
Co-committed-by: Flea Flicker <22+gb_flea@noreply.git.farh.net>
141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
buildGoogleMapsUrl,
|
|
buildAppleMapsUrl,
|
|
buildNavigationUrl,
|
|
intermediateWaypointCount,
|
|
GOOGLE_MAPS_MAX_WAYPOINTS,
|
|
APPLE_MAPS_MAX_WAYPOINTS,
|
|
type NavigationStop,
|
|
} from "../services/navigationExport.js";
|
|
|
|
function stops(n: number): NavigationStop[] {
|
|
return Array.from({ length: n }, (_, i) => ({
|
|
latitude: 47 + i / 100,
|
|
longitude: -122 - i / 100,
|
|
label: `Stop ${i + 1}`,
|
|
}));
|
|
}
|
|
|
|
describe("intermediateWaypointCount", () => {
|
|
it("excludes origin and destination", () => {
|
|
expect(intermediateWaypointCount(0)).toBe(0);
|
|
expect(intermediateWaypointCount(1)).toBe(0);
|
|
expect(intermediateWaypointCount(2)).toBe(0);
|
|
expect(intermediateWaypointCount(5)).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("buildGoogleMapsUrl", () => {
|
|
it("rejects an empty route", () => {
|
|
const r = buildGoogleMapsUrl([]);
|
|
expect(r).toEqual({ error: "route has no stops to export", status: 400 });
|
|
});
|
|
|
|
it("builds a single-stop link (destination only, no waypoints)", () => {
|
|
const r = buildGoogleMapsUrl(stops(1));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.platform).toBe("google-maps");
|
|
expect(r.stopCount).toBe(1);
|
|
expect(r.waypointCount).toBe(0);
|
|
expect(r.url).toContain("https://www.google.com/maps/dir/?");
|
|
expect(r.url).toContain("api=1");
|
|
expect(r.url).toContain("travelmode=driving");
|
|
expect(r.url).toContain("origin=47%2C-122");
|
|
expect(r.url).toContain("destination=47%2C-122");
|
|
expect(r.url).not.toContain("waypoints=");
|
|
});
|
|
|
|
it("builds origin/destination only for two stops", () => {
|
|
const r = buildGoogleMapsUrl(stops(2));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.waypointCount).toBe(0);
|
|
expect(r.url).not.toContain("waypoints=");
|
|
expect(r.url).toContain("origin=47%2C-122");
|
|
expect(r.url).toContain("destination=47.01%2C-122.01");
|
|
});
|
|
|
|
it("includes intermediate waypoints in order, pipe-separated", () => {
|
|
const r = buildGoogleMapsUrl(stops(4));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.stopCount).toBe(4);
|
|
expect(r.waypointCount).toBe(2);
|
|
// waypoints param holds stops[1] and stops[2], pipe-joined (encoded %7C)
|
|
const url = new URL(r.url);
|
|
expect(url.searchParams.get("origin")).toBe("47,-122");
|
|
expect(url.searchParams.get("destination")).toBe("47.03,-122.03");
|
|
expect(url.searchParams.get("waypoints")).toBe(
|
|
"47.01,-122.01|47.02,-122.02"
|
|
);
|
|
});
|
|
|
|
it("accepts a route at exactly the waypoint limit", () => {
|
|
const r = buildGoogleMapsUrl(stops(GOOGLE_MAPS_MAX_WAYPOINTS + 2));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.waypointCount).toBe(GOOGLE_MAPS_MAX_WAYPOINTS);
|
|
});
|
|
|
|
it("rejects a route over the waypoint limit", () => {
|
|
const r = buildGoogleMapsUrl(stops(GOOGLE_MAPS_MAX_WAYPOINTS + 3));
|
|
expect("error" in r).toBe(true);
|
|
if ("error" in r) {
|
|
expect(r.status).toBe(400);
|
|
expect(r.error).toContain(`${GOOGLE_MAPS_MAX_WAYPOINTS}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("buildAppleMapsUrl", () => {
|
|
it("rejects an empty route", () => {
|
|
const r = buildAppleMapsUrl([]);
|
|
expect(r).toEqual({ error: "route has no stops to export", status: 400 });
|
|
});
|
|
|
|
it("builds a destination-only link for one stop", () => {
|
|
const r = buildAppleMapsUrl(stops(1));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.platform).toBe("apple-maps");
|
|
expect(r.url).toBe("maps://?daddr=47,-122&dirflg=d");
|
|
expect(r.url).not.toContain("saddr=");
|
|
});
|
|
|
|
it("chains destinations with +to: for multiple stops", () => {
|
|
const r = buildAppleMapsUrl(stops(3));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.stopCount).toBe(3);
|
|
expect(r.waypointCount).toBe(1);
|
|
expect(r.url).toBe(
|
|
"maps://?saddr=47,-122&daddr=47.01,-122.01+to:47.02,-122.02&dirflg=d"
|
|
);
|
|
});
|
|
|
|
it("accepts a route at exactly the waypoint limit", () => {
|
|
const r = buildAppleMapsUrl(stops(APPLE_MAPS_MAX_WAYPOINTS + 2));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.waypointCount).toBe(APPLE_MAPS_MAX_WAYPOINTS);
|
|
});
|
|
|
|
it("rejects a route over the waypoint limit", () => {
|
|
const r = buildAppleMapsUrl(stops(APPLE_MAPS_MAX_WAYPOINTS + 3));
|
|
expect("error" in r).toBe(true);
|
|
if ("error" in r) {
|
|
expect(r.status).toBe(400);
|
|
expect(r.error).toContain(`${APPLE_MAPS_MAX_WAYPOINTS}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("buildNavigationUrl", () => {
|
|
it("dispatches to the google-maps builder", () => {
|
|
const r = buildNavigationUrl("google-maps", stops(2));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.platform).toBe("google-maps");
|
|
});
|
|
|
|
it("dispatches to the apple-maps builder", () => {
|
|
const r = buildNavigationUrl("apple-maps", stops(2));
|
|
if ("error" in r) throw new Error(r.error);
|
|
expect(r.platform).toBe("apple-maps");
|
|
});
|
|
});
|