From f3a39d9452b565d902f0d26cfdf1f9ef30ac9b1f Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 19 Oct 2026 22:15:22 +0200 Subject: [PATCH] fix(tours): keep the bike paints, classes and costing to their modules, and pin the profile column to the contract --- client/src/components/Tours/bike/bikePaint.ts | 2 +- client/src/components/Tours/bike/routeWays.ts | 6 +++--- client/src/components/Tours/bike/useRouteWays.ts | 2 +- .../src/components/Tours/planner/tourRouting.ts | 2 +- server/tests/unit/db/tour-bike-columns.test.ts | 16 ++++++++++++++++ 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 server/tests/unit/db/tour-bike-columns.test.ts diff --git a/client/src/components/Tours/bike/bikePaint.ts b/client/src/components/Tours/bike/bikePaint.ts index 0dde4d79b..1b9753218 100644 --- a/client/src/components/Tours/bike/bikePaint.ts +++ b/client/src/components/Tours/bike/bikePaint.ts @@ -7,7 +7,7 @@ import type { SurfaceClass, TrailClass } from './routeWays'; * own; the map reads them resolved (`useBikePaint`). A kind of way has no * colour of its own: it is pointed at in the accent. */ -export const BIKE_PAINT_TOKENS = { +const BIKE_PAINT_TOKENS = { paved: '--bike-surface-paved', rough: '--bike-surface-rough', compacted: '--bike-surface-compacted', diff --git a/client/src/components/Tours/bike/routeWays.ts b/client/src/components/Tours/bike/routeWays.ts index f5d997bd4..382848839 100644 --- a/client/src/components/Tours/bike/routeWays.ts +++ b/client/src/components/Tours/bike/routeWays.ts @@ -9,11 +9,11 @@ import { decodePolyline } from '../../Map/transitGeometry'; */ /** Surfaces from smooth to rough; `unknown` where the data names none. */ -export const SURFACE_CLASSES = ['paved', 'rough', 'compacted', 'gravel', 'ground', 'unknown'] as const; +const SURFACE_CLASSES = ['paved', 'rough', 'compacted', 'gravel', 'ground', 'unknown'] as const; export type SurfaceClass = (typeof SURFACE_CLASSES)[number]; /** Kinds of way, the ones a rider plans by. */ -export const WAY_CLASSES = ['cyclePath', 'cycleLane', 'quiet', 'road', 'track', 'path', 'ferry', 'other'] as const; +const WAY_CLASSES = ['cyclePath', 'cycleLane', 'quiet', 'road', 'track', 'path', 'ferry', 'other'] as const; export type WayClass = (typeof WAY_CLASSES)[number]; /** @@ -21,7 +21,7 @@ export type WayClass = (typeof WAY_CLASSES)[number]; * (rare, and all of them expert ground); `none` where OpenStreetMap gives the * way no grade, `unasked` where it could not be asked this time. */ -export const TRAIL_CLASSES = ['s0', 's1', 's2', 's3', 's4', 'none', 'unasked'] as const; +const TRAIL_CLASSES = ['s0', 's1', 's2', 's3', 's4', 'none', 'unasked'] as const; export type TrailClass = (typeof TRAIL_CLASSES)[number]; /** Surfaces a road bike's rider calls unpaved (BIK-02): what is no paving of any kind. */ diff --git a/client/src/components/Tours/bike/useRouteWays.ts b/client/src/components/Tours/bike/useRouteWays.ts index ffeb1cbaf..68333f058 100644 --- a/client/src/components/Tours/bike/useRouteWays.ts +++ b/client/src/components/Tours/bike/useRouteWays.ts @@ -10,7 +10,7 @@ import { routeWaysFrom, wayIdsOf, type RouteWays } from './routeWays'; /** How long a route has to stay before its ways are asked about, so a route being shaped asks once. */ const SETTLE_MS = 600; -export type RouteWaysStatus = 'idle' | 'loading' | 'ready' | 'failed' | 'offline'; +type RouteWaysStatus = 'idle' | 'loading' | 'ready' | 'failed' | 'offline'; export interface TrailGrades { status: 'loading' | 'ready' | 'failed' | 'offline'; diff --git a/client/src/components/Tours/planner/tourRouting.ts b/client/src/components/Tours/planner/tourRouting.ts index cf72e3887..a719a9c5b 100644 --- a/client/src/components/Tours/planner/tourRouting.ts +++ b/client/src/components/Tours/planner/tourRouting.ts @@ -77,7 +77,7 @@ export function hikingCostingOptions(preferences: HikingPreferences | null | und * `use_roads` and the bicycle type also set the router's riding speed, so the * time it gives is the profile's. None of it says a way is rideable or safe. */ -export const BIKE_COSTING: Record> = { +const BIKE_COSTING: Record> = { touring: { bicycle_type: 'Hybrid', use_roads: 0.2, avoid_bad_surfaces: 1 }, road: { bicycle_type: 'Road', use_roads: 0.5, avoid_bad_surfaces: 1 }, gravel: { bicycle_type: 'Cross', use_roads: 0.1, avoid_bad_surfaces: 0.25 }, diff --git a/server/tests/unit/db/tour-bike-columns.test.ts b/server/tests/unit/db/tour-bike-columns.test.ts new file mode 100644 index 000000000..022463253 --- /dev/null +++ b/server/tests/unit/db/tour-bike-columns.test.ts @@ -0,0 +1,16 @@ +import { ToursBikeProfile } from '../../../src/db/entities/Tours.entity'; +import { BIKE_PROFILES } from '@trek/shared'; + +import { describe, expect, it } from 'vitest'; + +/** + * BIK-01: the bike profile column holds a CHECK list (migration 045200) that + * the entity carries as its enum, and the contract holds its own list. A + * profile one side knows and the other refuses would be a tour the planner + * can save and the database cannot store. + */ +describe('bike profile column matches the contract', () => { + it('knows the same profiles as the bike contract', () => { + expect(Object.values(ToursBikeProfile).sort()).toEqual([...BIKE_PROFILES].sort()); + }); +});