import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { MapLocateControl } from './map-locate-control'; const POSITION_UNAVAILABLE = 2; function mockGeolocation(impl: Partial) { Object.defineProperty(navigator, 'geolocation', { configurable: true, value: { getCurrentPosition: vi.fn(), watchPosition: vi.fn(), clearWatch: vi.fn(), ...impl }, }); } describe('MapLocateControl', () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { delete (navigator as unknown as { geolocation?: unknown }).geolocation; }); it('renders a labelled button', () => { mockGeolocation({}); render( {}} />); expect(screen.getByRole('button', { name: 'Use my location' })).toBeInTheDocument(); }); it('reports the reading with its accuracy', async () => { mockGeolocation({ getCurrentPosition: vi.fn(success => success({ coords: { latitude: -30.03, longitude: -51.22, accuracy: 12 }, timestamp: 1000, } as GeolocationPosition) ), }); const onLocate = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'Use my location' })); await waitFor(() => expect(onLocate).toHaveBeenCalledWith( { lat: -30.03, lng: -51.22 }, expect.objectContaining({ accuracyMeters: 12, mocked: false }) ) ); }); it('surfaces the platform mock-provider flag', async () => { mockGeolocation({ getCurrentPosition: vi.fn(success => success({ coords: { latitude: -30, longitude: -51, accuracy: 5 }, timestamp: 1, mocked: true, } as unknown as GeolocationPosition) ), }); const onLocate = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'Use my location' })); await waitFor(() => expect(onLocate).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ mocked: true }) ) ); }); it('classifies a denied permission rather than swallowing it', async () => { mockGeolocation({ getCurrentPosition: vi.fn((_success, error) => error?.({ code: 1, PERMISSION_DENIED: 1, message: 'denied' } as GeolocationPositionError) ), }); const onError = vi.fn(); render( {}} onError={onError} />); fireEvent.click(screen.getByRole('button', { name: 'Use my location' })); await waitFor(() => expect(onError).toHaveBeenCalledWith('denied', 'denied')); // A denied permission is not worth retrying, so the button stops offering. await waitFor(() => expect(screen.getByRole('button', { name: 'Location permission denied' })).toBeDisabled() ); }); it('classifies a timeout separately from an absent provider', async () => { mockGeolocation({ getCurrentPosition: vi.fn((_success, error) => error?.({ code: 3, TIMEOUT: 3, message: 'timed out' } as GeolocationPositionError) ), }); const onError = vi.fn(); render( {}} onError={onError} />); fireEvent.click(screen.getByRole('button', { name: 'Use my location' })); await waitFor(() => expect(onError).toHaveBeenCalledWith('timeout', 'timed out')); }); it('reports an unavailable provider', async () => { mockGeolocation({ getCurrentPosition: vi.fn((_success, error) => error?.({ code: POSITION_UNAVAILABLE, POSITION_UNAVAILABLE, message: 'no provider', } as GeolocationPositionError) ), }); const onError = vi.fn(); render( {}} onError={onError} />); fireEvent.click(screen.getByRole('button', { name: 'Use my location' })); await waitFor(() => expect(onError).toHaveBeenCalledWith('unavailable', 'no provider')); }); });