import { NativeModulesProxy } from '@unimodules/core';
import { mockProperty, unmockAllProperties, mockPlatformIOS, mockPlatformAndroid } from 'jest-expo';
import * as Location from '../Location';
const fakeReturnValue = {
coords: {
latitude: 1,
longitude: 2,
altitude: 3,
accuracy: 4,
heading: 5,
speed: 6,
},
timestamp: 7,
};
function applyMocks() {
mockProperty(
NativeModulesProxy.ExpoLocation,
'getCurrentPositionAsync',
jest.fn(async () => fakeReturnValue)
);
mockProperty(NativeModulesProxy.ExpoLocation, 'requestPermissionsAsync', jest.fn(async () => {}));
}
describe('Location', () => {
beforeAll(() => {
Location.installWebGeolocationPolyfill();
});
beforeEach(() => {
applyMocks();
});
afterEach(() => {
unmockAllProperties();
});
describe('getCurrentPositionAsync', () => {
it('works on Android', async () => {
mockPlatformAndroid();
const result = await Location.getCurrentPositionAsync({});
expect(result).toEqual(fakeReturnValue);
});
it('works on iOS', async () => {
mockPlatformIOS();
const result = await Location.getCurrentPositionAsync({});
expect(result).toEqual(fakeReturnValue);
});
});
describe('watchPositionAsync', () => {
it('receives repeated events', async () => {
let resolveBarrier;
const callback = jest.fn();
const watchBarrier = new Promise(resolve => {
resolveBarrier = resolve;
});
mockProperty(
NativeModulesProxy.ExpoLocation,
'watchPositionImplAsync',
jest.fn(resolveBarrier)
);
await Location.watchPositionAsync({}, callback);
await watchBarrier;
emitNativeLocationUpdate(fakeReturnValue);
emitNativeLocationUpdate(fakeReturnValue);
expect(callback).toHaveBeenCalledTimes(2);
});
});
describe('geocodeAsync', () => {
// TODO(@tsapeta): This doesn't work due to missing Google Maps API key.
xit('falls back to Google Maps API on Android without Google Play services', () => {
mockPlatformAndroid();
mockProperty(NativeModulesProxy.ExpoLocation, 'geocodeAsync', async () => {
const error = new Error();
(error as any).code = 'E_NO_GEOCODER';
throw error;
});
return expect(Location.geocodeAsync('Googleplex')).rejects.toMatchObject({
code: 'E_NO_GEOCODER',
});
});
});
describe('reverseGeocodeAsync', () => {
it('rejects non-numeric latitude/longitude', () => {
// We need to cast these latitude/longitude strings to any type, so TypeScript diagnostics will pass here.
return expect(
Location.reverseGeocodeAsync({ latitude: '37.7' as any, longitude: '-122.5' as any })
).rejects.toEqual(expect.any(TypeError));
});
});
describe('navigator.geolocation polyfill', () => {
beforeEach(() => {
applyMocks();
});
afterEach(() => {
unmockAllProperties();
});
describe('getCurrentPosition', () => {
it('delegates to getCurrentPositionAsync', async () => {
let pass;
const barrier = new Promise(resolve => {
pass = resolve;
});
const options = {};
navigator.geolocation.getCurrentPosition(pass, pass, options);
await barrier;
expect(NativeModulesProxy.ExpoLocation.getCurrentPositionAsync).toHaveBeenCalledWith(
options
);
});
});
describe('watchPosition', () => {
it('watches for updates and stops when clearWatch is called', async () => {
let resolveBarrier;
const watchBarrier = new Promise(resolve => {
resolveBarrier = resolve;
});
mockProperty(
NativeModulesProxy.ExpoLocation,
'watchPositionImplAsync',
jest.fn(async () => {
resolveBarrier();
})
);
const callback = jest.fn();
const watchId = navigator.geolocation.watchPosition(callback);
await watchBarrier;
emitNativeLocationUpdate(fakeReturnValue);
emitNativeLocationUpdate(fakeReturnValue);
expect(callback).toHaveBeenCalledTimes(2);
navigator.geolocation.clearWatch(watchId);
emitNativeLocationUpdate(fakeReturnValue);
expect(callback).toHaveBeenCalledTimes(2);
});
});
});
});
function emitNativeLocationUpdate(location) {
Location.EventEmitter.emit('Expo.locationChanged', {
watchId: Location._getCurrentWatchId(),
location,
});
}