d16c7e5541
Onglet « Carte » sur les fiches Client et Fournisseur (visible sous field_sales.tours.view et module actif) : tous les points géolocalisés du Tiers sur une carte Leaflet, pin ajustable (PATCH lat/lng + geoManual), adresses non géolocalisées listées à part. Composant réutilisable TierAddressMap. Écran de planification : point de départ choisi parmi les sites de l'utilisateur (MalioSelect) ou en adresse libre (autocomplete BAN), marqueur « maison » sur la carte et trace partant du départ. Le mode n'est dérivé du back qu'au premier chargement (les sauvegardes ne réécrasent plus le choix). Suppression de l'ajout d'étape « point libre » (bouton + modale) ; l'affichage des étapes custom existantes est conservé.
159 lines
5.9 KiB
TypeScript
159 lines
5.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
|
import TierAddressMap, { type TierMapAddress } from '../TierAddressMap.vue'
|
|
|
|
// Mock Leaflet (hoisted) : capture les marqueurs crees (un par adresse
|
|
// geolocalisee) et leur handler `dragend`, et trace l'appel a fitBounds.
|
|
const leafletState = vi.hoisted(() => ({
|
|
markers: [] as Array<{
|
|
_latlng: { lat: number, lng: number }
|
|
dragend: (() => void) | null
|
|
setLatLng: ReturnType<typeof vi.fn>
|
|
}>,
|
|
fitBoundsCalled: false,
|
|
}))
|
|
|
|
vi.mock('leaflet', () => {
|
|
function makeMarker(lat: number, lng: number) {
|
|
const marker = {
|
|
_latlng: { lat, lng },
|
|
dragend: null as (() => void) | null,
|
|
addTo: vi.fn().mockReturnThis(),
|
|
bindPopup: vi.fn().mockReturnThis(),
|
|
on: vi.fn((event: string, handler: () => void) => {
|
|
if (event === 'dragend') marker.dragend = handler
|
|
}),
|
|
getLatLng: vi.fn(() => marker._latlng),
|
|
setLatLng: vi.fn(),
|
|
remove: vi.fn(),
|
|
}
|
|
return marker
|
|
}
|
|
const map = {
|
|
fitBounds: vi.fn(() => { leafletState.fitBoundsCalled = true }),
|
|
setView: vi.fn().mockReturnThis(),
|
|
remove: vi.fn(),
|
|
}
|
|
const L = {
|
|
map: vi.fn(() => map),
|
|
tileLayer: vi.fn(() => ({ addTo: vi.fn() })),
|
|
divIcon: vi.fn(() => ({})),
|
|
latLngBounds: vi.fn((points: unknown) => points),
|
|
marker: vi.fn((pos: [number, number]) => {
|
|
const marker = makeMarker(pos[0], pos[1])
|
|
leafletState.markers.push(marker)
|
|
return marker
|
|
}),
|
|
}
|
|
return { default: L, ...L }
|
|
})
|
|
vi.mock('leaflet/dist/leaflet.css', () => ({ default: {} }))
|
|
|
|
// Mock controlable de l'API (PATCH des coordonnees au drag).
|
|
const { patchMock } = vi.hoisted(() => ({ patchMock: vi.fn() }))
|
|
|
|
// Auto-imports Nuxt/Vue utilises sans import explicite par le composant.
|
|
vi.stubGlobal('useI18n', () => ({ t: (key: string) => key }))
|
|
vi.stubGlobal('useApi', () => ({ patch: patchMock }))
|
|
vi.stubGlobal('ref', ref)
|
|
vi.stubGlobal('computed', computed)
|
|
vi.stubGlobal('watch', watch)
|
|
vi.stubGlobal('nextTick', nextTick)
|
|
vi.stubGlobal('onMounted', onMounted)
|
|
vi.stubGlobal('onBeforeUnmount', onBeforeUnmount)
|
|
|
|
function address(over: Partial<TierMapAddress> = {}): TierMapAddress {
|
|
return {
|
|
id: 1,
|
|
latitude: '47.218',
|
|
longitude: '-1.553',
|
|
geoManual: false,
|
|
title: '1 rue du Test, 44000 Nantes',
|
|
typeLabel: 'Livraison',
|
|
patchPath: '/client_addresses/1',
|
|
...over,
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
leafletState.markers = []
|
|
leafletState.fitBoundsCalled = false
|
|
patchMock.mockReset()
|
|
patchMock.mockResolvedValue({})
|
|
})
|
|
|
|
describe('TierAddressMap — marqueurs', () => {
|
|
it('pose un marqueur par adresse geolocalisee et liste a part celles sans coordonnees', async () => {
|
|
const wrapper = mount(TierAddressMap, {
|
|
props: {
|
|
addresses: [
|
|
address({ id: 1, patchPath: '/client_addresses/1' }),
|
|
address({ id: 2, latitude: '48.85', longitude: '2.35', patchPath: '/client_addresses/2' }),
|
|
address({ id: 3, latitude: null, longitude: null, patchPath: '/client_addresses/3', title: '5 rue Sans Geo' }),
|
|
],
|
|
},
|
|
})
|
|
await flushPromises() // import dynamique de Leaflet + montage carte
|
|
|
|
// Deux adresses geolocalisees -> deux marqueurs ; la troisieme (sans
|
|
// coords) n'est pas posee sur la carte mais listee a part.
|
|
expect(leafletState.markers).toHaveLength(2)
|
|
expect(leafletState.fitBoundsCalled).toBe(true)
|
|
|
|
const missing = wrapper.findAll('[data-testid="tier-map-missing"]')
|
|
expect(missing).toHaveLength(1)
|
|
expect(missing[0]?.text()).toContain('5 rue Sans Geo')
|
|
expect(wrapper.find('[data-testid="tier-map"]').exists()).toBe(true)
|
|
})
|
|
|
|
it('affiche un etat vide quand aucune adresse n\'est geolocalisee', async () => {
|
|
const wrapper = mount(TierAddressMap, {
|
|
props: { addresses: [address({ latitude: null, longitude: null })] },
|
|
})
|
|
await flushPromises()
|
|
|
|
expect(leafletState.markers).toHaveLength(0)
|
|
expect(wrapper.find('[data-testid="tier-map"]').exists()).toBe(false)
|
|
expect(wrapper.findAll('[data-testid="tier-map-missing"]')).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe('TierAddressMap — pin ajustable (RG-6.08)', () => {
|
|
it('PATCH les coordonnees + geoManual=true au drag quand editable', async () => {
|
|
const wrapper = mount(TierAddressMap, {
|
|
props: { addresses: [address({ id: 7, patchPath: '/client_addresses/7' })], editable: true },
|
|
})
|
|
await flushPromises()
|
|
|
|
const marker = leafletState.markers[0]
|
|
expect(marker?.dragend).not.toBeNull()
|
|
|
|
// L'utilisateur depose le pin ailleurs (entree de site mal geocodee).
|
|
marker!._latlng = { lat: 48.1234567, lng: -1.6543217 }
|
|
marker!.dragend?.()
|
|
await flushPromises()
|
|
|
|
expect(patchMock).toHaveBeenCalledWith(
|
|
'/client_addresses/7',
|
|
{ latitude: '48.1234567', longitude: '-1.6543217', geoManual: true },
|
|
{ toast: false },
|
|
)
|
|
expect(wrapper.emitted('updated')?.[0]?.[0]).toEqual({
|
|
id: 7,
|
|
latitude: '48.1234567',
|
|
longitude: '-1.6543217',
|
|
})
|
|
})
|
|
|
|
it('ne rend pas les marqueurs draggables (pas de PATCH) en lecture seule', async () => {
|
|
mount(TierAddressMap, {
|
|
props: { addresses: [address()], editable: false },
|
|
})
|
|
await flushPromises()
|
|
|
|
// Aucun handler dragend cable -> pas de drag possible.
|
|
expect(leafletState.markers[0]?.dragend).toBeNull()
|
|
})
|
|
})
|