feat(GRO-2157): navigation export endpoints (Phase 2.3) (#190)
This commit was merged in pull request #190.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user