Files
TREK/client/src/native/geolocation.test.ts
T
Maurice b7951baf23 fix(ci): green the dev gates after the native and popup rounds
- RTL: the measured pills and centred controls use logical forms or a marked line
- i18n: the two native widget numbers are positions, not counts (NOT_PLURAL)
- Types: styleOverride on the lazy GL map, the location and shared-file payloads,
  the capabilities fallback, the transport sheet's stay metadata
- ESLint: latest-value refs move to layout effects, usePresence keeps the last
  open state in state, the booking import opens clean with a shared document
  kept in the list (the reset effect used to clear it again)
- Layers: the phone import sheet lives under mobile/ and reads through tripRepo;
  the offline host check moves to utils/tileUrl
- Sizes: plugin layer geometry, hardNavigate and the exit animations get
  modules of their own
- Tests follow the status radios, the stay chip overview, the dock + button
  for the first list and the field token; store registry lists the new stores
2026-11-06 18:24:25 +01:00

80 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { hasNativeFeature } from './capabilities';
import { _resetNativeGeolocation, installNativeGeolocation } from './geolocation';
import { isNativeIos } from './platform';
import { TrekShell, type NativeLocationPayload } from './trekShell';
vi.mock('./platform', () => ({ isNativeIos: 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(isNativeIos).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('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 iOS binary with the feature', () => {
vi.mocked(hasNativeFeature).mockReturnValue(false);
expect(installNativeGeolocation()).toBe(false);
expect(navigator.geolocation).toBe(original);
});
});