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"); }); });