Merge pull request #10083 from matrix-org/johannes/find-myself
Add option to find own location in map views
This commit is contained in:
commit
3eee91d4ed
13 changed files with 263 additions and 37 deletions
|
@ -125,6 +125,9 @@ const BeaconViewDialog: React.FC<IProps> = ({ initialFocusedBeacon, roomId, matr
|
||||||
setFocusedBeaconState({ beacon, ts: Date.now() });
|
setFocusedBeaconState({ beacon, ts: Date.now() });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasOwnBeacon =
|
||||||
|
liveBeacons.filter((beacon) => beacon?.beaconInfoOwner === matrixClient.getUserId()).length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_BeaconViewDialog" onFinished={onFinished} fixedWidth={false}>
|
<BaseDialog className="mx_BeaconViewDialog" onFinished={onFinished} fixedWidth={false}>
|
||||||
<MatrixClientContext.Provider value={matrixClient}>
|
<MatrixClientContext.Provider value={matrixClient}>
|
||||||
|
@ -136,6 +139,7 @@ const BeaconViewDialog: React.FC<IProps> = ({ initialFocusedBeacon, roomId, matr
|
||||||
interactive
|
interactive
|
||||||
onError={setMapDisplayError}
|
onError={setMapDisplayError}
|
||||||
className="mx_BeaconViewDialog_map"
|
className="mx_BeaconViewDialog_map"
|
||||||
|
allowGeolocate={!hasOwnBeacon}
|
||||||
>
|
>
|
||||||
{({ map }: { map: maplibregl.Map }) => (
|
{({ map }: { map: maplibregl.Map }) => (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -23,10 +23,9 @@ import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/client";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
|
import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
|
||||||
import { GenericPosition, genericPositionFromGeolocation, getGeoUri } from "../../../utils/beacon";
|
import { GenericPosition, genericPositionFromGeolocation, getGeoUri } from "../../../utils/beacon";
|
||||||
import { LocationShareError, findMapStyleUrl } from "../../../utils/location";
|
import { LocationShareError, findMapStyleUrl, positionFailureMessage } from "../../../utils/location";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { MapError } from "./MapError";
|
import { MapError } from "./MapError";
|
||||||
|
@ -266,21 +265,3 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LocationPicker;
|
export default LocationPicker;
|
||||||
|
|
||||||
function positionFailureMessage(code: number): string {
|
|
||||||
const brand = SdkConfig.get().brand;
|
|
||||||
switch (code) {
|
|
||||||
case 1:
|
|
||||||
return _t(
|
|
||||||
"%(brand)s was denied permission to fetch your location. " +
|
|
||||||
"Please allow location access in your browser settings.",
|
|
||||||
{ brand },
|
|
||||||
);
|
|
||||||
case 2:
|
|
||||||
return _t("Failed to fetch your location. Please try again later.");
|
|
||||||
case 3:
|
|
||||||
return _t("Timed out trying to fetch your location. Please try again later.");
|
|
||||||
case 4:
|
|
||||||
return _t("Unknown error fetching location. Please try again later.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
interactive
|
interactive
|
||||||
className="mx_LocationViewDialog_map"
|
className="mx_LocationViewDialog_map"
|
||||||
|
allowGeolocate
|
||||||
>
|
>
|
||||||
{({ map }) => (
|
{({ map }) => (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode, useContext, useEffect } from "react";
|
import React, { ReactNode, useContext, useEffect, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import * as maplibregl from "maplibre-gl";
|
import * as maplibregl from "maplibre-gl";
|
||||||
import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
|
import { ClientEvent, IClientWellKnown } from "matrix-js-sdk/src/matrix";
|
||||||
|
@ -22,10 +22,13 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||||
import { parseGeoUri } from "../../../utils/location";
|
import { parseGeoUri, positionFailureMessage } from "../../../utils/location";
|
||||||
import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
|
import { tileServerFromWellKnown } from "../../../utils/WellKnownUtils";
|
||||||
import { useMap } from "../../../utils/location/useMap";
|
import { useMap } from "../../../utils/location/useMap";
|
||||||
import { Bounds } from "../../../utils/beacon/bounds";
|
import { Bounds } from "../../../utils/beacon/bounds";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
const useMapWithStyle = ({
|
const useMapWithStyle = ({
|
||||||
id,
|
id,
|
||||||
|
@ -33,14 +36,16 @@ const useMapWithStyle = ({
|
||||||
onError,
|
onError,
|
||||||
interactive,
|
interactive,
|
||||||
bounds,
|
bounds,
|
||||||
|
allowGeolocate,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
centerGeoUri?: string;
|
centerGeoUri?: string;
|
||||||
|
onError?(error: Error): void;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
bounds?: Bounds;
|
bounds?: Bounds;
|
||||||
onError(error: Error): void;
|
allowGeolocate?: boolean;
|
||||||
}): {
|
}): {
|
||||||
map: maplibregl.Map;
|
map: maplibregl.Map | undefined;
|
||||||
bodyId: string;
|
bodyId: string;
|
||||||
} => {
|
} => {
|
||||||
const bodyId = `mx_Map_${id}`;
|
const bodyId = `mx_Map_${id}`;
|
||||||
|
@ -86,12 +91,51 @@ const useMapWithStyle = ({
|
||||||
}
|
}
|
||||||
}, [map, bounds]);
|
}, [map, bounds]);
|
||||||
|
|
||||||
|
const [geolocate, setGeolocate] = useState<maplibregl.GeolocateControl | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (allowGeolocate && !geolocate) {
|
||||||
|
const geolocate = new maplibregl.GeolocateControl({
|
||||||
|
positionOptions: {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
},
|
||||||
|
trackUserLocation: false,
|
||||||
|
});
|
||||||
|
setGeolocate(geolocate);
|
||||||
|
map.addControl(geolocate);
|
||||||
|
}
|
||||||
|
if (!allowGeolocate && geolocate) {
|
||||||
|
map.removeControl(geolocate);
|
||||||
|
setGeolocate(null);
|
||||||
|
}
|
||||||
|
}, [map, geolocate, allowGeolocate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (geolocate) {
|
||||||
|
geolocate.on("error", onGeolocateError);
|
||||||
|
return () => {
|
||||||
|
geolocate.off("error", onGeolocateError);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [geolocate]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
map,
|
map,
|
||||||
bodyId,
|
bodyId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onGeolocateError = (e: GeolocationPositionError): void => {
|
||||||
|
logger.error("Could not fetch location", e);
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t("Could not fetch location"),
|
||||||
|
description: positionFailureMessage(e.code) ?? "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
interface MapProps {
|
interface MapProps {
|
||||||
id: string;
|
id: string;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
|
@ -105,13 +149,24 @@ interface MapProps {
|
||||||
centerGeoUri?: string;
|
centerGeoUri?: string;
|
||||||
bounds?: Bounds;
|
bounds?: Bounds;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
allowGeolocate?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
children?: (renderProps: { map: maplibregl.Map }) => ReactNode;
|
children?: (renderProps: { map: maplibregl.Map }) => ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Map: React.FC<MapProps> = ({ bounds, centerGeoUri, children, className, id, interactive, onError, onClick }) => {
|
const Map: React.FC<MapProps> = ({
|
||||||
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds });
|
bounds,
|
||||||
|
centerGeoUri,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
allowGeolocate,
|
||||||
|
id,
|
||||||
|
interactive,
|
||||||
|
onError,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds, allowGeolocate });
|
||||||
|
|
||||||
const onMapClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
|
const onMapClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
|
||||||
// Eat click events when clicking the attribution button
|
// Eat click events when clicking the attribution button
|
||||||
|
|
|
@ -787,6 +787,10 @@
|
||||||
"Reset bearing to north": "Reset bearing to north",
|
"Reset bearing to north": "Reset bearing to north",
|
||||||
"Zoom in": "Zoom in",
|
"Zoom in": "Zoom in",
|
||||||
"Zoom out": "Zoom out",
|
"Zoom out": "Zoom out",
|
||||||
|
"%(brand)s was denied permission to fetch your location. Please allow location access in your browser settings.": "%(brand)s was denied permission to fetch your location. Please allow location access in your browser settings.",
|
||||||
|
"Failed to fetch your location. Please try again later.": "Failed to fetch your location. Please try again later.",
|
||||||
|
"Timed out trying to fetch your location. Please try again later.": "Timed out trying to fetch your location. Please try again later.",
|
||||||
|
"Unknown error fetching location. Please try again later.": "Unknown error fetching location. Please try again later.",
|
||||||
"Are you sure you want to exit during this export?": "Are you sure you want to exit during this export?",
|
"Are you sure you want to exit during this export?": "Are you sure you want to exit during this export?",
|
||||||
"Unnamed Room": "Unnamed Room",
|
"Unnamed Room": "Unnamed Room",
|
||||||
"Generating a ZIP": "Generating a ZIP",
|
"Generating a ZIP": "Generating a ZIP",
|
||||||
|
@ -2447,10 +2451,6 @@
|
||||||
"Click to move the pin": "Click to move the pin",
|
"Click to move the pin": "Click to move the pin",
|
||||||
"Click to drop a pin": "Click to drop a pin",
|
"Click to drop a pin": "Click to drop a pin",
|
||||||
"Share location": "Share location",
|
"Share location": "Share location",
|
||||||
"%(brand)s was denied permission to fetch your location. Please allow location access in your browser settings.": "%(brand)s was denied permission to fetch your location. Please allow location access in your browser settings.",
|
|
||||||
"Failed to fetch your location. Please try again later.": "Failed to fetch your location. Please try again later.",
|
|
||||||
"Timed out trying to fetch your location. Please try again later.": "Timed out trying to fetch your location. Please try again later.",
|
|
||||||
"Unknown error fetching location. Please try again later.": "Unknown error fetching location. Please try again later.",
|
|
||||||
"You don't have permission to share locations": "You don't have permission to share locations",
|
"You don't have permission to share locations": "You don't have permission to share locations",
|
||||||
"You need to have the right permissions in order to share locations in this room.": "You need to have the right permissions in order to share locations in this room.",
|
"You need to have the right permissions in order to share locations in this room.": "You need to have the right permissions in order to share locations in this room.",
|
||||||
"We couldn't send your location": "We couldn't send your location",
|
"We couldn't send your location": "We couldn't send your location",
|
||||||
|
|
|
@ -20,3 +20,4 @@ export * from "./locationEventGeoUri";
|
||||||
export * from "./LocationShareErrors";
|
export * from "./LocationShareErrors";
|
||||||
export * from "./map";
|
export * from "./map";
|
||||||
export * from "./parseGeoUri";
|
export * from "./parseGeoUri";
|
||||||
|
export * from "./positionFailureMessage";
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { parseGeoUri } from "./parseGeoUri";
|
||||||
import { findMapStyleUrl } from "./findMapStyleUrl";
|
import { findMapStyleUrl } from "./findMapStyleUrl";
|
||||||
import { LocationShareError } from "./LocationShareErrors";
|
import { LocationShareError } from "./LocationShareErrors";
|
||||||
|
|
||||||
export const createMap = (interactive: boolean, bodyId: string, onError: (error: Error) => void): maplibregl.Map => {
|
export const createMap = (interactive: boolean, bodyId: string, onError?: (error: Error) => void): maplibregl.Map => {
|
||||||
try {
|
try {
|
||||||
const styleUrl = findMapStyleUrl();
|
const styleUrl = findMapStyleUrl();
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export const createMap = (interactive: boolean, bodyId: string, onError: (error:
|
||||||
"Failed to load map: check map_style_url in config.json has a " + "valid URL and API key",
|
"Failed to load map: check map_style_url in config.json has a " + "valid URL and API key",
|
||||||
e.error,
|
e.error,
|
||||||
);
|
);
|
||||||
onError(new Error(LocationShareError.MapStyleUrlNotReachable));
|
onError?.(new Error(LocationShareError.MapStyleUrlNotReachable));
|
||||||
});
|
});
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
|
|
41
src/utils/location/positionFailureMessage.ts
Normal file
41
src/utils/location/positionFailureMessage.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
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 { _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) {
|
||||||
|
case 1:
|
||||||
|
return _t(
|
||||||
|
"%(brand)s was denied permission to fetch your location. " +
|
||||||
|
"Please allow location access in your browser settings.",
|
||||||
|
{ brand },
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return _t("Failed to fetch your location. Please try again later.");
|
||||||
|
case 3:
|
||||||
|
return _t("Timed out trying to fetch your location. Please try again later.");
|
||||||
|
case 4:
|
||||||
|
return _t("Unknown error fetching location. Please try again later.");
|
||||||
|
}
|
||||||
|
};
|
|
@ -21,7 +21,7 @@ import { createMap } from "./map";
|
||||||
|
|
||||||
interface UseMapProps {
|
interface UseMapProps {
|
||||||
bodyId: string;
|
bodyId: string;
|
||||||
onError: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export const useMap = ({ interactive, bodyId, onError }: UseMapProps): MapLibreM
|
||||||
try {
|
try {
|
||||||
setMap(createMap(!!interactive, bodyId, onError));
|
setMap(createMap(!!interactive, bodyId, onError));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError(error);
|
onError?.(error);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (map) {
|
if (map) {
|
||||||
|
|
|
@ -16,15 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { act } from "react-dom/test-utils";
|
import { act } from "react-dom/test-utils";
|
||||||
|
import { fireEvent, getByTestId, render } from "@testing-library/react";
|
||||||
import * as maplibregl from "maplibre-gl";
|
import * as maplibregl from "maplibre-gl";
|
||||||
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
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 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 MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
|
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
|
||||||
|
import Modal from "../../../../src/Modal";
|
||||||
|
import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
describe("<Map />", () => {
|
describe("<Map />", () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -52,6 +55,11 @@ describe("<Map />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(logger, "error").mockRestore();
|
jest.spyOn(logger, "error").mockRestore();
|
||||||
|
mocked(maplibregl.GeolocateControl).mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.spyOn(logger, "error").mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||||
|
@ -201,4 +209,70 @@ describe("<Map />", () => {
|
||||||
expect(onClick).toHaveBeenCalled();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
<Map
|
<Map
|
||||||
|
allowGeolocate={true}
|
||||||
centerGeoUri="geo:51.5076,-0.1276"
|
centerGeoUri="geo:51.5076,-0.1276"
|
||||||
className="mx_LocationViewDialog_map"
|
className="mx_LocationViewDialog_map"
|
||||||
id="mx_LocationViewDialog_$2"
|
id="mx_LocationViewDialog_$2"
|
||||||
|
@ -29,12 +30,27 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
MockAttributionControl {},
|
MockAttributionControl {},
|
||||||
"top-right",
|
"top-right",
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
MockGeolocateControl {
|
||||||
|
"_events": {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"_eventsCount": 1,
|
||||||
|
"_maxListeners": undefined,
|
||||||
|
"trigger": [MockFunction],
|
||||||
|
Symbol(kCapture): false,
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"type": "return",
|
"type": "return",
|
||||||
"value": undefined,
|
"value": undefined,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"fitBounds": [MockFunction],
|
"fitBounds": [MockFunction],
|
||||||
|
@ -97,12 +113,27 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
MockAttributionControl {},
|
MockAttributionControl {},
|
||||||
"top-right",
|
"top-right",
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
MockGeolocateControl {
|
||||||
|
"_events": {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"_eventsCount": 1,
|
||||||
|
"_maxListeners": undefined,
|
||||||
|
"trigger": [MockFunction],
|
||||||
|
Symbol(kCapture): false,
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
"results": [
|
"results": [
|
||||||
{
|
{
|
||||||
"type": "return",
|
"type": "return",
|
||||||
"value": undefined,
|
"value": undefined,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "return",
|
||||||
|
"value": undefined,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"fitBounds": [MockFunction],
|
"fitBounds": [MockFunction],
|
||||||
|
|
|
@ -439,7 +439,7 @@ describe("<MBeaconBody />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// mock map utils to raise MapStyleUrlNotConfigured error
|
// mock map utils to raise MapStyleUrlNotConfigured error
|
||||||
jest.spyOn(mapUtilHooks, "useMap").mockImplementation(({ onError }) => {
|
jest.spyOn(mapUtilHooks, "useMap").mockImplementation(({ onError }) => {
|
||||||
onError(new Error(LocationShareError.MapStyleUrlNotConfigured));
|
onError?.(new Error(LocationShareError.MapStyleUrlNotConfigured));
|
||||||
return mockMap;
|
return mockMap;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
38
test/utils/location/positionFailureMessage-test.ts
Normal file
38
test/utils/location/positionFailureMessage-test.ts
Normal file
|
@ -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<TestCase>([
|
||||||
|
[
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue