238 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
| /*
 | |
| Copyright 2022 The Matrix.org Foundation C.I.C.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| import { logger } from "matrix-js-sdk/src/logger";
 | |
| import { Mocked } from "jest-mock";
 | |
| 
 | |
| import {
 | |
|     GenericPosition,
 | |
|     GeolocationError,
 | |
|     getGeoUri,
 | |
|     mapGeolocationError,
 | |
|     mapGeolocationPositionToTimedGeo,
 | |
|     watchPosition,
 | |
| } from "../../../src/utils/beacon";
 | |
| import { getCurrentPosition } from "../../../src/utils/beacon/geolocation";
 | |
| import { makeGeolocationPosition, mockGeolocation, getMockGeolocationPositionError } from "../../test-utils";
 | |
| 
 | |
| describe("geolocation utilities", () => {
 | |
|     let geolocation: Mocked<Geolocation>;
 | |
|     const defaultPosition = makeGeolocationPosition({});
 | |
| 
 | |
|     // 14.03.2022 16:15
 | |
|     const now = 1647270879403;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|         geolocation = mockGeolocation();
 | |
|         jest.spyOn(Date, "now").mockReturnValue(now);
 | |
|     });
 | |
| 
 | |
|     afterEach(() => {
 | |
|         jest.spyOn(Date, "now").mockRestore();
 | |
|         jest.spyOn(logger, "error").mockRestore();
 | |
|     });
 | |
| 
 | |
|     describe("getGeoUri", () => {
 | |
|         it("Renders a URI with only lat and lon", () => {
 | |
|             const pos: GenericPosition = {
 | |
|                 latitude: 43.2,
 | |
|                 longitude: 12.4,
 | |
|                 altitude: undefined,
 | |
|                 accuracy: undefined,
 | |
| 
 | |
|                 timestamp: 12334,
 | |
|             };
 | |
|             expect(getGeoUri(pos)).toEqual("geo:43.2,12.4");
 | |
|         });
 | |
| 
 | |
|         it("Nulls in location are not shown in URI", () => {
 | |
|             const pos: GenericPosition = {
 | |
|                 latitude: 43.2,
 | |
|                 longitude: 12.4,
 | |
| 
 | |
|                 timestamp: 12334,
 | |
|             };
 | |
|             expect(getGeoUri(pos)).toEqual("geo:43.2,12.4");
 | |
|         });
 | |
| 
 | |
|         it("Renders a URI with 3 coords", () => {
 | |
|             const pos: GenericPosition = {
 | |
|                 latitude: 43.2,
 | |
|                 longitude: 12.4,
 | |
|                 altitude: 332.54,
 | |
|                 accuracy: undefined,
 | |
|                 timestamp: 12334,
 | |
|             };
 | |
|             expect(getGeoUri(pos)).toEqual("geo:43.2,12.4,332.54");
 | |
|         });
 | |
| 
 | |
|         it("Renders a URI with accuracy", () => {
 | |
|             const pos: GenericPosition = {
 | |
|                 latitude: 43.2,
 | |
|                 longitude: 12.4,
 | |
|                 altitude: undefined,
 | |
|                 accuracy: 21,
 | |
|                 timestamp: 12334,
 | |
|             };
 | |
|             expect(getGeoUri(pos)).toEqual("geo:43.2,12.4;u=21");
 | |
|         });
 | |
| 
 | |
|         it("Renders a URI with accuracy and altitude", () => {
 | |
|             const pos = {
 | |
|                 latitude: 43.2,
 | |
|                 longitude: 12.4,
 | |
|                 altitude: 12.3,
 | |
|                 accuracy: 21,
 | |
|                 timestamp: 12334,
 | |
|             };
 | |
|             expect(getGeoUri(pos)).toEqual("geo:43.2,12.4,12.3;u=21");
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe("mapGeolocationError", () => {
 | |
|         beforeEach(() => {
 | |
|             // suppress expected errors from test log
 | |
|             jest.spyOn(logger, "error").mockImplementation(() => {});
 | |
|         });
 | |
| 
 | |
|         it("returns default for other error", () => {
 | |
|             const error = new Error("oh no..");
 | |
|             expect(mapGeolocationError(error)).toEqual(GeolocationError.Default);
 | |
|         });
 | |
| 
 | |
|         it("returns unavailable for unavailable error", () => {
 | |
|             const error = new Error(GeolocationError.Unavailable);
 | |
|             expect(mapGeolocationError(error)).toEqual(GeolocationError.Unavailable);
 | |
|         });
 | |
| 
 | |
|         it("maps geo error permissiondenied correctly", () => {
 | |
|             const error = getMockGeolocationPositionError(1, "message");
 | |
|             expect(mapGeolocationError(error)).toEqual(GeolocationError.PermissionDenied);
 | |
|         });
 | |
| 
 | |
|         it("maps geo position unavailable error correctly", () => {
 | |
|             const error = getMockGeolocationPositionError(2, "message");
 | |
|             expect(mapGeolocationError(error)).toEqual(GeolocationError.PositionUnavailable);
 | |
|         });
 | |
| 
 | |
|         it("maps geo timeout error correctly", () => {
 | |
|             const error = getMockGeolocationPositionError(3, "message");
 | |
|             expect(mapGeolocationError(error)).toEqual(GeolocationError.Timeout);
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe("mapGeolocationPositionToTimedGeo()", () => {
 | |
|         it("maps geolocation position correctly", () => {
 | |
|             expect(mapGeolocationPositionToTimedGeo(defaultPosition)).toEqual({
 | |
|                 timestamp: now,
 | |
|                 geoUri: "geo:54.001927,-8.253491;u=1",
 | |
|             });
 | |
|         });
 | |
|     });
 | |
| 
 | |
|     describe("watchPosition()", () => {
 | |
|         it("throws with unavailable error when geolocation is not available", () => {
 | |
|             // 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;
 | |
| 
 | |
|             const positionHandler = jest.fn();
 | |
|             const errorHandler = jest.fn();
 | |
| 
 | |
|             expect(() => watchPosition(positionHandler, errorHandler)).toThrow(GeolocationError.Unavailable);
 | |
|         });
 | |
| 
 | |
|         it("sets up position handler with correct options", () => {
 | |
|             const positionHandler = jest.fn();
 | |
|             const errorHandler = jest.fn();
 | |
|             watchPosition(positionHandler, errorHandler);
 | |
| 
 | |
|             const [, , options] = geolocation.watchPosition.mock.calls[0];
 | |
|             expect(options).toEqual({
 | |
|                 maximumAge: 60000,
 | |
|                 timeout: 10000,
 | |
|             });
 | |
|         });
 | |
| 
 | |
|         it("returns clearWatch function", () => {
 | |
|             const watchId = 1;
 | |
|             geolocation.watchPosition.mockReturnValue(watchId);
 | |
|             const positionHandler = jest.fn();
 | |
|             const errorHandler = jest.fn();
 | |
|             const clearWatch = watchPosition(positionHandler, errorHandler);
 | |
| 
 | |
|             clearWatch();
 | |
| 
 | |
|             expect(geolocation.clearWatch).toHaveBeenCalledWith(watchId);
 | |
|         });
 | |
| 
 | |
|         it("calls position handler with position", () => {
 | |
|             const positionHandler = jest.fn();
 | |
|             const errorHandler = jest.fn();
 | |
|             watchPosition(positionHandler, errorHandler);
 | |
| 
 | |
|             expect(positionHandler).toHaveBeenCalledWith(defaultPosition);
 | |
|         });
 | |
| 
 | |
|         it("maps geolocation position error and calls error handler", () => {
 | |
|             // suppress expected errors from test log
 | |
|             jest.spyOn(logger, "error").mockImplementation(() => {});
 | |
|             geolocation.watchPosition.mockImplementation((_callback, error) => {
 | |
|                 error!(getMockGeolocationPositionError(1, "message"));
 | |
|                 return -1;
 | |
|             });
 | |
|             const positionHandler = jest.fn();
 | |
|             const errorHandler = jest.fn();
 | |
|             watchPosition(positionHandler, errorHandler);
 | |
| 
 | |
|             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 () => {
 | |
|             geolocation.getCurrentPosition.mockImplementation((callback, error) => callback(defaultPosition));
 | |
| 
 | |
|             const result = await getCurrentPosition();
 | |
|             expect(result).toEqual(defaultPosition);
 | |
|         });
 | |
|     });
 | |
| });
 |