From 178df0fbe3974cadf6b094e54c0d44f700a23c1e Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 4 Nov 2026 11:37:52 +0100 Subject: [PATCH] fix(map): keep the world-copy fix inside the CI ratchets Moves the Leaflet map's event bridges (click, context menu, viewport, POI drop and the longitude-frame follower) out of MapView into leafletMapBridges, and shares one POI drop target between the Leaflet and GL maps, so both map files shrink instead of growing. The repair migration does its arithmetic in one UPDATE per table, and the new tests drop their non-null assertions and loops. --- client/scripts/size-baseline.json | 4 +- client/src/components/Map/MapView.tsx | 120 +----------------- client/src/components/Map/MapViewGL.tsx | 33 +---- .../src/components/Map/leafletMapBridges.tsx | 120 ++++++++++++++++++ client/src/components/Map/poiDropTarget.ts | 39 ++++++ .../src/components/Map/transportHops.test.ts | 3 +- client/src/utils/lngFrame.test.ts | 5 +- ...01046350_a_place_lies_on_the_real_world.ts | 25 ++-- .../db/place-real-world-migration.test.ts | 2 +- .../repositories/longitude-on-write.test.ts | 32 +++-- 10 files changed, 205 insertions(+), 178 deletions(-) create mode 100644 client/src/components/Map/leafletMapBridges.tsx create mode 100644 client/src/components/Map/poiDropTarget.ts diff --git a/client/scripts/size-baseline.json b/client/scripts/size-baseline.json index c0a3b2a25..f6c481626 100644 --- a/client/scripts/size-baseline.json +++ b/client/scripts/size-baseline.json @@ -5,9 +5,9 @@ "src/components/Admin/AdminPluginsPanel.tsx": 1266, "src/components/Budget/CostsPanel.test.tsx": 2730, "src/components/Budget/CostsPanel.tsx": 1798, - "src/components/Map/MapView.tsx": 1385, + "src/components/Map/MapView.tsx": 1337, "src/components/Map/MapViewGL.test.tsx": 2919, - "src/components/Map/MapViewGL.tsx": 2218, + "src/components/Map/MapViewGL.tsx": 2198, "src/components/Planner/DayDetailPanel.test.tsx": 2333, "src/components/Planner/DayPlanSidebar.test.tsx": 5349, "src/components/Planner/DayPlanSidebar.tsx": 2929, diff --git a/client/src/components/Map/MapView.tsx b/client/src/components/Map/MapView.tsx index b1ef0ea0e..7cee1ab75 100644 --- a/client/src/components/Map/MapView.tsx +++ b/client/src/components/Map/MapView.tsx @@ -3,7 +3,7 @@ import DOM from 'react-dom' import { categoryIconSvg } from './categoryIconSvg' import { MapContainer, TileLayer, Marker, Polyline, CircleMarker, Circle, useMap, Tooltip } from 'react-leaflet' import MarkerClusterGroup from 'react-leaflet-cluster' -import { makeMarkerDraggable, makePoiDraggable, draggedPoiId } from './markerDrag' +import { makeMarkerDraggable, makePoiDraggable } from './markerDrag' import { CLUSTER_OPTIONS, createClusterIcon, revealInCluster, type ClusterGroupLike } from './markerCluster' import RoadtripViaMarkers from './RoadtripViaMarkers' import PlannerWaypointMarker from './PlannerWaypointMarker' @@ -50,7 +50,7 @@ import { useTransportRoutes } from '../../hooks/useTransportRoutes' import { visibleRouteReservations } from '../../utils/reservationRoutes' import { safeHexColor } from '../../utils/safeColor' import { placeMarkerLook } from './markerLook' -import { escapeHtml, wrapLongitude } from '@trek/shared' +import { escapeHtml } from '@trek/shared' import type { Day, Place, Reservation, RouteVia } from '../../types' import type { MapHoverInfo } from './mapHover' import { nightPauseMarker, NIGHT_PAUSE_MIN_ZOOM } from './nightPauseMarker' @@ -299,60 +299,6 @@ function CameraHoverGuard({ movingRef, onMoveStart, onZoom }: { movingRef: { cur return null } -/** - * Takes a corridor hit dropped anywhere on the map and reports where it landed. - * - * The container rather than the drawn route: a polyline is a real element here but not in - * the GL renderers, and the drop coordinate answers "where on the drive" just as well - * once the caller projects it onto the routed geometry — one behaviour for all three. - */ -function PoiDropTarget({ onPoiDropOnRoute }: { onPoiDropOnRoute?: (osmId: string, lat: number, lng: number) => void }) { - const map = useMap() - useEffect(() => { - if (!onPoiDropOnRoute) return - const container = map.getContainer() - const onDragOver = (e: DragEvent) => { - if (!draggedPoiId(e)) return - e.preventDefault() - if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy' - } - const onDrop = (e: DragEvent) => { - const osmId = draggedPoiId(e) - if (!osmId) return - e.preventDefault() - const rect = container.getBoundingClientRect() - const at = onRealWorld(map.containerPointToLatLng([e.clientX - rect.left, e.clientY - rect.top])) - onPoiDropOnRoute(osmId, at.lat, at.lng) - } - container.addEventListener('dragover', onDragOver) - container.addEventListener('drop', onDrop) - return () => { - container.removeEventListener('dragover', onDragOver) - container.removeEventListener('drop', onDrop) - } - }, [map, onPoiDropOnRoute]) - return null -} - -// Emits the current viewport bbox on pan/zoom so the POI-explore pill can fetch -// OSM places for the visible area, with the zoom an overlay's minimum is read against. -function ViewportController({ onViewportChange }: { onViewportChange?: MapViewProps['onViewportChange'] }) { - const map = useMap() - useEffect(() => { - if (!onViewportChange) return - const emit = () => { - const b = map.getBounds() - // A view of a world copy (or of a trip drawn past the antimeridian) asks for the real places there. - onViewportChange(bboxOnRealWorld({ south: b.getSouth(), west: b.getWest(), north: b.getNorth(), east: b.getEast() }), map.getZoom()) - } - map.whenReady(emit) // ensure the first bbox is captured once the map is laid out - map.on('moveend', emit) - map.on('zoomend', emit) - return () => { map.off('moveend', emit); map.off('zoomend', emit) } - }, [map, onViewportChange]) - return null -} - function MapCameraSnapshot({ onChange }: { onChange: (center: [number, number], zoom: number) => void }) { const map = useMap() useEffect(() => { @@ -412,28 +358,6 @@ interface MapControllerProps { zoom: number } -/** - * Moves the view along when the longitude frame changes under it (#2975): the trip - * is then drawn one world further east or west, and a view left where it was would - * look at an empty copy. One jump of a whole world, so nothing on screen changes. - */ -function LngFrameFollower() { - const map = useMap() - const frame = useLngFrame() - const prev = useRef(frame) - useEffect(() => { - const before = prev.current - prev.current = frame - if (before === frame || typeof map.getCenter !== 'function') return - const center = map.getCenter() - // Without a frame the trip is back on the real world, and so is the view. - const target = frame ? frameLng(frame, center.lng) : wrapLongitude(center.lng) - const shift = target - center.lng - if (shift !== 0) map.setView([center.lat, target], map.getZoom(), { animate: false }) - }, [frame, map]) - return null -} - function MapController({ center, zoom }: MapControllerProps) { const map = useMap() const frame = useLngFrame() @@ -563,10 +487,6 @@ function BoundsController({ places, routeCoords, fitKey, paddingOpts, framedOnMo return null } -interface MapClickHandlerProps { - onClick: ((e: L.LeafletMouseEvent) => void) | null -} - const TRACK_CASING_PANE = 'trek-track-casing' /** @@ -589,38 +509,6 @@ function TrackCasingPane({ onReady }: { onReady: (ready: boolean) => void }) { return null } -/** - * The event as the map reports it, with its point moved from a repeated world copy - * back onto the real world: a right-click on the copy east of it said 274.9 for - * -85.1, and the place was saved there (#2976). - */ -function onRealWorldEvent(e: L.LeafletMouseEvent): L.LeafletMouseEvent { - const lng = wrapLongitude(e.latlng.lng) - return lng === e.latlng.lng ? e : { ...e, latlng: L.latLng(e.latlng.lat, lng) } -} - -function MapClickHandler({ onClick }: MapClickHandlerProps) { - const map = useMap() - useEffect(() => { - if (!onClick) return - const handle = (e: L.LeafletMouseEvent) => onClick(onRealWorldEvent(e)) - map.on('click', handle) - return () => { map.off('click', handle) } - }, [map, onClick]) - return null -} - -function MapContextMenuHandler({ onContextMenu }: { onContextMenu: ((e: L.LeafletMouseEvent) => void) | null }) { - const map = useMap() - useEffect(() => { - if (!onContextMenu) return - const handle = (e: L.LeafletMouseEvent) => onContextMenu(onRealWorldEvent(e)) - map.on('contextmenu', handle) - return () => { map.off('contextmenu', handle) } - }, [map, onContextMenu]) - return null -} - // Travel times are shown in the day sidebar (per-segment connectors), not on the map. import { useMarkerThumbs } from './useMarkerThumbs' @@ -635,8 +523,8 @@ import { useIsPhone } from '../../mobile/useIsPhone' import type { GeoPosition, TrackingMode } from '../../hooks/useGeolocation' import { selectedPlaceTarget } from './selectedPlaceTarget' import { LngFrameProvider, useLngFrame, useTripLngFrame } from './lngFrameContext' -import { frameLine, frameLng, type LngFrame } from '../../utils/lngFrame' -import { bboxOnRealWorld, onRealWorld } from '../../utils/worldCopy' +import { frameLine, frameLng } from '../../utils/lngFrame' +import { LngFrameFollower, MapClickHandler, MapContextMenuHandler, PoiDropTarget, ViewportController } from './leafletMapBridges' function LeafletLocationLayer({ position: fix, mode }: { position: GeoPosition | null; mode: TrackingMode }) { const map = useMap() diff --git a/client/src/components/Map/MapViewGL.tsx b/client/src/components/Map/MapViewGL.tsx index 62eef119e..d5464a0dd 100644 --- a/client/src/components/Map/MapViewGL.tsx +++ b/client/src/components/Map/MapViewGL.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useMemo, useState, useCallback } from 'react' -import { makeMarkerDraggable, makePoiDraggable, draggedPoiId } from './markerDrag' +import { makeMarkerDraggable, makePoiDraggable } from './markerDrag' import { useStableVias } from './viaMarkerState' import { usePlannerWaypointMarkersGL } from './usePlannerWaypointMarkersGL' import { carryAssistantMarks, useAssistantMapGL } from './useAssistantMapGL' @@ -50,6 +50,7 @@ import { pluginsApi, type PluginMapMarker, type PluginMapLayer } from '../../api import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM } from '../../constants/mapDefaults' import { alongShortestArc, computeMapViewport, TILE_SIZE_GL } from '../../utils/mapViewport' import { onRealWorld } from '../../utils/worldCopy' +import { bindPoiDropTarget } from './poiDropTarget' import { selectedPlaceTarget } from './selectedPlaceTarget' import { PLACE_CLUSTER_CIRCLE_LAYER_ID, PLACE_CLUSTER_COUNT_LAYER_ID, PLACE_CLUSTER_SOURCE_ID, @@ -946,35 +947,12 @@ export function MapViewGL({ } }, [mapReady, onRouteClick]) - /** - * The map takes the drop, not the drawn route: in a GL renderer the line lives in the - * canvas and has no element to aim at. The coordinate under the pointer says just as - * much once it is projected onto the routed geometry, and it works the same in all - * three renderers. - */ + // A POI dropped on the map lands on the drive (see poiDropTarget.ts). useEffect(() => { const map = mapRef.current const container = map?.getContainer?.() if (!container || !mapReady || !onPoiDropOnRoute) return - const onDragOver = (e: DragEvent) => { - if (!draggedPoiId(e)) return - e.preventDefault() - if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy' - } - const onDrop = (e: DragEvent) => { - const osmId = draggedPoiId(e) - if (!osmId) return - e.preventDefault() - const rect = container.getBoundingClientRect() - const at = onRealWorld(map.unproject([e.clientX - rect.left, e.clientY - rect.top])) - onPoiDropOnRoute(osmId, at.lat, at.lng) - } - container.addEventListener('dragover', onDragOver) - container.addEventListener('drop', onDrop) - return () => { - container.removeEventListener('dragover', onDragOver) - container.removeEventListener('drop', onDrop) - } + return bindPoiDropTarget(container, (x, y) => map.unproject([x, y]), onPoiDropOnRoute) }, [mapReady, onPoiDropOnRoute]) const onViewportChangeRef = useRef(onViewportChange) onViewportChangeRef.current = onViewportChange @@ -1378,7 +1356,6 @@ export function MapViewGL({ // timer — dedupe so the form doesn't open twice. if (Date.now() - lastContextFire < 700) return false lastContextFire = Date.now() - // A right-click on a repeated world copy still means the place on the real world (#2976). onClickRefs.current.context?.({ latlng: onRealWorld({ lat: lngLat.lat, lng: lngLat.lng }), originalEvent }) return true } @@ -1985,8 +1962,6 @@ export function MapViewGL({ const fitPoints = routeCoords.length > 0 ? [...routeCoords, ...markerPoints] : markerPoints if (fitPoints.length === 0) return const bounds = new gl.LngLatBounds() - // The short way across the antimeridian, as the focus fit below does: a day - // from Los Angeles to Auckland frames the Pacific, not the rest of the world (#2975). alongShortestArc(fitPoints).forEach(([lat, lng]) => bounds.extend([lng, lat])) let fitted = false const run = () => { diff --git a/client/src/components/Map/leafletMapBridges.tsx b/client/src/components/Map/leafletMapBridges.tsx new file mode 100644 index 000000000..4bd32f4f3 --- /dev/null +++ b/client/src/components/Map/leafletMapBridges.tsx @@ -0,0 +1,120 @@ +// The Leaflet map's bridges to the page: the events it hands out, with their +// points on the real world (#2976), and the view kept on the world copy the +// trip is drawn on (#2975). + +import { wrapLongitude } from '@trek/shared'; +import L from 'leaflet'; +import { useEffect, useRef } from 'react'; +import { useMap } from 'react-leaflet'; +import { frameLng, type LngFrame } from '../../utils/lngFrame'; +import { bboxOnRealWorld } from '../../utils/worldCopy'; +import { useLngFrame } from './lngFrameContext'; +import type { MapViewProps } from './mapViewContract'; +import { bindPoiDropTarget } from './poiDropTarget'; + +/** + * Takes a corridor hit dropped anywhere on the map and reports where it landed. + * + * The container rather than the drawn route: a polyline is a real element here but not in + * the GL renderers, and the drop coordinate answers "where on the drive" just as well + * once the caller projects it onto the routed geometry — one behaviour for all three. + */ +export function PoiDropTarget({ + onPoiDropOnRoute, +}: { + onPoiDropOnRoute?: (osmId: string, lat: number, lng: number) => void; +}) { + const map = useMap(); + useEffect(() => { + if (!onPoiDropOnRoute) return; + return bindPoiDropTarget(map.getContainer(), (x, y) => map.containerPointToLatLng([x, y]), onPoiDropOnRoute); + }, [map, onPoiDropOnRoute]); + return null; +} + +// Emits the current viewport bbox on pan/zoom so the POI-explore pill can fetch +// OSM places for the visible area, with the zoom an overlay's minimum is read against. +export function ViewportController({ onViewportChange }: { onViewportChange?: MapViewProps['onViewportChange'] }) { + const map = useMap(); + useEffect(() => { + if (!onViewportChange) return; + const emit = () => { + const b = map.getBounds(); + // A view of a world copy (or of a trip drawn past the antimeridian) asks for the real places there. + onViewportChange( + bboxOnRealWorld({ south: b.getSouth(), west: b.getWest(), north: b.getNorth(), east: b.getEast() }), + map.getZoom() + ); + }; + map.whenReady(emit); // ensure the first bbox is captured once the map is laid out + map.on('moveend', emit); + map.on('zoomend', emit); + return () => { + map.off('moveend', emit); + map.off('zoomend', emit); + }; + }, [map, onViewportChange]); + return null; +} + +/** + * Moves the view along when the longitude frame changes under it (#2975): the trip + * is then drawn one world further east or west, and a view left where it was would + * look at an empty copy. One jump of a whole world, so nothing on screen changes. + */ +export function LngFrameFollower() { + const map = useMap(); + const frame = useLngFrame(); + const prev = useRef(frame); + useEffect(() => { + const before = prev.current; + prev.current = frame; + if (before === frame || typeof map.getCenter !== 'function') return; + const center = map.getCenter(); + // Without a frame the trip is back on the real world, and so is the view. + const target = frame ? frameLng(frame, center.lng) : wrapLongitude(center.lng); + const shift = target - center.lng; + if (shift !== 0) map.setView([center.lat, target], map.getZoom(), { animate: false }); + }, [frame, map]); + return null; +} + +interface MapClickHandlerProps { + onClick: ((e: L.LeafletMouseEvent) => void) | null; +} + +/** + * The event as the map reports it, with its point moved from a repeated world copy + * back onto the real world: a right-click on the copy east of it said 274.9 for + * -85.1, and the place was saved there (#2976). + */ +function onRealWorldEvent(e: L.LeafletMouseEvent): L.LeafletMouseEvent { + const lng = wrapLongitude(e.latlng.lng); + return lng === e.latlng.lng ? e : { ...e, latlng: L.latLng(e.latlng.lat, lng) }; +} + +export function MapClickHandler({ onClick }: MapClickHandlerProps) { + const map = useMap(); + useEffect(() => { + if (!onClick) return; + const handle = (e: L.LeafletMouseEvent) => onClick(onRealWorldEvent(e)); + map.on('click', handle); + return () => { + map.off('click', handle); + }; + }, [map, onClick]); + return null; +} + +export function MapContextMenuHandler({ onContextMenu }: { onContextMenu: ((e: L.LeafletMouseEvent) => void) | null }) { + const map = useMap(); + useEffect(() => { + if (!onContextMenu) return; + const handle = (e: L.LeafletMouseEvent) => onContextMenu(onRealWorldEvent(e)); + map.on('contextmenu', handle); + return () => { + map.off('contextmenu', handle); + }; + }, [map, onContextMenu]); + return null; +} diff --git a/client/src/components/Map/poiDropTarget.ts b/client/src/components/Map/poiDropTarget.ts new file mode 100644 index 000000000..84e923866 --- /dev/null +++ b/client/src/components/Map/poiDropTarget.ts @@ -0,0 +1,39 @@ +// A corridor hit dropped anywhere on the map, for every renderer. +// +// The map takes the drop, not the drawn route: in a GL renderer the line lives in the +// canvas and has no element to aim at, and the coordinate under the pointer says just +// as much once the caller projects it onto the routed geometry. The point goes out on +// the real world, whichever world copy it was dropped on (#2976). + +import { onRealWorld } from '../../utils/worldCopy'; +import { draggedPoiId } from './markerDrag'; + +/** Where a point of the container lies, in the renderer's own coordinates. */ +type PointAt = (x: number, y: number) => { lat: number; lng: number }; + +/** Listens for POI drops on `container` and reports each one; returns the cleanup. */ +export function bindPoiDropTarget( + container: HTMLElement, + pointAt: PointAt, + onPoiDropOnRoute: (osmId: string, lat: number, lng: number) => void +): () => void { + const onDragOver = (e: DragEvent) => { + if (!draggedPoiId(e)) return; + e.preventDefault(); + if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; + }; + const onDrop = (e: DragEvent) => { + const osmId = draggedPoiId(e); + if (!osmId) return; + e.preventDefault(); + const rect = container.getBoundingClientRect(); + const at = onRealWorld(pointAt(e.clientX - rect.left, e.clientY - rect.top)); + onPoiDropOnRoute(osmId, at.lat, at.lng); + }; + container.addEventListener('dragover', onDragOver); + container.addEventListener('drop', onDrop); + return () => { + container.removeEventListener('dragover', onDragOver); + container.removeEventListener('drop', onDrop); + }; +} diff --git a/client/src/components/Map/transportHops.test.ts b/client/src/components/Map/transportHops.test.ts index f302f818e..1a0dbabf2 100644 --- a/client/src/components/Map/transportHops.test.ts +++ b/client/src/components/Map/transportHops.test.ts @@ -93,7 +93,8 @@ describe('transportHop', () => { endpoint({ role: 'from', lat: 33.94, lng: -118.41 }), endpoint({ role: 'to', sequence: 1, lat: -37.01, lng: 174.79 }), ]); - const hop = transportHop(r, true, { seam: 28 })!; + const hop = transportHop(r, true, { seam: 28 }); + if (!hop) throw new Error('a flight with two located ends draws a hop'); expect(hop.from.lng).toBeCloseTo(241.59, 5); expect(hop.to.lng).toBeCloseTo(174.79, 5); expect(hop.arcs).toHaveLength(1); diff --git a/client/src/utils/lngFrame.test.ts b/client/src/utils/lngFrame.test.ts index 779b23ef2..205a6a5ce 100644 --- a/client/src/utils/lngFrame.test.ts +++ b/client/src/utils/lngFrame.test.ts @@ -15,7 +15,7 @@ describe('tripLngFrame (#2975)', () => { it('seams a trip across the Pacific in the middle of the stretch it leaves empty', () => { const frame = tripLngFrame([LA, -118.3, AUCKLAND, 174.76]); expect(frame).not.toBeNull(); - expect(frame!.seam).toBeCloseTo((-118.3 + 174.76) / 2, 5); + expect(frame?.seam).toBeCloseTo((-118.3 + 174.76) / 2, 5); expect(frameLng(frame, LA)).toBeCloseTo(LA + 360, 9); expect(frameLng(frame, AUCKLAND)).toBeCloseTo(AUCKLAND, 9); }); @@ -36,7 +36,8 @@ describe('tripLngFrame (#2975)', () => { }); it('reads a longitude from a world copy where it really is', () => { - expect(tripLngFrame([LA + 360, AUCKLAND - 720])!.seam).toBeCloseTo(tripLngFrame([LA, AUCKLAND])!.seam, 9); + const fromCopies = tripLngFrame([LA + 360, AUCKLAND - 720]); + expect(fromCopies?.seam).toBeCloseTo(tripLngFrame([LA, AUCKLAND])?.seam ?? Number.NaN, 9); }); }); diff --git a/server/src/db/migrations/Migration20200101046350_a_place_lies_on_the_real_world.ts b/server/src/db/migrations/Migration20200101046350_a_place_lies_on_the_real_world.ts index 7cf77c34d..21bcd95b0 100644 --- a/server/src/db/migrations/Migration20200101046350_a_place_lies_on_the_real_world.ts +++ b/server/src/db/migrations/Migration20200101046350_a_place_lies_on_the_real_world.ts @@ -6,16 +6,12 @@ import { Migration } from '@mikro-orm/migrations'; */ const TABLES = ['places', 'reservation_endpoints', 'collection_places', 'place_contrib_outbox'] as const; -interface LngRow { - id: number; - lng: number; -} - -/** Into [-180, 180], the same arithmetic as wrapLongitude in @trek/shared, kept here so this body never changes. */ -function onTheRealWorld(lng: number): number { - const wrapped = ((((lng + 180) % 360) + 360) % 360) - 180; - return wrapped === 0 ? 0 : wrapped; -} +/** + * How many whole worlds `lng` lies east of the real one: floor((lng + 180) / 360), + * spelled with CAST, which truncates towards zero, because SQLite has no floor() + * without its math extension. + */ +const WORLDS = '(CAST((lng + 180) / 360.0 AS INTEGER) - ((lng + 180) / 360.0 < CAST((lng + 180) / 360.0 AS INTEGER)))'; /** * Brings every stored longitude back onto the real world. @@ -30,14 +26,9 @@ function onTheRealWorld(lng: number): number { export class Migration20200101046350_a_place_lies_on_the_real_world extends Migration { override name = 'Migration20200101046350_a_place_lies_on_the_real_world'; - override async up(): Promise { + override up(): void { for (const table of TABLES) { - const rows = (await this.execute( - `SELECT id, lng FROM ${table} WHERE lng IS NOT NULL AND (lng < -180 OR lng > 180)`, - )) as LngRow[]; - for (const row of rows) { - await this.execute(`UPDATE ${table} SET lng = ? WHERE id = ?`, [onTheRealWorld(row.lng), row.id]); - } + this.addSql(`UPDATE ${table} SET lng = lng - 360 * ${WORLDS} WHERE lng < -180 OR lng > 180`); } } } diff --git a/server/tests/unit/db/place-real-world-migration.test.ts b/server/tests/unit/db/place-real-world-migration.test.ts index 8237986bf..9965f7c16 100644 --- a/server/tests/unit/db/place-real-world-migration.test.ts +++ b/server/tests/unit/db/place-real-world-migration.test.ts @@ -60,7 +60,7 @@ async function seed(orm: MikroORM): Promise { await rawExec( orm, `INSERT INTO place_contrib_outbox (id, contrib_key, field, value, place_id, place_name, lat, lng, cid, eligible_at, next_attempt_at) - VALUES (1, 'k1', 'name', 'x', 1, 'right-hand copy', 10.14, 274.921875, 'c1', '2026-01-01', '2026-01-01')`, + VALUES (1, 'k1', 'website', 'x', 1, 'right-hand copy', 10.14, 274.921875, 'c1', '2026-01-01', '2026-01-01')`, ); } diff --git a/server/tests/unit/db/repositories/longitude-on-write.test.ts b/server/tests/unit/db/repositories/longitude-on-write.test.ts index 52c883d70..9a1ec4416 100644 --- a/server/tests/unit/db/repositories/longitude-on-write.test.ts +++ b/server/tests/unit/db/repositories/longitude-on-write.test.ts @@ -108,16 +108,28 @@ describe('longitude on write', () => { it('LNGWRITE-002: a place on the real world, the edges included, is written as it came', async () => { const { user } = createUser(testDb); const trip = createTrip(testDb, user.id); - for (const lng of [-118.4, 180, -180]) { - const id = await places.insertTourPlace({ - trip_id: trip.id, - name: `At ${lng}`, - lat: 0, - lng, - route_geometry: null, - }); - expect(await storedLng(id)).toBe(lng); - } + const west = await places.insertTourPlace({ + trip_id: trip.id, + name: 'West', + lat: 0, + lng: -118.4, + route_geometry: null, + }); + const edge = await places.insertTourPlace({ + trip_id: trip.id, + name: 'Edge', + lat: 0, + lng: 180, + route_geometry: null, + }); + const otherEdge = await places.insertTourPlace({ + trip_id: trip.id, + name: 'Other edge', + lat: 0, + lng: -180, + route_geometry: null, + }); + expect([await storedLng(west), await storedLng(edge), await storedLng(otherEdge)]).toEqual([-118.4, 180, -180]); }); it('LNGWRITE-003: tour places and their route rewrites wrap too', async () => {