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 = {
|
||||
timeout: 5000,
|
||||
maximumAge: 1000,
|
||||
maximumAge: 2000,
|
||||
};
|
||||
|
||||
const isGeolocationPositionError = (error: unknown): error is GeolocationPositionError =>
|
||||
|
@ -112,13 +112,31 @@ export const mapGeolocationPositionToTimedGeo = (position: GeolocationPosition):
|
|||
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 = (
|
||||
onWatchPosition: PositionCallback,
|
||||
onWatchPositionError: (error: GeolocationError) => void): () => void => {
|
||||
onWatchPositionError: (error: GeolocationError) => void): ClearWatchCallback => {
|
||||
try {
|
||||
const onError = (error) => onWatchPositionError(mapGeolocationError(error));
|
||||
const watchId = getGeolocation().watchPosition(onWatchPosition, onError, GeolocationOptions);
|
||||
const clearWatch = () => getGeolocation().clearWatch(watchId);
|
||||
const clearWatch = () => {
|
||||
getGeolocation().clearWatch(watchId);
|
||||
};
|
||||
return clearWatch;
|
||||
} catch (error) {
|
||||
throw new Error(mapGeolocationError(error));
|
||||
|
|
|
@ -141,3 +141,27 @@ export const mockGeolocation = (): MockedObject<Geolocation> => {
|
|||
|
||||
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));
|
||||
|
||||
// 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.
|
||||
* @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,
|
||||
watchPosition,
|
||||
} from "../../../src/utils/beacon";
|
||||
import { getCurrentPosition } from "../../../src/utils/beacon/geolocation";
|
||||
import { makeGeolocationPosition, mockGeolocation } from "../../test-utils/beacon";
|
||||
|
||||
describe('geolocation utilities', () => {
|
||||
|
@ -166,7 +167,7 @@ describe('geolocation utilities', () => {
|
|||
|
||||
const [, , options] = geolocation.watchPosition.mock.calls[0];
|
||||
expect(options).toEqual({
|
||||
maximumAge: 1000,
|
||||
maximumAge: 2000,
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
|
@ -204,4 +205,36 @@ describe('geolocation utilities', () => {
|
|||
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