import { act, cleanup, renderHook } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { useGeolocation } from "../src/hooks/useGeolocation"; const originalGeolocation = Object.getOwnPropertyDescriptor(navigator, "geolocation"); afterEach(() => { cleanup(); if (originalGeolocation) Object.defineProperty(navigator, "geolocation", originalGeolocation); else Reflect.deleteProperty(navigator, "geolocation"); }); describe("useGeolocation", () => { it("derives COG only after movement exceeds GPS noise and clears the watch on stop", () => { let success: PositionCallback | undefined; const watchPosition = vi.fn((callback: PositionCallback) => { success = callback; return 23; }); const clearWatch = vi.fn(); Object.defineProperty(navigator, "geolocation", { configurable: true, value: { watchPosition, clearWatch, getCurrentPosition: vi.fn() } }); const { result } = renderHook(() => useGeolocation()); act(() => result.current.start()); act(() => success?.(positionAt(53, 7, 5, 1_000))); expect(result.current.courseDeg).toBeNull(); act(() => success?.(positionAt(53, 7.00001, 5, 2_000))); expect(result.current.courseDeg).toBeNull(); const receivedAtMs = Date.now(); act(() => success?.(positionAt(53, 7.0001, 5, 3_000))); expect(result.current.courseDeg).toBeCloseTo(90, 0); expect(result.current.timestampMs).toBeGreaterThanOrEqual(receivedAtMs); act(() => result.current.stop()); expect(clearWatch).toHaveBeenCalledWith(23); expect(result.current.status).toBe("idle"); expect(result.current.position).toBeNull(); }); }); function positionAt(lat: number, lon: number, accuracy: number, timestamp: number): GeolocationPosition { return { coords: { latitude: lat, longitude: lon, accuracy, altitude: null, altitudeAccuracy: null, heading: null, speed: 3, toJSON: () => ({}) }, timestamp, toJSON: () => ({}) }; }