From d38a1fa201595dd77677838d1f7a4c41e8041035 Mon Sep 17 00:00:00 2001 From: Kerry Date: Fri, 11 Mar 2022 09:52:57 +0100 Subject: [PATCH] fallback to event text in location body when map unavailable (#7982) * center icon better Signed-off-by: Kerry Archibald * remove debug Signed-off-by: Kerry Archibald * retrigger all builds Signed-off-by: Kerry Archibald * set assetType on share event Signed-off-by: Kerry Archibald * use pin marker on map for pin drop share Signed-off-by: Kerry Archibald * lint Signed-off-by: Kerry Archibald * test events Signed-off-by: Kerry Archibald * pin drop helper text Signed-off-by: Kerry Archibald * use generic location type Signed-off-by: Kerry Archibald * add navigationcontrol when in pin mode Signed-off-by: Kerry Archibald * allow pin drop without location permissions Signed-off-by: Kerry Archibald * remove geolocate control when pin dropping without geo perms Signed-off-by: Kerry Archibald * test locationpicker Signed-off-by: Kerry Archibald * test marker type, tidy Signed-off-by: Kerry Archibald * move findMapStyleUrl Signed-off-by: Kerry Archibald * fallback to event content when cant render map Signed-off-by: Kerry Archibald * update mocks in location picker, show same messages as timeline Signed-off-by: Kerry Archibald * style error message in location share menu Signed-off-by: Kerry Archibald * i18n Signed-off-by: Kerry Archibald * forgotten copyright Signed-off-by: Kerry Archibald * add copyright Signed-off-by: Kerry Archibald * update style Signed-off-by: Kerry Archibald * icon bigger Signed-off-by: Kerry Archibald --- res/css/_components.scss | 1 + .../components/views/location/_MapError.scss | 36 +++++++ res/css/views/location/_LocationPicker.scss | 15 ++- .../views/location/LocationPicker.tsx | 28 +++--- .../views/location/LocationShareErrors.ts | 34 +++++++ src/components/views/location/MapError.tsx | 39 ++++++++ .../views/location/findMapStyleUrl.ts | 41 ++++++++ .../views/messages/MLocationBody.tsx | 70 ++++++-------- src/i18n/strings/en_EN.json | 6 +- .../views/location/LocationPicker-test.tsx | 31 +++++- .../views/location/LocationShareMenu-test.tsx | 2 +- .../views/location/MapError-test.tsx | 43 +++++++++ .../__snapshots__/MapError-test.tsx.snap | 95 +++++++++++++++++++ .../views/messages/MLocationBody-test.tsx | 83 ++++++++++++++-- .../__snapshots__/MLocationBody-test.tsx.snap | 29 ++++++ 15 files changed, 485 insertions(+), 68 deletions(-) create mode 100644 res/css/components/views/location/_MapError.scss create mode 100644 src/components/views/location/LocationShareErrors.ts create mode 100644 src/components/views/location/MapError.tsx create mode 100644 src/components/views/location/findMapStyleUrl.ts create mode 100644 test/components/views/location/MapError-test.tsx create mode 100644 test/components/views/location/__snapshots__/MapError-test.tsx.snap create mode 100644 test/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap diff --git a/res/css/_components.scss b/res/css/_components.scss index 65ce8742b8..9c7f6b878e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -5,6 +5,7 @@ @import "./_font-weights.scss"; @import "./_spacing.scss"; @import "./components/views/location/_LocationShareMenu.scss"; +@import "./components/views/location/_MapError.scss"; @import "./components/views/location/_ShareDialogButtons.scss"; @import "./components/views/location/_ShareType.scss"; @import "./components/views/spaces/_QuickThemeSwitcher.scss"; diff --git a/res/css/components/views/location/_MapError.scss b/res/css/components/views/location/_MapError.scss new file mode 100644 index 0000000000..83d6316ec4 --- /dev/null +++ b/res/css/components/views/location/_MapError.scss @@ -0,0 +1,36 @@ +/* +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. +*/ + +.mx_MapError { + padding: 100px $spacing-32 0; + text-align: center; + + p { + margin: $spacing-16 0 $spacing-32; + } +} + +.mx_MapError_heading { + padding-top: $spacing-24; +} + +.mx_MapError_icon { + height: 58px; + + path { + fill: $secondary-content; + } +} diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index be68eaf5eb..53b3655ed8 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -21,6 +21,16 @@ limitations under the License. position: relative; overflow: hidden; + // when there are errors loading the map + // the canvas is still inserted + // and can overlap error message/close buttons + // hide it + &.mx_LocationPicker_hasError { + .maplibregl-canvas-container, .maplibregl-control-container { + display: none; + } + } + #mx_LocationPicker_map { height: 100%; border-radius: 8px; @@ -94,11 +104,6 @@ limitations under the License. background-color: $header-panel-bg-color; } - - .mx_LocationPicker_error { - color: red; - margin: auto; - } } .mx_MLocationBody_markerIcon { diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index c906c9d148..26eda1edba 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -26,12 +26,13 @@ import MemberAvatar from '../avatars/MemberAvatar'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import Modal from '../../../Modal'; import ErrorDialog from '../dialogs/ErrorDialog'; -import { findMapStyleUrl } from '../messages/MLocationBody'; import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; +import { findMapStyleUrl } from './findMapStyleUrl'; import { LocationShareType } from './shareLocation'; import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg'; +import { LocationShareError } from './LocationShareErrors'; import AccessibleButton from '../elements/AccessibleButton'; - +import { MapError } from './MapError'; export interface ILocationPickerProps { sender: RoomMember; shareType: LocationShareType; @@ -48,7 +49,7 @@ interface IPosition { } interface IState { position?: IPosition; - error: Error; + error?: LocationShareError; } /* @@ -104,10 +105,10 @@ class LocationPicker extends React.Component { this.map.on('error', (e) => { logger.error( "Failed to load map: check map_style_url in config.json " - + "has a valid URL and API key", + + "has a valid URL and API key", e.error, ); - this.setState({ error: e.error }); + this.setState({ error: LocationShareError.MapStyleUrlNotReachable }); }); this.map.on('load', () => { @@ -129,7 +130,10 @@ class LocationPicker extends React.Component { } } catch (e) { logger.error("Failed to render map", e); - this.setState({ error: e }); + const errorType = e?.message === LocationShareError.MapStyleUrlNotConfigured ? + LocationShareError.MapStyleUrlNotConfigured : + LocationShareError.Default; + this.setState({ error: errorType }); } } @@ -213,10 +217,13 @@ class LocationPicker extends React.Component { }; render() { - const error = this.state.error ? -
- { _t("Failed to load map") } -
: null; + if (this.state.error) { + return
+ +
; + } return (
@@ -227,7 +234,6 @@ class LocationPicker extends React.Component {
} - { error }
diff --git a/src/components/views/location/LocationShareErrors.ts b/src/components/views/location/LocationShareErrors.ts new file mode 100644 index 0000000000..9e244e4dab --- /dev/null +++ b/src/components/views/location/LocationShareErrors.ts @@ -0,0 +1,34 @@ +/* +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 { _t } from "../../../languageHandler"; + +export enum LocationShareError { + MapStyleUrlNotConfigured = 'MapStyleUrlNotConfigured', + MapStyleUrlNotReachable = 'MapStyleUrlNotReachable', + Default = 'Default' +} + +export const getLocationShareErrorMessage = (errorType?: LocationShareError): string => { + switch (errorType) { + case LocationShareError.MapStyleUrlNotConfigured: + return _t('This homeserver is not configured to display maps.'); + case LocationShareError.MapStyleUrlNotReachable: + default: + return _t(`This homeserver is not configured correctly to display maps, ` + + `or the configured map server may be unreachable.`); + } +}; diff --git a/src/components/views/location/MapError.tsx b/src/components/views/location/MapError.tsx new file mode 100644 index 0000000000..7ab5d681ab --- /dev/null +++ b/src/components/views/location/MapError.tsx @@ -0,0 +1,39 @@ +/* +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 React from 'react'; + +import { Icon as WarningBadge } from '../../../../res/img/element-icons/warning-badge.svg'; +import { _t } from '../../../languageHandler'; +import AccessibleButton from '../elements/AccessibleButton'; +import Heading from '../typography/Heading'; +import { getLocationShareErrorMessage, LocationShareError } from './LocationShareErrors'; + +interface Props { + onFinished: () => void; + error: LocationShareError; +} + +export const MapError: React.FC = ({ + onFinished, error, +}) => (
+ + { _t("Unable to load map") } +

+ { getLocationShareErrorMessage(error) } +

+ { _t("OK") } +
); diff --git a/src/components/views/location/findMapStyleUrl.ts b/src/components/views/location/findMapStyleUrl.ts new file mode 100644 index 0000000000..bf3823da7d --- /dev/null +++ b/src/components/views/location/findMapStyleUrl.ts @@ -0,0 +1,41 @@ +/* +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 SdkConfig from "../../../SdkConfig"; +import { getTileServerWellKnown } from "../../../utils/WellKnownUtils"; +import { LocationShareError } from "./LocationShareErrors"; + +/** + * Look up what map tile server style URL was provided in the homeserver's + * .well-known location, or, failing that, in our local config, or, failing + * that, defaults to the same tile server listed by matrix.org. + */ +export function findMapStyleUrl(): string { + const mapStyleUrl = ( + getTileServerWellKnown()?.map_style_url ?? + SdkConfig.get().map_style_url + ); + + if (!mapStyleUrl) { + logger.error("'map_style_url' missing from homeserver .well-known area, and " + + "missing from from config.json."); + throw new Error(LocationShareError.MapStyleUrlNotConfigured); + } + + return mapStyleUrl; +} diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index c466d10737..1167320052 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -26,7 +26,6 @@ import { } from 'matrix-js-sdk/src/@types/location'; import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client'; -import SdkConfig from '../../../SdkConfig'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { IBodyProps } from "./IBodyProps"; import { _t } from '../../../languageHandler'; @@ -36,8 +35,10 @@ import LocationViewDialog from '../location/LocationViewDialog'; import TooltipTarget from '../elements/TooltipTarget'; import { Alignment } from '../elements/Tooltip'; import AccessibleButton from '../elements/AccessibleButton'; -import { getTileServerWellKnown, tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; +import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import { findMapStyleUrl } from '../location/findMapStyleUrl'; +import { getLocationShareErrorMessage, LocationShareError } from '../location/LocationShareErrors'; interface IState { error: Error; @@ -117,14 +118,16 @@ export default class MLocationBody extends React.Component { }; render(): React.ReactElement { - return ; + return this.state.error ? + : + ; } } @@ -146,6 +149,23 @@ interface ILocationBodyContentProps { onZoomOut?: () => void; } +export const LocationBodyFallbackContent: React.FC<{ event: MatrixEvent, error: Error }> = ({ error, event }) => { + const errorType = error?.message as LocationShareError; + const message = `${_t('Unable to load map')}: ${getLocationShareErrorMessage(errorType)}`; + + const locationFallback = isSelfLocation(event.getContent()) ? + (_t('Shared their location: ') + event.getContent()?.body) : + (_t('Shared a location: ') + event.getContent()?.body); + + return
+ + { message } + +
+ { locationFallback } +
; +}; + export function LocationBodyContent(props: ILocationBodyContentProps): React.ReactElement { const mapDiv =
- { - props.error - ?
- { _t("Failed to load map") } -
- : null - } { props.tooltip ? ; } -/** - * Look up what map tile server style URL was provided in the homeserver's - * .well-known location, or, failing that, in our local config, or, failing - * that, defaults to the same tile server listed by matrix.org. - */ -export function findMapStyleUrl(): string { - const mapStyleUrl = ( - getTileServerWellKnown()?.map_style_url ?? - SdkConfig.get().map_style_url - ); - - if (!mapStyleUrl) { - throw new Error( - "'map_style_url' missing from homeserver .well-known area, and " + - "missing from from config.json.", - ); - } - - return mapStyleUrl; -} - export function createMap( coords: GeolocationCoordinates, interactive: boolean, @@ -279,7 +271,7 @@ export function createMap( + "valid URL and API key", e.error, ); - onError(e.error); + onError(new Error(LocationShareError.MapStyleUrlNotReachable)); }); return map; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 89a820d175..9743cc028f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2134,7 +2134,9 @@ "%(name)s wants to verify": "%(name)s wants to verify", "You sent a verification request": "You sent a verification request", "Expand map": "Expand map", - "Failed to load map": "Failed to load map", + "Unable to load map": "Unable to load map", + "Shared their location: ": "Shared their location: ", + "Shared a location: ": "Shared a location: ", "Zoom in": "Zoom in", "Zoom out": "Zoom out", "Can't edit poll": "Can't edit poll", @@ -2182,6 +2184,8 @@ "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.", + "This homeserver is not configured to display maps.": "This homeserver is not configured to display maps.", + "This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.": "This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.", "We couldn't send your location": "We couldn't send your location", "%(brand)s could not send your location. Please try again later.": "%(brand)s could not send your location. Please try again later.", "My current location": "My current location", diff --git a/test/components/views/location/LocationPicker-test.tsx b/test/components/views/location/LocationPicker-test.tsx index 46ca04aeff..324ae549aa 100644 --- a/test/components/views/location/LocationPicker-test.tsx +++ b/test/components/views/location/LocationPicker-test.tsx @@ -13,6 +13,7 @@ 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 React from 'react'; import maplibregl from "maplibre-gl"; import { mount } from "enzyme"; @@ -28,8 +29,10 @@ import { LocationShareType } from "../../../../src/components/views/location/sha import MatrixClientContext from '../../../../src/contexts/MatrixClientContext'; import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import { findByTestId } from '../../../test-utils'; +import { findMapStyleUrl } from '../../../../src/components/views/location/findMapStyleUrl'; +import { LocationShareError } from '../../../../src/components/views/location/LocationShareErrors'; -jest.mock('../../../../src/components/views/messages/MLocationBody', () => ({ +jest.mock('../../../../src/components/views/location/findMapStyleUrl', () => ({ findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'), })); @@ -139,6 +142,7 @@ describe("LocationPicker", () => { jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient); jest.clearAllMocks(); mocked(mockMap).addControl.mockReset(); + mocked(findMapStyleUrl).mockReturnValue('tileserver.com'); }); it('displays error when map emits an error', () => { @@ -152,7 +156,25 @@ describe("LocationPicker", () => { wrapper.setProps({}); }); - expect(findByTestId(wrapper, 'location-picker-error').length).toBeTruthy(); + expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + "This homeserver is not configured correctly to display maps, " + + "or the configured map server may be unreachable.", + ); + }); + + it('displays error when map display is not configured properly', () => { + // suppress expected error log + jest.spyOn(logger, 'error').mockImplementation(() => { }); + mocked(findMapStyleUrl).mockImplementation(() => { + throw new Error(LocationShareError.MapStyleUrlNotConfigured); + }); + + const wrapper = getComponent(); + wrapper.setProps({}); + + expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + "This homeserver is not configured to display maps.", + ); }); it('displays error when map setup throws', () => { @@ -165,7 +187,10 @@ describe("LocationPicker", () => { const wrapper = getComponent(); wrapper.setProps({}); - expect(findByTestId(wrapper, 'location-picker-error').length).toBeTruthy(); + expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual( + "This homeserver is not configured correctly to display maps, " + + "or the configured map server may be unreachable.", + ); }); it('initiates map with geolocation', () => { diff --git a/test/components/views/location/LocationShareMenu-test.tsx b/test/components/views/location/LocationShareMenu-test.tsx index 267c8aa6f1..6083c746fc 100644 --- a/test/components/views/location/LocationShareMenu-test.tsx +++ b/test/components/views/location/LocationShareMenu-test.tsx @@ -31,7 +31,7 @@ import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import { LocationShareType } from '../../../../src/components/views/location/shareLocation'; import { findByTestId } from '../../../test-utils'; -jest.mock('../../../../src/components/views/messages/MLocationBody', () => ({ +jest.mock('../../../../src/components/views/location/findMapStyleUrl', () => ({ findMapStyleUrl: jest.fn().mockReturnValue('test'), })); diff --git a/test/components/views/location/MapError-test.tsx b/test/components/views/location/MapError-test.tsx new file mode 100644 index 0000000000..e482d043d0 --- /dev/null +++ b/test/components/views/location/MapError-test.tsx @@ -0,0 +1,43 @@ +/* +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 React from 'react'; +import { mount } from 'enzyme'; + +import '../../../skinned-sdk'; +import { MapError } from '../../../../src/components/views/location/MapError'; +import { LocationShareError } from '../../../../src/components/views/location/LocationShareErrors'; + +describe('', () => { + const defaultProps = { + onFinished: jest.fn(), + error: LocationShareError.MapStyleUrlNotConfigured, + }; + const getComponent = (props = {}) => + mount(); + + it('renders correctly for MapStyleUrlNotConfigured', () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + }); + + it('renders correctly for MapStyleUrlNotReachable', () => { + const component = getComponent({ + error: LocationShareError.MapStyleUrlNotReachable, + }); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/location/__snapshots__/MapError-test.tsx.snap b/test/components/views/location/__snapshots__/MapError-test.tsx.snap new file mode 100644 index 0000000000..726e945493 --- /dev/null +++ b/test/components/views/location/__snapshots__/MapError-test.tsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders correctly for MapStyleUrlNotConfigured 1`] = ` + +
+
+ +

+ Unable to load map +

+
+

+ This homeserver is not configured to display maps. +

+ + + +
+ +`; + +exports[` renders correctly for MapStyleUrlNotReachable 1`] = ` + +
+
+ +

+ Unable to load map +

+
+

+ This homeserver is not configured correctly to display maps, or the configured map server may be unreachable. +

+ + + +
+ +`; diff --git a/test/components/views/messages/MLocationBody-test.tsx b/test/components/views/messages/MLocationBody-test.tsx index 62bdc82a54..c9b4bb2f49 100644 --- a/test/components/views/messages/MLocationBody-test.tsx +++ b/test/components/views/messages/MLocationBody-test.tsx @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from 'react'; +import { mount } from "enzyme"; +import { mocked } from 'jest-mock'; import { makeLocationContent } from "matrix-js-sdk/src/content-helpers"; import { M_ASSET, @@ -24,14 +27,30 @@ import { } from "matrix-js-sdk/src/@types/location"; import { TEXT_NODE_TYPE } from "matrix-js-sdk/src/@types/extensible_events"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import maplibregl from 'maplibre-gl'; +import { logger } from 'matrix-js-sdk/src/logger'; import sdk from "../../../skinned-sdk"; -import { +import MLocationBody, { createMapSiteLink, isSelfLocation, parseGeoUri, } from "../../../../src/components/views/messages/MLocationBody"; +import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; +import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; +import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper"; +import { getTileServerWellKnown } from "../../../../src/utils/WellKnownUtils"; +import SdkConfig from "../../../../src/SdkConfig"; +jest.mock("../../../../src/utils/WellKnownUtils", () => ({ + getTileServerWellKnown: jest.fn(), +})); + +let EVENT_ID = 0; +function nextId(): string { + EVENT_ID++; + return EVENT_ID.toString(); +} sdk.getComponent("views.messages.MLocationBody"); describe("MLocationBody", () => { @@ -245,6 +264,59 @@ describe("MLocationBody", () => { expect(isSelfLocation(content)).toBe(false); }); }); + + describe('', () => { + describe('with error', () => { + const mockClient = { + on: jest.fn(), + off: jest.fn(), + }; + const defaultEvent = modernLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Pin); + const defaultProps = { + mxEvent: defaultEvent, + highlights: [], + highlightLink: '', + onHeightChanged: jest.fn(), + onMessageAllowed: jest.fn(), + permalinkCreator: {} as RoomPermalinkCreator, + mediaEventHelper: {} as MediaEventHelper, + }; + const getComponent = (props = {}) => mount(, { + wrappingComponent: MatrixClientContext.Provider, + wrappingComponentProps: { value: mockClient }, + }); + let sdkConfigSpy; + + beforeEach(() => { + // eat expected errors to keep console clean + jest.spyOn(logger, 'error').mockImplementation(() => { }); + mocked(getTileServerWellKnown).mockReturnValue({}); + sdkConfigSpy = jest.spyOn(SdkConfig, 'get').mockReturnValue({}); + }); + + afterAll(() => { + sdkConfigSpy.mockRestore(); + jest.spyOn(logger, 'error').mockRestore(); + }); + + it('displays correct fallback content without error style when map_style_url is not configured', () => { + const component = getComponent(); + expect(component.find(".mx_EventTile_body")).toMatchSnapshot(); + }); + + it('displays correct fallback content when map_style_url is misconfigured', () => { + const mockMap = new maplibregl.Map(); + mocked(getTileServerWellKnown).mockReturnValue({ map_style_url: 'bad-tile-server.com' }); + const component = getComponent(); + + // simulate error initialising map in maplibregl + // @ts-ignore + mockMap.emit('error', { status: 404 }); + component.setProps({}); + expect(component.find(".mx_EventTile_body")).toMatchSnapshot(); + }); + }); + }); }); function oldLocationEvent(geoUri: string): MatrixEvent { @@ -261,7 +333,7 @@ function oldLocationEvent(geoUri: string): MatrixEvent { ); } -function modernLocationEvent(geoUri: string): MatrixEvent { +function modernLocationEvent(geoUri: string, assetType?: LocationAssetType): MatrixEvent { return new MatrixEvent( { "event_id": nextId(), @@ -271,6 +343,7 @@ function modernLocationEvent(geoUri: string): MatrixEvent { geoUri, 252523, "Human-readable label", + assetType, ), }, ); @@ -290,9 +363,3 @@ function nonLocationEvent(): MatrixEvent { }, ); } - -let EVENT_ID = 0; -function nextId(): string { - EVENT_ID++; - return EVENT_ID.toString(); -} diff --git a/test/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap b/test/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap new file mode 100644 index 0000000000..7db39cdfcb --- /dev/null +++ b/test/components/views/messages/__snapshots__/MLocationBody-test.tsx.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MLocationBody with error displays correct fallback content when map_style_url is misconfigured 1`] = ` +
+ + Unable to load map: This homeserver is not configured correctly to display maps, or the configured map server may be unreachable. + +
+ Shared a location: Found at geo:51.5076,-0.1276 at 2021-12-21T12:22+0000 +
+`; + +exports[`MLocationBody with error displays correct fallback content without error style when map_style_url is not configured 1`] = ` +
+ + Unable to load map: This homeserver is not configured to display maps. + +
+ Shared a location: Found at geo:51.5076,-0.1276 at 2021-12-21T12:22+0000 +
+`;