mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(roadtrip): re-pin a via to the leg it was dragged onto
Dragging a via somewhere else on the map changed where the point IS and never where it sits in the chain. The anchor that decides which two stops it is threaded between was worked out once, when the point was dropped, and the drag sent only coordinates — `roadtripViaUpdateRequestSchema` carried a lat and a lng and nothing else. So a via pulled past the stop it used to precede kept claiming the earlier leg. The router then drove out to it and turned back before carrying on, which on screen is not a subtly wrong route: it is a spur that reads as the drag having done nothing at all, with the original road still sitting there unchanged. The anchor is not a property of the point but of where the point sits along the drive, so it is recomputed on a drag the same way it is on a drop. The projection that did that was inline in addRoadtripVia; it moves to a shared `anchorFor` and both callers use it. A drag differs from a drop in two ways, both deliberate. It measures against its OWN day: letting the nearest day win, which is right for a click on open map, would hand the via to a neighbouring day whose road happens to pass closer and make it disappear from the day it was dragged in. And it has no 2 km guard: pulling a via well off the current road is exactly what the gesture is for, and the anchor only says which leg gets bent. `after_order_index` is optional on the way in, so a drag inside one leg and an older client still send just the coordinates, and the stored pin is then left alone rather than cleared.
This commit is contained in:
@@ -108,7 +108,7 @@ export class RoadtripController {
|
||||
@Body() body: RoadtripViaUpdateDto,
|
||||
): { via: RoadtripVia } {
|
||||
this.requireDay(dayId, tripId);
|
||||
const via = this.roadtrip.move(id, dayId, body.lat, body.lng);
|
||||
const via = this.roadtrip.move(id, dayId, body.lat, body.lng, body.after_order_index);
|
||||
if (!via) throw new HttpException({ error: 'Via not found' }, 404);
|
||||
return { via };
|
||||
}
|
||||
|
||||
@@ -154,14 +154,34 @@ export class RoadtripService {
|
||||
}
|
||||
|
||||
/** Moving a via is the whole edit; where it sits in the chain does not change. */
|
||||
move(id: string | number, dayId: string | number, lat: number, lng: number): RoadtripVia | null {
|
||||
move(
|
||||
id: string | number,
|
||||
dayId: string | number,
|
||||
lat: number,
|
||||
lng: number,
|
||||
afterOrderIndex?: number,
|
||||
): RoadtripVia | null {
|
||||
const existing = this.db.get<{ id: number }>(
|
||||
'SELECT id FROM roadtrip_vias WHERE id = ? AND day_id = ?',
|
||||
id,
|
||||
dayId,
|
||||
);
|
||||
if (!existing) return null;
|
||||
this.db.run('UPDATE roadtrip_vias SET lat = ?, lng = ? WHERE id = ?', lat, lng, id);
|
||||
// The anchor moves with the point when the caller worked out a new one. It is not a
|
||||
// property of the via but of where the via sits along the drive, so dragging one past
|
||||
// a stop changes which leg it belongs to — and leaving it behind is what made the
|
||||
// route run out to the point and back instead of bending through it.
|
||||
if (afterOrderIndex === undefined) {
|
||||
this.db.run('UPDATE roadtrip_vias SET lat = ?, lng = ? WHERE id = ?', lat, lng, id);
|
||||
} else {
|
||||
this.db.run(
|
||||
'UPDATE roadtrip_vias SET lat = ?, lng = ?, after_order_index = ? WHERE id = ?',
|
||||
lat,
|
||||
lng,
|
||||
afterOrderIndex,
|
||||
id,
|
||||
);
|
||||
}
|
||||
return this.byId(Number(id));
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,15 @@ describe('RoadtripController — writes', () => {
|
||||
it('ROADTRIP-CTL-012: moving a via reports where it went', () => {
|
||||
const s = svc();
|
||||
expect(new RoadtripController(s).update('7', '4', '5', { lat: 54, lng: 11 })).toEqual({ via: VIA });
|
||||
expect(s.move).toHaveBeenCalledWith('5', '4', 54, 11);
|
||||
// Undefined anchor, explicitly: a drag that stayed between the same two stops sends
|
||||
// no new one, and the service must leave the existing pin alone rather than clear it.
|
||||
expect(s.move).toHaveBeenCalledWith('5', '4', 54, 11, undefined);
|
||||
});
|
||||
|
||||
it('ROADTRIP-CTL-015: a new anchor is passed on, so a via dragged past a stop is re-pinned', () => {
|
||||
const s = svc();
|
||||
new RoadtripController(s).update('7', '4', '5', { lat: 54, lng: 11, after_order_index: 2 });
|
||||
expect(s.move).toHaveBeenCalledWith('5', '4', 54, 11, 2);
|
||||
});
|
||||
|
||||
it('ROADTRIP-CTL-013: a via that is not on this day is a 404, not a silent no-op', () => {
|
||||
|
||||
@@ -82,7 +82,10 @@ describe('RoadtripService', () => {
|
||||
expect(service.listForTrip(7).map(v => v.day_id)).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('ROADTRIP-SVC-005: moving a via changes where it is, not where it sits in the chain', () => {
|
||||
it('ROADTRIP-SVC-005: a move with no new anchor leaves the chain alone', () => {
|
||||
// The drag stayed between the same two stops, so there is nothing to re-pin. The
|
||||
// sequence is never touched by a move either way: it orders the vias that share one
|
||||
// anchor, and dragging one does not reorder its neighbours.
|
||||
const via = service.create(1, { after_order_index: 1, lat: 53, lng: 10 });
|
||||
|
||||
const moved = service.move(via.id, 1, 52.5, 11.5);
|
||||
@@ -90,6 +93,26 @@ describe('RoadtripService', () => {
|
||||
expect(moved).toMatchObject({ lat: 52.5, lng: 11.5, after_order_index: 1, sequence: via.sequence });
|
||||
});
|
||||
|
||||
it('ROADTRIP-SVC-007: a via dragged past a stop is re-pinned to the leg it landed on', () => {
|
||||
// The bug this exists for: a drag used to carry only the coordinates, so a via pulled
|
||||
// beyond the stop it used to precede kept claiming the earlier leg. The route then ran
|
||||
// out to the point and back before carrying on, which reads as the drag doing nothing.
|
||||
const via = service.create(1, { after_order_index: 0, lat: 53.87, lng: 10.7 });
|
||||
|
||||
const moved = service.move(via.id, 1, 53.87, 11.53, 1);
|
||||
|
||||
expect(moved).toMatchObject({ lat: 53.87, lng: 11.53, after_order_index: 1 });
|
||||
});
|
||||
|
||||
it('ROADTRIP-SVC-008: a re-pin through the wrong day changes nothing at all', () => {
|
||||
// The day check has to guard the anchor as well as the coordinates, or an id from
|
||||
// another day could be renumbered through a day the caller does happen to reach.
|
||||
const via = service.create(1, { after_order_index: 0, lat: 53, lng: 10 });
|
||||
|
||||
expect(service.move(via.id, 2, 51, 12, 3)).toBeNull();
|
||||
expect(service.listForDay(1)[0]).toMatchObject({ lat: 53, lng: 10, after_order_index: 0 });
|
||||
});
|
||||
|
||||
it('ROADTRIP-SVC-006: a via cannot be moved or removed through the wrong day', () => {
|
||||
const via = service.create(1, { after_order_index: 0, lat: 53, lng: 10 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user