f8793ab359
- API visitable_tiers (provider DBAL bbox/q/type, paginé) pour les pins de la carte
- POST /tours/{id}/reorder (drag & drop) : renumérotation atomique + recompute
- Layer front field-sales : TourMap (pins, popup, polyline, sélection rectangle),
liste d'étapes draggable (vuedraggable), composable de planification + Vitest
- Pages /tours, /tours/new, /tours/[id]/plan (split responsive, point custom géocodé)
- i18n FR, deep links Waze/Google/Apple, état 100% local
133 lines
4.9 KiB
TypeScript
133 lines
4.9 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
reorderStops,
|
|
computeTotals,
|
|
buildNavigationLinks,
|
|
isStopLocated,
|
|
formatDistance,
|
|
formatDuration,
|
|
formatTime,
|
|
type PlanningStop,
|
|
} from '../useTourPlanning'
|
|
|
|
/** Fabrique une etape de planification minimale pour les tests. */
|
|
function makeStop(overrides: Partial<PlanningStop> = {}): PlanningStop {
|
|
return {
|
|
id: overrides.id ?? 1,
|
|
tierType: overrides.tierType ?? 'client',
|
|
tierId: overrides.tierId ?? null,
|
|
addressId: overrides.addressId ?? null,
|
|
customLabel: null,
|
|
customAddress: null,
|
|
customLatitude: null,
|
|
customLongitude: null,
|
|
position: overrides.position ?? 0,
|
|
visitMinutes: overrides.visitMinutes ?? null,
|
|
legDistanceM: overrides.legDistanceM ?? null,
|
|
legDurationS: overrides.legDurationS ?? null,
|
|
eta: overrides.eta ?? null,
|
|
label: overrides.label ?? 'Étape',
|
|
displayAddress: overrides.displayAddress ?? '',
|
|
latitude: overrides.latitude ?? null,
|
|
longitude: overrides.longitude ?? null,
|
|
}
|
|
}
|
|
|
|
describe('reorderStops', () => {
|
|
it('deplace une etape et renumerote les positions de maniere contigue', () => {
|
|
const stops = [
|
|
makeStop({ id: 1, position: 0, label: 'A' }),
|
|
makeStop({ id: 2, position: 1, label: 'B' }),
|
|
makeStop({ id: 3, position: 2, label: 'C' }),
|
|
]
|
|
|
|
// Deplace C (index 2) en tete (index 0).
|
|
const result = reorderStops(stops, 2, 0)
|
|
|
|
expect(result.map(s => s.label)).toEqual(['C', 'A', 'B'])
|
|
expect(result.map(s => s.position)).toEqual([0, 1, 2])
|
|
})
|
|
|
|
it('ne mute pas le tableau source', () => {
|
|
const stops = [makeStop({ id: 1, position: 0 }), makeStop({ id: 2, position: 1 })]
|
|
reorderStops(stops, 0, 1)
|
|
expect(stops.map(s => s.id)).toEqual([1, 2])
|
|
})
|
|
|
|
it('retourne une copie inchangee si un index est hors borne', () => {
|
|
const stops = [makeStop({ id: 1, position: 0 })]
|
|
const result = reorderStops(stops, 0, 5)
|
|
expect(result.map(s => s.id)).toEqual([1])
|
|
})
|
|
})
|
|
|
|
describe('computeTotals', () => {
|
|
it('somme distances/trajets et ajoute les visites (defaut + specifique)', () => {
|
|
const stops = [
|
|
// 1re etape : pas de leg (point de depart). Visite = defaut 30 min.
|
|
makeStop({ id: 1, legDistanceM: null, legDurationS: null }),
|
|
// 2e : 10 km / 12 min de trajet, visite specifique 15 min.
|
|
makeStop({ id: 2, legDistanceM: 10_000, legDurationS: 720, visitMinutes: 15 }),
|
|
// 3e : 5 km / 6 min, visite par defaut.
|
|
makeStop({ id: 3, legDistanceM: 5_000, legDurationS: 360, visitMinutes: null }),
|
|
]
|
|
|
|
const totals = computeTotals(stops, 30)
|
|
|
|
expect(totals.totalDistanceM).toBe(15_000)
|
|
expect(totals.travelDurationS).toBe(1_080)
|
|
// Visites : 30 + 15 + 30 = 75 min = 4500 s.
|
|
expect(totals.visitDurationS).toBe(4_500)
|
|
expect(totals.totalDurationS).toBe(1_080 + 4_500)
|
|
expect(totals.visitCount).toBe(3)
|
|
})
|
|
|
|
it('renvoie des totaux nuls pour une tournee vide', () => {
|
|
const totals = computeTotals([], 30)
|
|
expect(totals.totalDistanceM).toBe(0)
|
|
expect(totals.totalDurationS).toBe(0)
|
|
expect(totals.visitCount).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('buildNavigationLinks', () => {
|
|
it('construit les trois deep links Waze/Google/Apple', () => {
|
|
const links = buildNavigationLinks({ latitude: 47.218, longitude: -1.553 })
|
|
|
|
expect(links).not.toBeNull()
|
|
expect(links!.waze).toBe('https://waze.com/ul?ll=47.218,-1.553&navigate=yes')
|
|
expect(links!.google).toBe('https://www.google.com/maps/dir/?api=1&destination=47.218,-1.553')
|
|
expect(links!.apple).toBe('https://maps.apple.com/?daddr=47.218,-1.553')
|
|
})
|
|
|
|
it('retourne null sans coordonnees (etape a geolocaliser)', () => {
|
|
expect(buildNavigationLinks(null)).toBeNull()
|
|
expect(buildNavigationLinks({ latitude: 47.2 })).toBeNull()
|
|
expect(buildNavigationLinks({ latitude: null, longitude: null })).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('isStopLocated', () => {
|
|
it('distingue une etape geolocalisee d\'une etape sans coordonnees', () => {
|
|
expect(isStopLocated({ latitude: 47.2, longitude: -1.5 })).toBe(true)
|
|
expect(isStopLocated({ latitude: null, longitude: null })).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('formatteurs', () => {
|
|
it('formate distances et durees', () => {
|
|
expect(formatDistance(850)).toBe('850 m')
|
|
expect(formatDistance(12_340)).toBe('12,3 km')
|
|
expect(formatDistance(null)).toBe('—')
|
|
|
|
expect(formatDuration(1_500)).toBe('25 min')
|
|
expect(formatDuration(5_100)).toBe('1 h 25')
|
|
expect(formatDuration(null)).toBe('—')
|
|
})
|
|
|
|
it('extrait l\'heure HH:MM d\'une chaine ISO', () => {
|
|
expect(formatTime('1970-01-01T08:30:00+00:00')).toBe('08:30')
|
|
expect(formatTime(null)).toBe('—')
|
|
})
|
|
})
|