mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
refactor(map): the road trip via helpers move to utils, so the route hook imports no component
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { buildAssignment, buildPlace } from '../../../tests/helpers/factories';
|
||||
import type { AssignmentsMap, RouteSegment } from '../../types';
|
||||
import { dayViaLookup } from '../../utils/dayRouteVias';
|
||||
import { routeLeg } from './dayRouteLeg';
|
||||
|
||||
vi.mock('./RouteCalculator', async (importActual) => {
|
||||
const actual = await importActual<typeof import('./RouteCalculator')>();
|
||||
return { ...actual, calculateRouteWithLegs: vi.fn() };
|
||||
});
|
||||
|
||||
const { calculateRouteWithLegs } = await import('./RouteCalculator');
|
||||
|
||||
const leg = (distance: number): RouteSegment => ({
|
||||
mid: [0, 0],
|
||||
from: [distance, 0],
|
||||
to: [distance + 1, 0],
|
||||
distance,
|
||||
duration: distance / 10,
|
||||
distanceText: '',
|
||||
durationText: '',
|
||||
walkingText: '',
|
||||
drivingText: '',
|
||||
});
|
||||
|
||||
const via = (dayId: number, afterOrderIndex: number, lat: number, sequence = 0): RoadtripVia =>
|
||||
({
|
||||
id: lat,
|
||||
day_id: dayId,
|
||||
after_order_index: afterOrderIndex,
|
||||
sequence,
|
||||
lat,
|
||||
lng: 0,
|
||||
created_at: '',
|
||||
}) as RoadtripVia;
|
||||
|
||||
const stop = (id: number, order: number, extra: Record<string, unknown> = {}) =>
|
||||
buildAssignment({ id, day_id: 1, order_index: order, place: buildPlace({ lat: 48 + order, lng: 2 }), ...extra });
|
||||
|
||||
// Stored order: hotel row, Paris, a place without coordinates, Lille, Brussels.
|
||||
const STORED: AssignmentsMap = {
|
||||
'1': [
|
||||
stop(14, 3),
|
||||
stop(11, 1),
|
||||
stop(10, 0, { accommodation_id: 5 }),
|
||||
buildAssignment({
|
||||
id: 12,
|
||||
day_id: 1,
|
||||
order_index: 2,
|
||||
place: buildPlace({ lat: null as unknown as number, lng: null as unknown as number }),
|
||||
}),
|
||||
stop(13, 2.5),
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(calculateRouteWithLegs).mockReset();
|
||||
});
|
||||
|
||||
describe('dayRouteLeg (#2580)', () => {
|
||||
it('FE-MAP-DRV-009: one leg is routed through its vias and read back as one leg in its mode', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValue({
|
||||
coordinates: [],
|
||||
distance: 0,
|
||||
duration: 0,
|
||||
legs: [leg(1000), leg(2000)],
|
||||
});
|
||||
const lookup = dayViaLookup(STORED, { 1: [via(1, 1, 49.3)] });
|
||||
const options = { profile: 'driving', tripId: 7, dayId: 1 };
|
||||
|
||||
const seg = await routeLeg(
|
||||
{ lat: 49, lng: 2, assignmentId: 11 },
|
||||
{ lat: 50.5, lng: 2, assignmentId: 13 },
|
||||
lookup,
|
||||
options,
|
||||
'metric'
|
||||
);
|
||||
|
||||
expect(vi.mocked(calculateRouteWithLegs).mock.calls[0][0]).toEqual([
|
||||
{ lat: 49, lng: 2 },
|
||||
{ lat: 49.3, lng: 0 },
|
||||
{ lat: 50.5, lng: 2 },
|
||||
]);
|
||||
expect(seg?.distance).toBe(3000);
|
||||
expect(seg?.mode).toBe('driving');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-010: a leg the router cannot answer is undefined', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockRejectedValueOnce(new Error('down'));
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValueOnce({ coordinates: [], distance: 0, duration: 0, legs: [] });
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { DistanceUnit, RouteSegment } from '../../types';
|
||||
import { foldViaLegs, viasOnDayLeg, type DayViaLookup } from '../../utils/dayRouteVias';
|
||||
import { calculateRouteWithLegs } from './RouteCalculator';
|
||||
|
||||
/** One end of a leg: a planned stop carries its assignment, which the vias are filed against. */
|
||||
export type LegEnd = { lat: number; lng: number; assignmentId?: number };
|
||||
|
||||
/**
|
||||
* The drive between two points of a day as one leg, bent through the road trip's vias
|
||||
* between them and tagged with the mode it was routed in. Undefined when the router has
|
||||
* no road for it.
|
||||
*/
|
||||
export async function routeLeg(
|
||||
from: LegEnd,
|
||||
to: LegEnd,
|
||||
lookup: DayViaLookup | null,
|
||||
options: Parameters<typeof calculateRouteWithLegs>[1] & { profile: string; dayId: number },
|
||||
unit: DistanceUnit | undefined
|
||||
): Promise<RouteSegment | undefined> {
|
||||
const vias = viasOnDayLeg(lookup, options.dayId, options.profile, from, to);
|
||||
try {
|
||||
const waypoints = [from, ...vias, to].map((p) => ({ lat: p.lat, lng: p.lng }));
|
||||
const r = await calculateRouteWithLegs(waypoints, options);
|
||||
const leg = foldViaLegs(r.legs, [0, vias.length + 1], unit)[0];
|
||||
return leg ? { ...leg, mode: options.profile } : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { calculateRouteWithLegs, RoutingRefusedError, type RouteProfileKey } from './RouteCalculator'
|
||||
import { buildDayRouteRuns, type DayRouteInputs, type DayRoutePoint } from './dayRoutePlan'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viasOnDayLeg, type DayViaLookup } from './dayRouteVias'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viasOnDayLeg, type DayViaLookup } from '../../utils/dayRouteVias'
|
||||
import { projectDayItinerary } from './dayTourProjection'
|
||||
import { resolveLegMode } from '../Planner/legMode'
|
||||
import { dayColor } from '../Roadtrip/dayColors'
|
||||
|
||||
@@ -11,7 +11,8 @@ import { ChevronDown, ChevronRight, ChevronUp, Compass, RotateCcw, ExternalLink,
|
||||
import { type PickedPlace } from './TransitSearchPanel'
|
||||
import { buildTransitLeg, buildTransitNameIndex } from './transitLeg'
|
||||
import { optimizeRoute, generateGoogleMapsUrl, generateCoMapsUrl } from '../Map/RouteCalculator'
|
||||
import { dayViaLookup, routeLeg, type LegEnd, type ViasByDay } from '../Map/dayRouteVias'
|
||||
import { dayViaLookup, type ViasByDay } from '../../utils/dayRouteVias'
|
||||
import { routeLeg, type LegEnd } from '../Map/dayRouteLeg'
|
||||
import GoogleMapsIcon from '../shared/GoogleMapsIcon'
|
||||
import PlaceAvatar from '../shared/PlaceAvatar'
|
||||
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||
|
||||
@@ -14,7 +14,7 @@ import { dayWindow } from './dayWindow'
|
||||
import { useTranslation } from '../../i18n/TranslationContext'
|
||||
import { stayStartingOn } from '../../utils/dayMerge'
|
||||
import { staysAtTheirPlaces } from './nightBookend'
|
||||
import { viaKeyOf } from '../Map/dayRouteVias'
|
||||
import { viaKeyOf } from '../../utils/dayRouteVias'
|
||||
|
||||
export type { RoadtripStop, RoadtripDay, RoadtripRoutes, QuietDay, AccessSpur, RoutedLeg, SnappedPoint } from '@trek/shared/roadtrip'
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSettingsStore } from '../store/settingsStore'
|
||||
import { useAddonStore } from '../store/addonStore'
|
||||
import { calculateRouteWithLegs, type RouteProfileKey } from '../components/Map/RouteCalculator'
|
||||
import { buildDayRouteRuns, hotelBookendOf, TRANSPORT_TYPES } from '../components/Map/dayRoutePlan'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viaKeyOf, viasOnDayLeg } from '../components/Map/dayRouteVias'
|
||||
import { dayViaLookup, foldViaLegs, threadVias, viaKeyOf, viasOnDayLeg } from '../utils/dayRouteVias'
|
||||
import { resolveLegMode } from '../components/Planner/legMode'
|
||||
import type { RoadtripVia } from '@trek/shared'
|
||||
import type { TripStoreState } from '../store/tripStore'
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useRefuelSearch } from '../../components/Roadtrip/useRefuelSearch'
|
||||
import { useRoadtripCorridor } from '../../components/Roadtrip/useRoadtripCorridor'
|
||||
import { useRoadtripRoutes } from '../../components/Roadtrip/useRoadtripRoutes'
|
||||
import type { RoadtripVias } from '../../components/Roadtrip/useRoadtripVias'
|
||||
import { roadtripStopList } from '../../components/Map/dayRouteVias'
|
||||
import { roadtripStopList } from '../../utils/dayRouteVias'
|
||||
import { useLoadRoadtripSettings } from '../../hooks/useRoadtripSettings'
|
||||
import { roadtripPreferencesRepo } from '../../repo/roadtripPreferencesRepo'
|
||||
import { useAuthStore } from '../../store/authStore'
|
||||
|
||||
+4
-61
@@ -1,23 +1,8 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { buildAssignment, buildPlace } from '../../../tests/helpers/factories';
|
||||
import type { AssignmentsMap, RouteSegment } from '../../types';
|
||||
import {
|
||||
dayViaLookup,
|
||||
foldViaLegs,
|
||||
roadtripStopList,
|
||||
routeLeg,
|
||||
threadVias,
|
||||
viaKeyOf,
|
||||
viasOnDayLeg,
|
||||
} from './dayRouteVias';
|
||||
|
||||
vi.mock('./RouteCalculator', async (importActual) => {
|
||||
const actual = await importActual<typeof import('./RouteCalculator')>();
|
||||
return { ...actual, calculateRouteWithLegs: vi.fn() };
|
||||
});
|
||||
|
||||
const { calculateRouteWithLegs } = await import('./RouteCalculator');
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildAssignment, buildPlace } from '../../tests/helpers/factories';
|
||||
import type { AssignmentsMap, RouteSegment } from '../types';
|
||||
import { dayViaLookup, foldViaLegs, roadtripStopList, threadVias, viaKeyOf, viasOnDayLeg } from './dayRouteVias';
|
||||
|
||||
const leg = (distance: number): RouteSegment => ({
|
||||
mid: [0, 0],
|
||||
@@ -68,10 +53,6 @@ function lookupFor(viasByDay: Record<number, RoadtripVia[]>) {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(calculateRouteWithLegs).mockReset();
|
||||
});
|
||||
|
||||
describe('dayRouteVias (#2580)', () => {
|
||||
it('FE-MAP-DRV-001: the stop list is the stored order with coordinates, hidden rows included', () => {
|
||||
expect(roadtripStopList(STORED['1']).map((a) => a.id)).toEqual([10, 11, 13, 14]);
|
||||
@@ -144,42 +125,4 @@ describe('dayRouteVias (#2580)', () => {
|
||||
expect(viaKeyOf({ ...a, 1: [via(1, 0, 49.4)] })).not.toBe(viaKeyOf(a));
|
||||
expect(viaKeyOf(undefined)).toBe('');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-009: one leg is routed through its vias and read back as one leg in its mode', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValue({
|
||||
coordinates: [],
|
||||
distance: 0,
|
||||
duration: 0,
|
||||
legs: [leg(1000), leg(2000)],
|
||||
});
|
||||
const lookup = dayViaLookup(STORED, { 1: [via(1, 1, 49.3)] });
|
||||
const options = { profile: 'driving', tripId: 7, dayId: 1 };
|
||||
|
||||
const seg = await routeLeg(
|
||||
{ lat: 49, lng: 2, assignmentId: 11 },
|
||||
{ lat: 50.5, lng: 2, assignmentId: 13 },
|
||||
lookup,
|
||||
options,
|
||||
'metric'
|
||||
);
|
||||
|
||||
expect(vi.mocked(calculateRouteWithLegs).mock.calls[0][0]).toEqual([
|
||||
{ lat: 49, lng: 2 },
|
||||
{ lat: 49.3, lng: 0 },
|
||||
{ lat: 50.5, lng: 2 },
|
||||
]);
|
||||
expect(seg?.distance).toBe(3000);
|
||||
expect(seg?.mode).toBe('driving');
|
||||
});
|
||||
|
||||
it('FE-MAP-DRV-010: a leg the router cannot answer is undefined', async () => {
|
||||
vi.mocked(calculateRouteWithLegs).mockRejectedValueOnce(new Error('down'));
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
vi.mocked(calculateRouteWithLegs).mockResolvedValueOnce({ coordinates: [], distance: 0, duration: 0, legs: [] });
|
||||
expect(
|
||||
await routeLeg({ lat: 1, lng: 1 }, { lat: 2, lng: 2 }, null, { profile: 'driving', dayId: 1 }, 'metric')
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { RoadtripVia } from '@trek/shared';
|
||||
import { isCarrierMode, mergeRouteSegments, viasOnLeg } from '@trek/shared/roadtrip';
|
||||
import type { Assignment, AssignmentsMap, DistanceUnit, RouteSegment } from '../../types';
|
||||
import { calculateRouteWithLegs } from './RouteCalculator';
|
||||
import type { Assignment, AssignmentsMap, DistanceUnit, RouteSegment } from '../types';
|
||||
|
||||
/**
|
||||
* The stops of a day as the road trip counts them, in the order it drives them.
|
||||
@@ -116,29 +115,3 @@ export function foldViaLegs(legs: RouteSegment[], stopAt: number[], unit: Distan
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
/** One end of a leg: a planned stop carries its assignment, which the vias are filed against. */
|
||||
export type LegEnd = { lat: number; lng: number; assignmentId?: number };
|
||||
|
||||
/**
|
||||
* The drive between two points of a day as one leg, bent through the road trip's vias
|
||||
* between them and tagged with the mode it was routed in. Undefined when the router has
|
||||
* no road for it.
|
||||
*/
|
||||
export async function routeLeg(
|
||||
from: LegEnd,
|
||||
to: LegEnd,
|
||||
lookup: DayViaLookup | null,
|
||||
options: Parameters<typeof calculateRouteWithLegs>[1] & { profile: string; dayId: number },
|
||||
unit: DistanceUnit | undefined
|
||||
): Promise<RouteSegment | undefined> {
|
||||
const vias = viasOnDayLeg(lookup, options.dayId, options.profile, from, to);
|
||||
try {
|
||||
const waypoints = [from, ...vias, to].map((p) => ({ lat: p.lat, lng: p.lng }));
|
||||
const r = await calculateRouteWithLegs(waypoints, options);
|
||||
const leg = foldViaLegs(r.legs, [0, vias.length + 1], unit)[0];
|
||||
return leg ? { ...leg, mode: options.profile } : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user