From 06d8755f6b2347804fe2dfa52aa7a811133dcf22 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 7 Feb 2023 16:51:58 +1300 Subject: [PATCH] add tests for geolocate self on map views --- .../views/location/LocationViewDialog.tsx | 2 +- src/utils/location/positionFailureMessage.ts | 5 ++ .../location/LocationViewDialog-test.tsx | 3 +- test/components/views/location/Map-test.tsx | 78 ++++++++++++++++++- .../location/positionFailureMessage-test.ts | 38 +++++++++ 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 test/utils/location/positionFailureMessage-test.ts diff --git a/src/components/views/location/LocationViewDialog.tsx b/src/components/views/location/LocationViewDialog.tsx index 3e706d3df2..76f8001a98 100644 --- a/src/components/views/location/LocationViewDialog.tsx +++ b/src/components/views/location/LocationViewDialog.tsx @@ -68,7 +68,7 @@ export default class LocationViewDialog extends React.Component onError={this.onError} interactive className="mx_LocationViewDialog_map" - allowGeolocate={true} + allowGeolocate > {({ map }) => ( <> diff --git a/src/utils/location/positionFailureMessage.ts b/src/utils/location/positionFailureMessage.ts index 8114ee9627..a5c1e6e60b 100644 --- a/src/utils/location/positionFailureMessage.ts +++ b/src/utils/location/positionFailureMessage.ts @@ -17,6 +17,11 @@ limitations under the License. import { _t } from "../../languageHandler"; import SdkConfig from "../../SdkConfig"; +/** + * Get a localised error message for GeolocationPositionError error codes + * @param code - error code from GeolocationPositionError + * @returns + */ export const positionFailureMessage = (code: number): string | undefined => { const brand = SdkConfig.get().brand; switch (code) { diff --git a/test/components/views/location/LocationViewDialog-test.tsx b/test/components/views/location/LocationViewDialog-test.tsx index 576e5f3217..41149299ee 100644 --- a/test/components/views/location/LocationViewDialog-test.tsx +++ b/test/components/views/location/LocationViewDialog-test.tsx @@ -49,9 +49,10 @@ describe("", () => { it("renders marker correctly for self share", () => { const selfShareEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Self); const member = new RoomMember(roomId, userId); - // @ts-ignore cheat assignment to property + // @ts-ignore cheat assignment to property selfShareEvent.sender = member; const component = getComponent({ mxEvent: selfShareEvent }); + // @ts-ignore fix when moving to rtl expect(component.find("SmartMarker").props()["roomMember"]).toEqual(member); }); }); diff --git a/test/components/views/location/Map-test.tsx b/test/components/views/location/Map-test.tsx index 1d92e09835..ab9f86cf98 100644 --- a/test/components/views/location/Map-test.tsx +++ b/test/components/views/location/Map-test.tsx @@ -16,15 +16,18 @@ limitations under the License. import React from "react"; import { act } from "react-dom/test-utils"; +import { fireEvent, getByTestId, render } from "@testing-library/react"; import * as maplibregl from "maplibre-gl"; import { ClientEvent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { fireEvent, getByTestId, render } from "@testing-library/react"; +import { mocked } from "jest-mock"; import Map from "../../../../src/components/views/location/Map"; -import { getMockClientWithEventEmitter } from "../../../test-utils"; +import { getMockClientWithEventEmitter, getMockGeolocationPositionError } from "../../../test-utils"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils"; +import Modal from "../../../../src/Modal"; +import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog"; describe("", () => { const defaultProps = { @@ -52,6 +55,11 @@ describe("", () => { }); jest.spyOn(logger, "error").mockRestore(); + mocked(maplibregl.GeolocateControl).mockClear(); + }); + + afterEach(() => { + jest.spyOn(logger, "error").mockRestore(); }); const mapOptions = { container: {} as unknown as HTMLElement, style: "" }; @@ -201,4 +209,70 @@ describe("", () => { expect(onClick).toHaveBeenCalled(); }); }); + + describe("geolocate", () => { + it("does not add a geolocate control when allowGeolocate is falsy", () => { + getComponent({ allowGeolocate: false }); + + // didn't create a geolocation control + expect(maplibregl.GeolocateControl).not.toHaveBeenCalled(); + }); + + it("creates a geolocate control and adds it to the map when allowGeolocate is truthy", () => { + getComponent({ allowGeolocate: true }); + + // didn't create a geolocation control + expect(maplibregl.GeolocateControl).toHaveBeenCalledWith({ + positionOptions: { + enableHighAccuracy: true, + }, + trackUserLocation: false, + }); + + // mocked maplibregl shares mock for each mocked instance + // so we can assert the geolocate control was added using this static mock + const mockGeolocate = new maplibregl.GeolocateControl({}); + expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate); + }); + + it("logs and opens a dialog on a geolocation error", () => { + const mockGeolocate = new maplibregl.GeolocateControl({}); + jest.spyOn(mockGeolocate, "on"); + const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {}); + jest.spyOn(Modal, "createDialog"); + + const { rerender } = getComponent({ allowGeolocate: true }); + + // wait for component to settle + getComponent({ allowGeolocate: true }, rerender); + expect(mockGeolocate.on).toHaveBeenCalledWith("error", expect.any(Function)); + const error = getMockGeolocationPositionError(1, "Test"); + + // @ts-ignore pretend to have geolocate emit an error + mockGeolocate.emit("error", error); + + expect(logSpy).toHaveBeenCalledWith("Could not fetch location", error); + + expect(Modal.createDialog).toHaveBeenCalledWith(ErrorDialog, { + title: "Could not fetch location", + description: + "Element was denied permission to fetch your location. Please allow location access in your browser settings.", + }); + }); + + it("unsubscribes from geolocate errors on destroy", () => { + const mockGeolocate = new maplibregl.GeolocateControl({}); + jest.spyOn(mockGeolocate, "on"); + jest.spyOn(mockGeolocate, "off"); + jest.spyOn(Modal, "createDialog"); + + const { unmount } = getComponent({ allowGeolocate: true }); + + expect(mockGeolocate.on).toHaveBeenCalled(); + + unmount(); + + expect(mockGeolocate.off).toHaveBeenCalled(); + }); + }); }); diff --git a/test/utils/location/positionFailureMessage-test.ts b/test/utils/location/positionFailureMessage-test.ts new file mode 100644 index 0000000000..643ebd5099 --- /dev/null +++ b/test/utils/location/positionFailureMessage-test.ts @@ -0,0 +1,38 @@ +/* +Copyright 2023 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 { positionFailureMessage } from "../../../src/utils/location/positionFailureMessage"; + +describe("positionFailureMessage()", () => { + // error codes from GeolocationPositionError + // see: https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError + // 1: PERMISSION_DENIED + // 2: POSITION_UNAVAILABLE + // 3: TIMEOUT + type TestCase = [number, string | undefined]; + it.each([ + [ + 1, + "Element was denied permission to fetch your location. Please allow location access in your browser settings.", + ], + [2, "Failed to fetch your location. Please try again later."], + [3, "Timed out trying to fetch your location. Please try again later."], + [4, "Unknown error fetching location. Please try again later."], + [5, undefined], + ])("returns correct message for error code %s", (code, expectedMessage) => { + expect(positionFailureMessage(code)).toEqual(expectedMessage); + }); +});