Files
kapteins-daagbok/client/src/utils/geolocation.test.ts
T
elpatron 79762a0baf fix(gps): Genauigkeitsanzeige unter und ab 100 m unterscheiden
Der tote Ternär lieferte in beiden Zweigen dieselbe Rundung; ab 100 m
wird jetzt auf 10 m gerundet, damit schwache Fixes nicht falsch präzise wirken.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 17:57:11 +02:00

124 lines
4.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
classifyGpsAccuracyMeters,
formatGpsAccuracyMeters,
geolocationErrorI18nKey,
GEOLOCATION_LIVE_INTRO_STORAGE_KEY,
getCurrentPosition,
getGeolocationErrorReason,
hasSeenGeolocationLiveIntro,
markGeolocationLiveIntroSeen,
normalizeGpsCoordinates,
parseGpsCoordinate,
queryGeolocationPermission
} from './geolocation.js'
describe('geolocation helpers', () => {
beforeEach(() => {
localStorage.removeItem(GEOLOCATION_LIVE_INTRO_STORAGE_KEY)
})
it('tracks Live-Log geolocation intro in localStorage', () => {
expect(hasSeenGeolocationLiveIntro()).toBe(false)
markGeolocationLiveIntroSeen()
expect(hasSeenGeolocationLiveIntro()).toBe(true)
})
it('parses coordinates with comma decimals', () => {
expect(parseGpsCoordinate('54,123')).toBeCloseTo(54.123)
})
it('normalizes valid lat/lng', () => {
expect(normalizeGpsCoordinates('54.1', '10.2')).toEqual({
lat: '54.100000',
lng: '10.200000'
})
})
it('rejects out-of-range values', () => {
expect(normalizeGpsCoordinates('91', '0')).toBeNull()
expect(normalizeGpsCoordinates('0', '181')).toBeNull()
})
it('reports unsupported when geolocation API is missing', async () => {
vi.stubGlobal('navigator', { geolocation: undefined })
await expect(getCurrentPosition({ timeoutMs: 100 })).rejects.toThrow('geolocation_unavailable')
})
it('rejects when the browser never calls back (watchdog)', async () => {
vi.useFakeTimers()
vi.stubGlobal('navigator', {
geolocation: {
getCurrentPosition: () => {
// Simulate a hung desktop location service.
}
}
})
const promise = getCurrentPosition({ timeoutMs: 50, enableHighAccuracy: false })
const assertion = expect(promise).rejects.toThrow('geolocation_timeout')
await vi.advanceTimersByTimeAsync(900)
await assertion
vi.useRealTimers()
})
it('resolves coordinates from getCurrentPosition', async () => {
vi.stubGlobal('navigator', {
geolocation: {
getCurrentPosition: (success: PositionCallback) => {
success({
coords: { latitude: 59.91, longitude: 10.75, speed: 2.5, accuracy: 12 }
} as GeolocationPosition)
}
}
})
await expect(getCurrentPosition({ timeoutMs: 1000, enableHighAccuracy: false })).resolves.toEqual({
lat: '59.910000',
lng: '10.750000',
speedKn: 4.9,
accuracyM: 12,
signalQuality: 'excellent'
})
})
it('formats GPS accuracy for display', () => {
expect(formatGpsAccuracyMeters(12.4)).toBe('12')
expect(formatGpsAccuracyMeters(87)).toBe('87')
expect(formatGpsAccuracyMeters(105)).toBe('110')
expect(formatGpsAccuracyMeters(247)).toBe('250')
})
it('classifies GPS accuracy into signal quality', () => {
expect(classifyGpsAccuracyMeters(8)).toBe('excellent')
expect(classifyGpsAccuracyMeters(30)).toBe('good')
expect(classifyGpsAccuracyMeters(80)).toBe('fair')
expect(classifyGpsAccuracyMeters(250)).toBe('poor')
expect(classifyGpsAccuracyMeters(null)).toBe('unknown')
})
it('maps GeolocationPositionError codes to reasons', () => {
expect(getGeolocationErrorReason({ code: 1 } as GeolocationPositionError)).toBe('permission_denied')
expect(getGeolocationErrorReason({ code: 2 } as GeolocationPositionError)).toBe('position_unavailable')
expect(getGeolocationErrorReason({ code: 3 } as GeolocationPositionError)).toBe('timeout')
expect(getGeolocationErrorReason(new Error('geolocation_timeout'))).toBe('timeout')
expect(getGeolocationErrorReason(new Error('geolocation_unavailable'))).toBe('unavailable')
expect(geolocationErrorI18nKey('permission_denied')).toBe('logs.gps_permission_denied')
})
it('reads permission state when supported', async () => {
vi.stubGlobal('navigator', {
geolocation: {},
permissions: {
query: vi.fn().mockResolvedValue({ state: 'denied' })
}
})
await expect(queryGeolocationPermission()).resolves.toBe('denied')
})
})
afterEach(() => {
vi.unstubAllGlobals()
vi.useRealTimers()
})