The routeData Y.Map's ~15 string keys (geojson, coordinates, segmentBoundaries, road metadata, profile, colorMode, baseLayer, overlays, poiCategories) were read and written raw at ~30 call sites, each with its own JSON parsing and casts; parseJsonArray existed twice and waypoint extraction four times. GPX assembly was duplicated between SaveToJournalButton and ExportButton, so the saved plan and the exported file could silently diverge. - new lib/route-data.ts owns the routeData (+ noGoAreas) schema: typed read/write, JSON encoding internal, ColorMode moves here (re-exported from ColoredRoute for existing importers) - new lib/gpx-export.ts owns GPX assembly: buildRouteGpx / buildPlanGpx / buildDayGpxFiles / hasDayBreaks; multi-day splitting becomes a pure, tested function - waypoint-ymap.ts gains extractWaypoints / extractWaypointData; the four hand-rolled copies (use-routing, use-waypoint-manager, WaypointSidebar, use-days) now share it, and WaypointSidebar's moveWaypoint reuses the round-trip helpers instead of re-listing every waypoint field - all hooks/components consume the seam; no raw routeData key strings remain outside route-data.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
2 KiB
TypeScript
77 lines
2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import * as Y from "yjs";
|
|
import type { Waypoint } from "@trails-cool/types";
|
|
import {
|
|
extractWaypointData,
|
|
extractWaypoints,
|
|
waypointFromYMap,
|
|
waypointToYMap,
|
|
} from "./waypoint-ymap.ts";
|
|
|
|
function createArray(): Y.Array<Y.Map<unknown>> {
|
|
return new Y.Doc().getArray<Y.Map<unknown>>("waypoints");
|
|
}
|
|
|
|
/** Y.Maps only expose values once integrated into a document. */
|
|
function roundTrip(wp: Waypoint): Waypoint {
|
|
const arr = createArray();
|
|
arr.push([waypointToYMap(wp)]);
|
|
return waypointFromYMap(arr.get(0)!);
|
|
}
|
|
|
|
describe("waypoint round-trip", () => {
|
|
it("preserves all fields through toYMap → fromYMap", () => {
|
|
const wp: Waypoint = {
|
|
lat: 52.5,
|
|
lon: 13.4,
|
|
name: "Hut",
|
|
note: "Book ahead",
|
|
isDayBreak: true,
|
|
osmId: 12345,
|
|
poiTags: { tourism: "alpine_hut" },
|
|
};
|
|
expect(roundTrip(wp)).toEqual(wp);
|
|
});
|
|
|
|
it("leaves optional fields undefined", () => {
|
|
const wp = roundTrip({ lat: 1, lon: 2 });
|
|
expect(wp).toEqual({
|
|
lat: 1,
|
|
lon: 2,
|
|
name: undefined,
|
|
note: undefined,
|
|
isDayBreak: undefined,
|
|
osmId: undefined,
|
|
poiTags: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("extractWaypoints", () => {
|
|
it("reads the whole list in order", () => {
|
|
const arr = createArray();
|
|
arr.push([
|
|
waypointToYMap({ lat: 1, lon: 2, name: "A" }),
|
|
waypointToYMap({ lat: 3, lon: 4, isDayBreak: true }),
|
|
]);
|
|
|
|
const wps = extractWaypoints(arr);
|
|
expect(wps).toHaveLength(2);
|
|
expect(wps[0]!.name).toBe("A");
|
|
expect(wps[1]!.isDayBreak).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("extractWaypointData", () => {
|
|
it("flattens isDayBreak to a plain overnight boolean", () => {
|
|
const arr = createArray();
|
|
arr.push([
|
|
waypointToYMap({ lat: 1, lon: 2 }),
|
|
waypointToYMap({ lat: 3, lon: 4, isDayBreak: true, note: "n" }),
|
|
]);
|
|
|
|
const data = extractWaypointData(arr);
|
|
expect(data[0]!.overnight).toBe(false);
|
|
expect(data[1]!).toEqual({ lat: 3, lon: 4, name: undefined, note: "n", overnight: true });
|
|
});
|
|
});
|