Files
TREK/client/src/native/geolocation.test.ts
T
Maurice 46f4cab1b7 fix(native): location at the accuracy asked for, and a Play payment that clears later is redeemed
- The location shim runs at high accuracy only while a watcher wants it and
  drops back once that watcher is gone, instead of keeping the 2 s GPS rate
  for the rest of the session.
- The purchase client can hand over a Play subscription that was paid but
  never delivered (a pending payment that cleared later), and the plans screen
  redeems it at once, as a restore would, before Google Play refunds it after
  three days.
2026-11-07 14:57:50 +01:00

100 lines
4.0 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { hasNativeFeature } from './capabilities';
import { _resetNativeGeolocation, installNativeGeolocation } from './geolocation';
import { isNativeApp } from './platform';
import { TrekShell, type NativeLocationPayload } from './trekShell';
vi.mock('./platform', () => ({ isNativeApp: vi.fn() }));
vi.mock('./capabilities', () => ({ hasNativeFeature: vi.fn() }));
vi.mock('./trekShell', () => ({
TrekShell: { startLocation: vi.fn(), stopLocation: vi.fn(), addListener: vi.fn() },
}));
let emit: (payload: NativeLocationPayload) => void = () => {};
const original = navigator.geolocation;
beforeEach(() => {
_resetNativeGeolocation();
Object.defineProperty(navigator, 'geolocation', { configurable: true, value: original });
vi.mocked(isNativeApp).mockReturnValue(true);
vi.mocked(hasNativeFeature).mockReturnValue(true);
vi.mocked(TrekShell.startLocation).mockReset().mockResolvedValue();
vi.mocked(TrekShell.stopLocation).mockReset().mockResolvedValue();
vi.mocked(TrekShell.addListener)
.mockReset()
.mockImplementation(async (_event, listener) => {
emit = listener as unknown as typeof emit;
return { remove: async () => {} };
});
});
const FIX: NativeLocationPayload = {
latitude: 35.68,
longitude: 139.76,
accuracy: 12,
altitude: null,
speed: null,
heading: 90,
timestamp: 1000,
};
describe('native geolocation', () => {
it('answers navigator.geolocation from the app, and stops the stream when nobody listens', async () => {
expect(installNativeGeolocation()).toBe(true);
const seen: number[] = [];
const id = navigator.geolocation.watchPosition((p) => seen.push(p.coords.latitude), undefined, {
enableHighAccuracy: true,
});
expect(TrekShell.startLocation).toHaveBeenCalledWith({ highAccuracy: true });
await Promise.resolve();
emit(FIX);
emit({ ...FIX, latitude: 35.7 });
expect(seen).toEqual([35.68, 35.7]);
navigator.geolocation.clearWatch(id);
expect(TrekShell.stopLocation).toHaveBeenCalledTimes(1);
});
it('runs at high accuracy only while a watcher asks for it, and drops back after', () => {
installNativeGeolocation();
const passive = navigator.geolocation.watchPosition(vi.fn());
expect(TrekShell.startLocation).toHaveBeenLastCalledWith({ highAccuracy: false });
const tracking = navigator.geolocation.watchPosition(vi.fn(), undefined, { enableHighAccuracy: true });
expect(TrekShell.startLocation).toHaveBeenLastCalledWith({ highAccuracy: true });
navigator.geolocation.clearWatch(tracking);
expect(TrekShell.startLocation).toHaveBeenLastCalledWith({ highAccuracy: false });
expect(TrekShell.startLocation).toHaveBeenCalledTimes(3);
// A later passive watch does not inherit the high accuracy.
navigator.geolocation.watchPosition(vi.fn());
expect(TrekShell.startLocation).toHaveBeenCalledTimes(3);
navigator.geolocation.clearWatch(passive);
expect(TrekShell.stopLocation).not.toHaveBeenCalled();
});
it('settles a one-off request with the first fix, and passes a denial on as the Geolocation error', async () => {
installNativeGeolocation();
const once = vi.fn();
navigator.geolocation.getCurrentPosition(once);
await Promise.resolve();
emit(FIX);
emit(FIX);
expect(once).toHaveBeenCalledTimes(1);
expect(TrekShell.stopLocation).toHaveBeenCalledTimes(1);
const failed = vi.fn();
navigator.geolocation.getCurrentPosition(vi.fn(), failed);
emit({ error: { code: 1, message: 'denied' } });
expect(failed.mock.calls[0][0]).toMatchObject({ code: 1, PERMISSION_DENIED: 1 });
});
it('leaves the browser API alone outside an app binary with the feature', () => {
vi.mocked(hasNativeFeature).mockReturnValue(false);
expect(installNativeGeolocation()).toBe(false);
expect(navigator.geolocation).toBe(original);
vi.mocked(hasNativeFeature).mockReturnValue(true);
vi.mocked(isNativeApp).mockReturnValue(false);
expect(installNativeGeolocation()).toBe(false);
expect(navigator.geolocation).toBe(original);
});
});