geo.getCurrentPosition and some testing helpers (#8150)
Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
f229ad6407
commit
0d513b3a2d
4 changed files with 99 additions and 4 deletions
|
@ -33,7 +33,7 @@ export enum GeolocationError {
|
||||||
|
|
||||||
const GeolocationOptions = {
|
const GeolocationOptions = {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
maximumAge: 1000,
|
maximumAge: 2000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isGeolocationPositionError = (error: unknown): error is GeolocationPositionError =>
|
const isGeolocationPositionError = (error: unknown): error is GeolocationPositionError =>
|
||||||
|
@ -112,13 +112,31 @@ export const mapGeolocationPositionToTimedGeo = (position: GeolocationPosition):
|
||||||
return { timestamp: position.timestamp, geoUri: getGeoUri(genericPositionFromGeolocation(position)) };
|
return { timestamp: position.timestamp, geoUri: getGeoUri(genericPositionFromGeolocation(position)) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current position, returns a promise
|
||||||
|
* @returns Promise<GeolocationPosition>
|
||||||
|
*/
|
||||||
|
export const getCurrentPosition = async (): Promise<GeolocationPosition> => {
|
||||||
|
try {
|
||||||
|
const position = await new Promise((resolve: PositionCallback, reject) => {
|
||||||
|
getGeolocation().getCurrentPosition(resolve, reject, GeolocationOptions);
|
||||||
|
});
|
||||||
|
return position;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(mapGeolocationError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClearWatchCallback = () => void;
|
||||||
export const watchPosition = (
|
export const watchPosition = (
|
||||||
onWatchPosition: PositionCallback,
|
onWatchPosition: PositionCallback,
|
||||||
onWatchPositionError: (error: GeolocationError) => void): () => void => {
|
onWatchPositionError: (error: GeolocationError) => void): ClearWatchCallback => {
|
||||||
try {
|
try {
|
||||||
const onError = (error) => onWatchPositionError(mapGeolocationError(error));
|
const onError = (error) => onWatchPositionError(mapGeolocationError(error));
|
||||||
const watchId = getGeolocation().watchPosition(onWatchPosition, onError, GeolocationOptions);
|
const watchId = getGeolocation().watchPosition(onWatchPosition, onError, GeolocationOptions);
|
||||||
const clearWatch = () => getGeolocation().clearWatch(watchId);
|
const clearWatch = () => {
|
||||||
|
getGeolocation().clearWatch(watchId);
|
||||||
|
};
|
||||||
return clearWatch;
|
return clearWatch;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(mapGeolocationError(error));
|
throw new Error(mapGeolocationError(error));
|
||||||
|
|
|
@ -141,3 +141,27 @@ export const mockGeolocation = (): MockedObject<Geolocation> => {
|
||||||
|
|
||||||
return mockGeolocation;
|
return mockGeolocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mock watchPosition implementation
|
||||||
|
* that calls success callback at the provided delays
|
||||||
|
* ```
|
||||||
|
* geolocation.watchPosition.mockImplementation([0, 1000, 5000, 50])
|
||||||
|
* ```
|
||||||
|
* will call the provided handler with a mock position at
|
||||||
|
* next tick, 1000ms, 6000ms, 6050ms
|
||||||
|
*/
|
||||||
|
export const watchPositionMockImplementation = (delays: number[]) => {
|
||||||
|
return (callback: PositionCallback) => {
|
||||||
|
const position = makeGeolocationPosition({});
|
||||||
|
|
||||||
|
let totalDelay = 0;
|
||||||
|
delays.map(delayMs => {
|
||||||
|
totalDelay += delayMs;
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
callback({ ...position, timestamp: position.timestamp + totalDelay });
|
||||||
|
}, totalDelay);
|
||||||
|
return timeout;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -31,6 +31,16 @@ export const findByTagAndTestId = findByTagAndAttr('data-test-id');
|
||||||
|
|
||||||
export const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
export const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
||||||
|
|
||||||
|
// with jest's modern fake timers process.nextTick is also mocked,
|
||||||
|
// flushing promises in the normal way then waits for some advancement
|
||||||
|
// of the fake timers
|
||||||
|
// https://gist.github.com/apieceofbart/e6dea8d884d29cf88cdb54ef14ddbcc4?permalink_comment_id=4018174#gistcomment-4018174
|
||||||
|
export const flushPromisesWithFakeTimers = async (): Promise<void> => {
|
||||||
|
const promise = new Promise(resolve => process.nextTick(resolve));
|
||||||
|
jest.advanceTimersByTime(1);
|
||||||
|
await promise;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call fn before calling componentDidUpdate on a react component instance, inst.
|
* Call fn before calling componentDidUpdate on a react component instance, inst.
|
||||||
* @param {React.Component} inst an instance of a React component.
|
* @param {React.Component} inst an instance of a React component.
|
||||||
|
@ -57,3 +67,13 @@ export function waitForUpdate(inst: React.Component, updates = 1): Promise<void>
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance jests fake timers and Date.now mock by ms
|
||||||
|
* Useful for testing code using timeouts or intervals
|
||||||
|
* that also checks timestamps
|
||||||
|
*/
|
||||||
|
export const advanceDateAndTime = (ms: number) => {
|
||||||
|
jest.spyOn(global.Date, 'now').mockReturnValue(Date.now() + ms);
|
||||||
|
jest.advanceTimersByTime(ms);
|
||||||
|
};
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {
|
||||||
mapGeolocationPositionToTimedGeo,
|
mapGeolocationPositionToTimedGeo,
|
||||||
watchPosition,
|
watchPosition,
|
||||||
} from "../../../src/utils/beacon";
|
} from "../../../src/utils/beacon";
|
||||||
|
import { getCurrentPosition } from "../../../src/utils/beacon/geolocation";
|
||||||
import { makeGeolocationPosition, mockGeolocation } from "../../test-utils/beacon";
|
import { makeGeolocationPosition, mockGeolocation } from "../../test-utils/beacon";
|
||||||
|
|
||||||
describe('geolocation utilities', () => {
|
describe('geolocation utilities', () => {
|
||||||
|
@ -166,7 +167,7 @@ describe('geolocation utilities', () => {
|
||||||
|
|
||||||
const [, , options] = geolocation.watchPosition.mock.calls[0];
|
const [, , options] = geolocation.watchPosition.mock.calls[0];
|
||||||
expect(options).toEqual({
|
expect(options).toEqual({
|
||||||
maximumAge: 1000,
|
maximumAge: 2000,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -204,4 +205,36 @@ describe('geolocation utilities', () => {
|
||||||
expect(errorHandler).toHaveBeenCalledWith(GeolocationError.PermissionDenied);
|
expect(errorHandler).toHaveBeenCalledWith(GeolocationError.PermissionDenied);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getCurrentPosition()', () => {
|
||||||
|
it('throws with unavailable error when geolocation is not available', async () => {
|
||||||
|
// suppress expected errors from test log
|
||||||
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
||||||
|
|
||||||
|
// remove the mock we added
|
||||||
|
// @ts-ignore illegal assignment to readonly property
|
||||||
|
navigator.geolocation = undefined;
|
||||||
|
|
||||||
|
await expect(() => getCurrentPosition()).rejects.toThrow(GeolocationError.Unavailable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws with geolocation error when geolocation.getCurrentPosition fails', async () => {
|
||||||
|
// suppress expected errors from test log
|
||||||
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
||||||
|
|
||||||
|
const timeoutError = getMockGeolocationPositionError(3, 'message');
|
||||||
|
geolocation.getCurrentPosition.mockImplementation((callback, error) => error(timeoutError));
|
||||||
|
|
||||||
|
await expect(() => getCurrentPosition()).rejects.toThrow(GeolocationError.Timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves with current location', async () => {
|
||||||
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
||||||
|
|
||||||
|
geolocation.getCurrentPosition.mockImplementation((callback, error) => callback(defaultPosition));
|
||||||
|
|
||||||
|
const result = await getCurrentPosition();
|
||||||
|
expect(result).toEqual(defaultPosition);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue