mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
57ea219a43
This commit completes the reviewed Tours follow-up scope after the Tours MVP was merged into dev. Included feature blocks: - RC-01: identify Tours in PLAN day rows and show compact Tour facts; - RC-06: search for a city or address in TOUR-PLANNER without hidden writes; - RC-07: reorder Tour waypoints in the left sidebar; - RC-08: drag Tour waypoints on Leaflet, MapLibre and Mapbox with stable viewport, history and routing behavior; - RS-01: persist Tour descriptions and HTTPS information links with local provider classification; - RS-02: persist breaks/additional time and calculated or manually adjusted planned Tour duration without changing scheduling semantics. The implementation preserves: - ordinary Place and Legacy Track behavior; - Tours-off fallback behavior; - existing GPX/import behavior; - Tour assignment and deletion behavior; - calculated routing duration as a separate value; - existing Tour provenance fields; - keyboard/list alternatives for waypoint editing. Not included: - PR1-09 trip-wide Tour summary; - Wanderer integration; - GPX import changes; - new Tour types; - Tour-to-Day drag-and-drop; - further roadmap or Post-Release features. Validation: - focused RC-01/RC-06/RC-07/RC-08/RS-01/RS-02 tests passed; - affected shared/server/client typechecks passed; - migration and generated DB type checks passed; - scoped ESLint passed; - staged diff checks passed; - owner manual tests passed for all six feature blocks.
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { wirePlannerWaypointDrag } from './plannerWaypointDrag';
|
|
|
|
function pointer(type: string, x: number, y: number, pointerId = 1, pointerType: 'mouse' | 'touch' = 'mouse'): Event {
|
|
const event = new Event(type, { bubbles: true, cancelable: true });
|
|
Object.defineProperties(event, {
|
|
button: { value: 0 },
|
|
clientX: { value: x },
|
|
clientY: { value: y },
|
|
pointerId: { value: pointerId },
|
|
pointerType: { value: pointerType },
|
|
});
|
|
return event;
|
|
}
|
|
|
|
describe('Tour waypoint marker drag lifecycle', () => {
|
|
it('previews during a drag and commits once on pointer up, swallowing only the generated click', () => {
|
|
const element = document.createElement('button');
|
|
const initial = { lat: 48, lng: 11 };
|
|
const preview = vi.fn();
|
|
const onCommit = vi.fn(() => true);
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial,
|
|
coordinateAt: (clientX, clientY) => ({ lat: clientY / 100, lng: clientX / 100 }),
|
|
setPosition: preview,
|
|
onCommit,
|
|
});
|
|
|
|
element.dispatchEvent(pointer('pointerdown', 10, 10));
|
|
element.dispatchEvent(pointer('pointermove', 11, 11));
|
|
expect(preview).not.toHaveBeenCalled();
|
|
expect(onCommit).not.toHaveBeenCalled();
|
|
|
|
element.dispatchEvent(pointer('pointermove', 20, 30));
|
|
expect(preview).toHaveBeenLastCalledWith({ lat: 0.3, lng: 0.2 });
|
|
expect(onCommit).not.toHaveBeenCalled();
|
|
element.dispatchEvent(pointer('pointerup', 30, 40));
|
|
expect(preview).toHaveBeenLastCalledWith({ lat: 0.4, lng: 0.3 });
|
|
expect(onCommit).toHaveBeenCalledOnce();
|
|
expect(onCommit).toHaveBeenCalledWith({ lat: 0.4, lng: 0.3 });
|
|
|
|
const generatedClick = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
element.dispatchEvent(generatedClick);
|
|
expect(generatedClick.defaultPrevented).toBe(true);
|
|
const nextClick = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
element.dispatchEvent(nextClick);
|
|
expect(nextClick.defaultPrevented).toBe(false);
|
|
cleanup();
|
|
});
|
|
|
|
it('restores the starting coordinate on pointer cancellation and does not commit', () => {
|
|
const element = document.createElement('button');
|
|
const initial = { lat: 48, lng: 11 };
|
|
const preview = vi.fn();
|
|
const onCommit = vi.fn();
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial,
|
|
coordinateAt: (clientX, clientY) => ({ lat: clientY, lng: clientX }),
|
|
setPosition: preview,
|
|
onCommit,
|
|
});
|
|
|
|
element.dispatchEvent(pointer('pointerdown', 10, 10));
|
|
element.dispatchEvent(pointer('pointermove', 20, 30));
|
|
element.dispatchEvent(pointer('pointercancel', 20, 30));
|
|
expect(preview).toHaveBeenLastCalledWith(initial);
|
|
expect(onCommit).not.toHaveBeenCalled();
|
|
cleanup();
|
|
});
|
|
|
|
it('restores the preview when the canonical commit is rejected', () => {
|
|
const element = document.createElement('button');
|
|
const initial = { lat: 48, lng: 11 };
|
|
const preview = vi.fn();
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial,
|
|
coordinateAt: (clientX, clientY) => ({ lat: clientY, lng: clientX }),
|
|
setPosition: preview,
|
|
onCommit: () => false,
|
|
});
|
|
|
|
element.dispatchEvent(pointer('pointerdown', 10, 10));
|
|
element.dispatchEvent(pointer('pointermove', 20, 30));
|
|
element.dispatchEvent(pointer('pointerup', 20, 30));
|
|
expect(preview).toHaveBeenLastCalledWith(initial);
|
|
cleanup();
|
|
});
|
|
|
|
it('keeps pointer, mouse, and touch starts from bubbling into map pan handlers', () => {
|
|
const mapSurface = document.createElement('div');
|
|
const element = document.createElement('button');
|
|
mapSurface.append(element);
|
|
const mapPan = vi.fn();
|
|
mapSurface.addEventListener('pointerdown', mapPan);
|
|
mapSurface.addEventListener('mousedown', mapPan);
|
|
mapSurface.addEventListener('touchstart', mapPan);
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial: { lat: 48, lng: 11 },
|
|
coordinateAt: () => ({ lat: 48, lng: 11 }),
|
|
setPosition: vi.fn(),
|
|
onCommit: vi.fn(),
|
|
});
|
|
|
|
element.dispatchEvent(pointer('pointerdown', 10, 10));
|
|
element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 }));
|
|
element.dispatchEvent(new Event('touchstart', { bubbles: true, cancelable: true }));
|
|
expect(mapPan).not.toHaveBeenCalled();
|
|
cleanup();
|
|
});
|
|
|
|
it('commits a touch-pointer drag once at release', () => {
|
|
const element = document.createElement('button');
|
|
const onCommit = vi.fn(() => true);
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial: { lat: 48, lng: 11 },
|
|
coordinateAt: (clientX, clientY) => ({ lat: clientY / 10, lng: clientX / 10 }),
|
|
setPosition: vi.fn(),
|
|
onCommit,
|
|
});
|
|
|
|
element.dispatchEvent(pointer('pointerdown', 5, 5, 9, 'touch'));
|
|
element.dispatchEvent(pointer('pointermove', 25, 35, 9, 'touch'));
|
|
expect(onCommit).not.toHaveBeenCalled();
|
|
element.dispatchEvent(pointer('pointerup', 25, 35, 9, 'touch'));
|
|
expect(onCommit).toHaveBeenCalledOnce();
|
|
expect(onCommit).toHaveBeenCalledWith({ lat: 3.5, lng: 2.5 });
|
|
cleanup();
|
|
});
|
|
|
|
it('restores existing touch and selection styles when editing drag wiring is removed', () => {
|
|
const element = document.createElement('button');
|
|
element.style.touchAction = 'pan-x';
|
|
element.style.userSelect = 'text';
|
|
const cleanup = wirePlannerWaypointDrag({
|
|
element,
|
|
initial: { lat: 48, lng: 11 },
|
|
coordinateAt: () => ({ lat: 48, lng: 11 }),
|
|
setPosition: vi.fn(),
|
|
onCommit: vi.fn(),
|
|
});
|
|
expect(element.style.touchAction).toBe('none');
|
|
expect(element.style.userSelect).toBe('none');
|
|
cleanup();
|
|
expect(element.style.touchAction).toBe('pan-x');
|
|
expect(element.style.userSelect).toBe('text');
|
|
});
|
|
});
|