Live location share - tiles without tile server (PSG-591) (#8962)
* live location without map POC * styles * force map tiles to show no map for test build * check latestlocationstate exists * just use loading style map fallback when cant display map * style map error for tile view * set pointer cursor when map error is clickable * test mbeaconbody with map display error, lint * lint more good * remove changes for first attempt tile * make maperror test id more accurate * fussy import ordering * PR tweakspull/28788/head^2
parent
e65409861a
commit
60faf6d025
|
@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_OwnBeaconStatus_button {
|
||||||
|
margin-left: $spacing-8;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile[data-layout="bubble"] .mx_OwnBeaconStatus_button {
|
.mx_EventTile[data-layout="bubble"] .mx_OwnBeaconStatus_button {
|
||||||
// align to top to make room for timestamp
|
// align to top to make room for timestamp
|
||||||
// in bubble view
|
// in bubble view
|
||||||
|
|
|
@ -18,9 +18,41 @@ limitations under the License.
|
||||||
padding: 100px $spacing-32 0;
|
padding: 100px $spacing-32 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
p {
|
--mx-map-error-icon-color: $secondary-content;
|
||||||
margin: $spacing-16 0 $spacing-32;
|
--mx-map-error-icon-size: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MapError.mx_MapError_isMinimised {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: $spacing-24;
|
||||||
|
background-color: $panels;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-16px;
|
||||||
|
|
||||||
|
--mx-map-error-icon-color: $alert;
|
||||||
|
--mx-map-error-icon-size: 26px;
|
||||||
|
|
||||||
|
.mx_MapError_message {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 275px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MapError_heading {
|
||||||
|
padding-top: $spacing-8;
|
||||||
|
// override h3 heading size
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MapError_message {
|
||||||
|
margin: $spacing-16 0 $spacing-32;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MapError_heading {
|
.mx_MapError_heading {
|
||||||
|
@ -28,9 +60,9 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MapError_icon {
|
.mx_MapError_icon {
|
||||||
height: 58px;
|
height: var(--mx-map-error-icon-size);
|
||||||
|
|
||||||
path {
|
path {
|
||||||
fill: $secondary-content;
|
fill: var(--mx-map-error-icon-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,29 @@ limitations under the License.
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MBeaconBody.mx_MBeaconBody_withoutMap {
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.mx_MBeaconBody_chin {
|
||||||
|
position: relative;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MBeaconBody_withoutMapContent {
|
||||||
|
background-color: $panels;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MBeaconBody_withoutMapInfoLastUpdated {
|
||||||
|
// 48px lines up with icon in BeaconStatus
|
||||||
|
margin-top: -$spacing-8;
|
||||||
|
padding: 0 $spacing-8 $spacing-8 48px;
|
||||||
|
|
||||||
|
color: $tertiary-content;
|
||||||
|
font-size: $font-10px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MBeaconBody_map {
|
.mx_MBeaconBody_map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -32,11 +55,18 @@ limitations under the License.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MBeaconBody_mapFallback {
|
.mx_MBeaconBody_mapFallback,
|
||||||
|
.mx_MBeaconBody_mapError {
|
||||||
// pushes spinner/icon up
|
// pushes spinner/icon up
|
||||||
// to appear more centered with the footer
|
// to appear more centered with the footer
|
||||||
padding-bottom: 50px;
|
padding-bottom: 50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MBeaconBody_mapErrorInteractive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MBeaconBody_mapFallback {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import {
|
import {
|
||||||
Beacon,
|
Beacon,
|
||||||
|
@ -38,6 +38,8 @@ import DialogSidebar from './DialogSidebar';
|
||||||
import DialogOwnBeaconStatus from './DialogOwnBeaconStatus';
|
import DialogOwnBeaconStatus from './DialogOwnBeaconStatus';
|
||||||
import BeaconStatusTooltip from './BeaconStatusTooltip';
|
import BeaconStatusTooltip from './BeaconStatusTooltip';
|
||||||
import MapFallback from '../location/MapFallback';
|
import MapFallback from '../location/MapFallback';
|
||||||
|
import { MapError } from '../location/MapError';
|
||||||
|
import { LocationShareError } from '../../../utils/location';
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
roomId: Room['roomId'];
|
roomId: Room['roomId'];
|
||||||
|
@ -83,6 +85,15 @@ const BeaconViewDialog: React.FC<IProps> = ({
|
||||||
|
|
||||||
const { bounds, centerGeoUri } = useInitialMapPosition(liveBeacons, focusBeacon);
|
const { bounds, centerGeoUri } = useInitialMapPosition(liveBeacons, focusBeacon);
|
||||||
|
|
||||||
|
const [mapDisplayError, setMapDisplayError] = useState<Error>();
|
||||||
|
|
||||||
|
// automatically open the sidebar if there is no map to see
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapDisplayError) {
|
||||||
|
setSidebarOpen(true);
|
||||||
|
}
|
||||||
|
}, [mapDisplayError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className='mx_BeaconViewDialog'
|
className='mx_BeaconViewDialog'
|
||||||
|
@ -90,11 +101,12 @@ const BeaconViewDialog: React.FC<IProps> = ({
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
>
|
>
|
||||||
<MatrixClientContext.Provider value={matrixClient}>
|
<MatrixClientContext.Provider value={matrixClient}>
|
||||||
{ !!liveBeacons?.length ? <Map
|
{ (!!liveBeacons?.length && !mapDisplayError) && <Map
|
||||||
id='mx_BeaconViewDialog'
|
id='mx_BeaconViewDialog'
|
||||||
bounds={bounds}
|
bounds={bounds}
|
||||||
centerGeoUri={centerGeoUri}
|
centerGeoUri={centerGeoUri}
|
||||||
interactive
|
interactive
|
||||||
|
onError={setMapDisplayError}
|
||||||
className="mx_BeaconViewDialog_map"
|
className="mx_BeaconViewDialog_map"
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
@ -109,7 +121,14 @@ const BeaconViewDialog: React.FC<IProps> = ({
|
||||||
<ZoomButtons map={map} />
|
<ZoomButtons map={map} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Map> :
|
</Map> }
|
||||||
|
{ mapDisplayError &&
|
||||||
|
<MapError
|
||||||
|
error={mapDisplayError.message as LocationShareError}
|
||||||
|
isMinimised
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{ !liveBeacons?.length && !mapDisplayError &&
|
||||||
<MapFallback
|
<MapFallback
|
||||||
data-test-id='beacon-view-dialog-map-fallback'
|
data-test-id='beacon-view-dialog-map-fallback'
|
||||||
className='mx_BeaconViewDialog_map'
|
className='mx_BeaconViewDialog_map'
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Icon as WarningBadge } from '../../../../res/img/element-icons/warning-badge.svg';
|
import { Icon as WarningBadge } from '../../../../res/img/element-icons/warning-badge.svg';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -22,18 +23,38 @@ import { getLocationShareErrorMessage, LocationShareError } from '../../../utils
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import Heading from '../typography/Heading';
|
import Heading from '../typography/Heading';
|
||||||
|
|
||||||
interface Props {
|
export interface MapErrorProps {
|
||||||
onFinished: () => void;
|
|
||||||
error: LocationShareError;
|
error: LocationShareError;
|
||||||
|
onFinished?: () => void;
|
||||||
|
isMinimised?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MapError: React.FC<Props> = ({
|
export const MapError: React.FC<MapErrorProps> = ({
|
||||||
onFinished, error,
|
error,
|
||||||
}) => (<div data-test-id='location-picker-error' className="mx_MapError">
|
isMinimised,
|
||||||
<WarningBadge className="mx_MapError_icon" />
|
className,
|
||||||
<Heading className="mx_MapError_heading" size='h3'>{ _t("Unable to load map") }</Heading>
|
onFinished,
|
||||||
<p>
|
onClick,
|
||||||
{ getLocationShareErrorMessage(error) }
|
}) => (
|
||||||
</p>
|
<div data-test-id='map-rendering-error'
|
||||||
<AccessibleButton element='button' kind="primary" onClick={onFinished}>{ _t("OK") }</AccessibleButton>
|
className={classNames('mx_MapError', className, { 'mx_MapError_isMinimised': isMinimised })}
|
||||||
</div>);
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<WarningBadge className='mx_MapError_icon' />
|
||||||
|
<Heading className='mx_MapError_heading' size='h3'>{ _t('Unable to load map') }</Heading>
|
||||||
|
<p className='mx_MapError_message'>
|
||||||
|
{ getLocationShareErrorMessage(error) }
|
||||||
|
</p>
|
||||||
|
{ onFinished &&
|
||||||
|
<AccessibleButton
|
||||||
|
element='button'
|
||||||
|
kind='primary'
|
||||||
|
onClick={onFinished}
|
||||||
|
>
|
||||||
|
{ _t('OK') }
|
||||||
|
</AccessibleButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
|
@ -30,7 +30,6 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
const MapFallback: React.FC<Props> = ({ className, isLoading, children, ...rest }) => {
|
const MapFallback: React.FC<Props> = ({ className, isLoading, children, ...rest }) => {
|
||||||
return <div className={classNames('mx_MapFallback', className)} {...rest}>
|
return <div className={classNames('mx_MapFallback', className)} {...rest}>
|
||||||
<MapFallbackImage className='mx_MapFallback_bg' />
|
<MapFallbackImage className='mx_MapFallback_bg' />
|
||||||
{ /* <div className='mx_MapFallback_bg'/> */ }
|
|
||||||
{ isLoading ? <Spinner h={32} w={32} /> : <LocationMarkerIcon className='mx_MapFallback_icon' /> }
|
{ isLoading ? <Spinner h={32} w={32} /> : <LocationMarkerIcon className='mx_MapFallback_icon' /> }
|
||||||
{ children }
|
{ children }
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -26,17 +26,19 @@ import {
|
||||||
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
|
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
|
||||||
import { randomString } from 'matrix-js-sdk/src/randomstring';
|
import { randomString } from 'matrix-js-sdk/src/randomstring';
|
||||||
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';
|
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { isBeaconWaitingToStart, useBeacon } from '../../../utils/beacon';
|
import { isBeaconWaitingToStart, useBeacon } from '../../../utils/beacon';
|
||||||
import { isSelfLocation } from '../../../utils/location';
|
import { isSelfLocation, LocationShareError } from '../../../utils/location';
|
||||||
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
||||||
import BeaconStatus from '../beacon/BeaconStatus';
|
import BeaconStatus from '../beacon/BeaconStatus';
|
||||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||||
import Map from '../location/Map';
|
import Map from '../location/Map';
|
||||||
|
import { MapError } from '../location/MapError';
|
||||||
import MapFallback from '../location/MapFallback';
|
import MapFallback from '../location/MapFallback';
|
||||||
import SmartMarker from '../location/SmartMarker';
|
import SmartMarker from '../location/SmartMarker';
|
||||||
import { GetRelationsForEvent } from '../rooms/EventTile';
|
import { GetRelationsForEvent } from '../rooms/EventTile';
|
||||||
|
@ -136,7 +138,16 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
||||||
|
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error, waitingToStart);
|
const isMapDisplayError = error?.message === LocationShareError.MapStyleUrlNotConfigured ||
|
||||||
|
error?.message === LocationShareError.MapStyleUrlNotReachable;
|
||||||
|
const displayStatus = getBeaconDisplayStatus(
|
||||||
|
isLive,
|
||||||
|
latestLocationState,
|
||||||
|
// if we are unable to display maps because it is not configured for the server
|
||||||
|
// don't display an error
|
||||||
|
isMapDisplayError ? undefined : error,
|
||||||
|
waitingToStart,
|
||||||
|
);
|
||||||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||||
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
||||||
|
|
||||||
|
@ -152,6 +163,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
||||||
roomId: mxEvent.getRoomId(),
|
roomId: mxEvent.getRoomId(),
|
||||||
matrixClient,
|
matrixClient,
|
||||||
focusBeacon: beacon,
|
focusBeacon: beacon,
|
||||||
|
isMapDisplayError,
|
||||||
},
|
},
|
||||||
"mx_BeaconViewDialog_wrapper",
|
"mx_BeaconViewDialog_wrapper",
|
||||||
false, // isPriority
|
false, // isPriority
|
||||||
|
@ -160,8 +172,11 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_MBeaconBody' ref={ref}>
|
<div
|
||||||
{ displayStatus === BeaconDisplayStatus.Active ?
|
className='mx_MBeaconBody'
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
{ (displayStatus === BeaconDisplayStatus.Active && !isMapDisplayError) ?
|
||||||
<Map
|
<Map
|
||||||
id={mapId}
|
id={mapId}
|
||||||
centerGeoUri={latestLocationState.uri}
|
centerGeoUri={latestLocationState.uri}
|
||||||
|
@ -180,10 +195,23 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</Map>
|
</Map>
|
||||||
: <MapFallback
|
: isMapDisplayError ?
|
||||||
isLoading={displayStatus === BeaconDisplayStatus.Loading}
|
<MapError
|
||||||
className='mx_MBeaconBody_map mx_MBeaconBody_mapFallback'
|
error={error.message as LocationShareError}
|
||||||
/>
|
onClick={onClick}
|
||||||
|
className={classNames(
|
||||||
|
'mx_MBeaconBody_mapError',
|
||||||
|
// set interactive class when maximised map can be opened
|
||||||
|
{ 'mx_MBeaconBody_mapErrorInteractive':
|
||||||
|
displayStatus === BeaconDisplayStatus.Active,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
isMinimised
|
||||||
|
/> :
|
||||||
|
<MapFallback
|
||||||
|
isLoading={displayStatus === BeaconDisplayStatus.Loading}
|
||||||
|
className='mx_MBeaconBody_map mx_MBeaconBody_mapFallback'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
{ isOwnBeacon ?
|
{ isOwnBeacon ?
|
||||||
<OwnBeaconStatus
|
<OwnBeaconStatus
|
||||||
|
|
|
@ -99,7 +99,7 @@ describe("LocationPicker", () => {
|
||||||
wrapper.setProps({});
|
wrapper.setProps({});
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual(
|
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
|
||||||
"This homeserver is not configured correctly to display maps, "
|
"This homeserver is not configured correctly to display maps, "
|
||||||
+ "or the configured map server may be unreachable.",
|
+ "or the configured map server may be unreachable.",
|
||||||
);
|
);
|
||||||
|
@ -115,7 +115,7 @@ describe("LocationPicker", () => {
|
||||||
const wrapper = getComponent();
|
const wrapper = getComponent();
|
||||||
wrapper.setProps({});
|
wrapper.setProps({});
|
||||||
|
|
||||||
expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual(
|
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
|
||||||
"This homeserver is not configured to display maps.",
|
"This homeserver is not configured to display maps.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -130,7 +130,7 @@ describe("LocationPicker", () => {
|
||||||
const wrapper = getComponent();
|
const wrapper = getComponent();
|
||||||
wrapper.setProps({});
|
wrapper.setProps({});
|
||||||
|
|
||||||
expect(findByTestId(wrapper, 'location-picker-error').find('p').text()).toEqual(
|
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
|
||||||
"This homeserver is not configured correctly to display maps, "
|
"This homeserver is not configured correctly to display maps, "
|
||||||
+ "or the configured map server may be unreachable.",
|
+ "or the configured map server may be unreachable.",
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,28 +15,45 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { render, RenderResult } from '@testing-library/react';
|
||||||
|
|
||||||
import { MapError } from '../../../../src/components/views/location/MapError';
|
import { MapError, MapErrorProps } from '../../../../src/components/views/location/MapError';
|
||||||
import { LocationShareError } from '../../../../src/utils/location';
|
import { LocationShareError } from '../../../../src/utils/location';
|
||||||
|
|
||||||
describe('<MapError />', () => {
|
describe('<MapError />', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
onFinished: jest.fn(),
|
onFinished: jest.fn(),
|
||||||
error: LocationShareError.MapStyleUrlNotConfigured,
|
error: LocationShareError.MapStyleUrlNotConfigured,
|
||||||
|
className: 'test',
|
||||||
};
|
};
|
||||||
const getComponent = (props = {}) =>
|
const getComponent = (props: Partial<MapErrorProps> = {}): RenderResult =>
|
||||||
mount(<MapError {...defaultProps} {...props} />);
|
render(<MapError {...defaultProps} {...props} />);
|
||||||
|
|
||||||
it('renders correctly for MapStyleUrlNotConfigured', () => {
|
it('renders correctly for MapStyleUrlNotConfigured', () => {
|
||||||
const component = getComponent();
|
const { container } = getComponent();
|
||||||
expect(component).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly for MapStyleUrlNotReachable', () => {
|
it('renders correctly for MapStyleUrlNotReachable', () => {
|
||||||
const component = getComponent({
|
const { container } = getComponent({
|
||||||
error: LocationShareError.MapStyleUrlNotReachable,
|
error: LocationShareError.MapStyleUrlNotReachable,
|
||||||
});
|
});
|
||||||
expect(component).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render button when onFinished falsy', () => {
|
||||||
|
const { queryByText } = getComponent({
|
||||||
|
error: LocationShareError.MapStyleUrlNotReachable,
|
||||||
|
onFinished: undefined,
|
||||||
|
});
|
||||||
|
// no button
|
||||||
|
expect(queryByText('OK')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies class when isMinimised is truthy', () => {
|
||||||
|
const { container } = getComponent({
|
||||||
|
isMinimised: true,
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,95 +1,91 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<MapError /> renders correctly for MapStyleUrlNotConfigured 1`] = `
|
exports[`<MapError /> applies class when isMinimised is truthy 1`] = `
|
||||||
<MapError
|
<div>
|
||||||
error="MapStyleUrlNotConfigured"
|
|
||||||
onFinished={[MockFunction]}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="mx_MapError"
|
class="mx_MapError test mx_MapError_isMinimised"
|
||||||
data-test-id="location-picker-error"
|
data-test-id="map-rendering-error"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_MapError_icon"
|
class="mx_MapError_icon"
|
||||||
/>
|
/>
|
||||||
<Heading
|
<h3
|
||||||
className="mx_MapError_heading"
|
class="mx_Heading_h3 mx_MapError_heading"
|
||||||
size="h3"
|
>
|
||||||
|
Unable to load map
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="mx_MapError_message"
|
||||||
>
|
>
|
||||||
<h3
|
|
||||||
className="mx_Heading_h3 mx_MapError_heading"
|
|
||||||
>
|
|
||||||
Unable to load map
|
|
||||||
</h3>
|
|
||||||
</Heading>
|
|
||||||
<p>
|
|
||||||
This homeserver is not configured to display maps.
|
This homeserver is not configured to display maps.
|
||||||
</p>
|
</p>
|
||||||
<AccessibleButton
|
<button
|
||||||
element="button"
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
kind="primary"
|
|
||||||
onClick={[MockFunction]}
|
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<button
|
OK
|
||||||
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
</button>
|
||||||
onClick={[MockFunction]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
OK
|
|
||||||
</button>
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
</MapError>
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<MapError /> renders correctly for MapStyleUrlNotConfigured 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_MapError test"
|
||||||
|
data-test-id="map-rendering-error"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_MapError_icon"
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
class="mx_Heading_h3 mx_MapError_heading"
|
||||||
|
>
|
||||||
|
Unable to load map
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="mx_MapError_message"
|
||||||
|
>
|
||||||
|
This homeserver is not configured to display maps.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<MapError /> renders correctly for MapStyleUrlNotReachable 1`] = `
|
exports[`<MapError /> renders correctly for MapStyleUrlNotReachable 1`] = `
|
||||||
<MapError
|
<div>
|
||||||
error="MapStyleUrlNotReachable"
|
|
||||||
onFinished={[MockFunction]}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="mx_MapError"
|
class="mx_MapError test"
|
||||||
data-test-id="location-picker-error"
|
data-test-id="map-rendering-error"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_MapError_icon"
|
class="mx_MapError_icon"
|
||||||
/>
|
/>
|
||||||
<Heading
|
<h3
|
||||||
className="mx_MapError_heading"
|
class="mx_Heading_h3 mx_MapError_heading"
|
||||||
size="h3"
|
>
|
||||||
|
Unable to load map
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="mx_MapError_message"
|
||||||
>
|
>
|
||||||
<h3
|
|
||||||
className="mx_Heading_h3 mx_MapError_heading"
|
|
||||||
>
|
|
||||||
Unable to load map
|
|
||||||
</h3>
|
|
||||||
</Heading>
|
|
||||||
<p>
|
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
<AccessibleButton
|
<button
|
||||||
element="button"
|
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
kind="primary"
|
|
||||||
onClick={[MockFunction]}
|
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<button
|
OK
|
||||||
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
</button>
|
||||||
onClick={[MockFunction]}
|
|
||||||
onKeyDown={[Function]}
|
|
||||||
onKeyUp={[Function]}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
OK
|
|
||||||
</button>
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
</MapError>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import {
|
||||||
getMockClientWithEventEmitter,
|
getMockClientWithEventEmitter,
|
||||||
makeBeaconEvent,
|
makeBeaconEvent,
|
||||||
makeBeaconInfoEvent,
|
makeBeaconInfoEvent,
|
||||||
|
makeRoomWithBeacons,
|
||||||
makeRoomWithStateEvents,
|
makeRoomWithStateEvents,
|
||||||
} from '../../../test-utils';
|
} from '../../../test-utils';
|
||||||
import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks';
|
import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks';
|
||||||
|
@ -40,6 +41,9 @@ import { MediaEventHelper } from '../../../../src/utils/MediaEventHelper';
|
||||||
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
|
||||||
import Modal from '../../../../src/Modal';
|
import Modal from '../../../../src/Modal';
|
||||||
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
|
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
|
||||||
|
import { MapError } from '../../../../src/components/views/location/MapError';
|
||||||
|
import * as mapUtilHooks from '../../../../src/utils/location/useMap';
|
||||||
|
import { LocationShareError } from '../../../../src/utils/location';
|
||||||
|
|
||||||
describe('<MBeaconBody />', () => {
|
describe('<MBeaconBody />', () => {
|
||||||
// 14.03.2022 16:15
|
// 14.03.2022 16:15
|
||||||
|
@ -94,112 +98,116 @@ describe('<MBeaconBody />', () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders stopped beacon UI for an explicitly stopped beacon', () => {
|
const testBeaconStatuses = () => {
|
||||||
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
it('renders stopped beacon UI for an explicitly stopped beacon', () => {
|
||||||
roomId,
|
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
||||||
{ isLive: false },
|
roomId,
|
||||||
'$alice-room1-1',
|
{ isLive: false },
|
||||||
);
|
'$alice-room1-1',
|
||||||
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
);
|
||||||
const component = getComponent({ mxEvent: beaconInfoEvent });
|
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
||||||
expect(component.text()).toEqual("Live location ended");
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
});
|
expect(component.text()).toEqual("Live location ended");
|
||||||
|
|
||||||
it('renders stopped beacon UI for an expired beacon', () => {
|
|
||||||
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
|
||||||
roomId,
|
|
||||||
// puts this beacons live period in the past
|
|
||||||
{ isLive: true, timestamp: now - 600000, timeout: 500 },
|
|
||||||
'$alice-room1-1',
|
|
||||||
);
|
|
||||||
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
|
||||||
const component = getComponent({ mxEvent: beaconInfoEvent });
|
|
||||||
expect(component.text()).toEqual("Live location ended");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders loading beacon UI for a beacon that has not started yet', () => {
|
|
||||||
const beaconInfoEvent = makeBeaconInfoEvent(
|
|
||||||
aliceId,
|
|
||||||
roomId,
|
|
||||||
// puts this beacons start timestamp in the future
|
|
||||||
{ isLive: true, timestamp: now + 60000, timeout: 500 },
|
|
||||||
'$alice-room1-1',
|
|
||||||
);
|
|
||||||
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
|
||||||
const component = getComponent({ mxEvent: beaconInfoEvent });
|
|
||||||
expect(component.text()).toEqual("Loading live location...");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not open maximised map when on click when beacon is stopped', () => {
|
|
||||||
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
|
||||||
roomId,
|
|
||||||
// puts this beacons live period in the past
|
|
||||||
{ isLive: true, timestamp: now - 600000, timeout: 500 },
|
|
||||||
'$alice-room1-1',
|
|
||||||
);
|
|
||||||
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
|
||||||
const component = getComponent({ mxEvent: beaconInfoEvent });
|
|
||||||
act(() => {
|
|
||||||
component.find('.mx_MBeaconBody_map').at(0).simulate('click');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(modalSpy).not.toHaveBeenCalled();
|
it('renders stopped beacon UI for an expired beacon', () => {
|
||||||
});
|
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
||||||
|
roomId,
|
||||||
it('renders stopped UI when a beacon event is not the latest beacon for a user', () => {
|
// puts this beacons live period in the past
|
||||||
const aliceBeaconInfo1 = makeBeaconInfoEvent(
|
{ isLive: true, timestamp: now - 600000, timeout: 500 },
|
||||||
aliceId,
|
'$alice-room1-1',
|
||||||
roomId,
|
);
|
||||||
// this one is a little older
|
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
||||||
{ isLive: true, timestamp: now - 500 },
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
'$alice-room1-1',
|
expect(component.text()).toEqual("Live location ended");
|
||||||
);
|
|
||||||
aliceBeaconInfo1.event.origin_server_ts = now - 500;
|
|
||||||
const aliceBeaconInfo2 = makeBeaconInfoEvent(
|
|
||||||
aliceId,
|
|
||||||
roomId,
|
|
||||||
{ isLive: true },
|
|
||||||
'$alice-room1-2',
|
|
||||||
);
|
|
||||||
|
|
||||||
makeRoomWithStateEvents([aliceBeaconInfo1, aliceBeaconInfo2], { roomId, mockClient });
|
|
||||||
|
|
||||||
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
|
|
||||||
// beacon1 has been superceded by beacon2
|
|
||||||
expect(component.text()).toEqual("Live location ended");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders stopped UI when a beacon event is replaced', () => {
|
|
||||||
const aliceBeaconInfo1 = makeBeaconInfoEvent(
|
|
||||||
aliceId,
|
|
||||||
roomId,
|
|
||||||
// this one is a little older
|
|
||||||
{ isLive: true, timestamp: now - 500 },
|
|
||||||
'$alice-room1-1',
|
|
||||||
);
|
|
||||||
aliceBeaconInfo1.event.origin_server_ts = now - 500;
|
|
||||||
const aliceBeaconInfo2 = makeBeaconInfoEvent(
|
|
||||||
aliceId,
|
|
||||||
roomId,
|
|
||||||
{ isLive: true },
|
|
||||||
'$alice-room1-2',
|
|
||||||
);
|
|
||||||
|
|
||||||
const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient });
|
|
||||||
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
|
|
||||||
|
|
||||||
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1));
|
|
||||||
// update alice's beacon with a new edition
|
|
||||||
// beacon instance emits
|
|
||||||
act(() => {
|
|
||||||
beaconInstance.update(aliceBeaconInfo2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
component.setProps({});
|
it('renders loading beacon UI for a beacon that has not started yet', () => {
|
||||||
|
const beaconInfoEvent = makeBeaconInfoEvent(
|
||||||
|
aliceId,
|
||||||
|
roomId,
|
||||||
|
// puts this beacons start timestamp in the future
|
||||||
|
{ isLive: true, timestamp: now + 60000, timeout: 500 },
|
||||||
|
'$alice-room1-1',
|
||||||
|
);
|
||||||
|
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
||||||
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
|
expect(component.text()).toEqual("Loading live location...");
|
||||||
|
});
|
||||||
|
|
||||||
// beacon1 has been superceded by beacon2
|
it('does not open maximised map when on click when beacon is stopped', () => {
|
||||||
expect(component.text()).toEqual("Live location ended");
|
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
||||||
});
|
roomId,
|
||||||
|
// puts this beacons live period in the past
|
||||||
|
{ isLive: true, timestamp: now - 600000, timeout: 500 },
|
||||||
|
'$alice-room1-1',
|
||||||
|
);
|
||||||
|
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
|
||||||
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
|
act(() => {
|
||||||
|
component.find('.mx_MBeaconBody_map').at(0).simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(modalSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders stopped UI when a beacon event is not the latest beacon for a user', () => {
|
||||||
|
const aliceBeaconInfo1 = makeBeaconInfoEvent(
|
||||||
|
aliceId,
|
||||||
|
roomId,
|
||||||
|
// this one is a little older
|
||||||
|
{ isLive: true, timestamp: now - 500 },
|
||||||
|
'$alice-room1-1',
|
||||||
|
);
|
||||||
|
aliceBeaconInfo1.event.origin_server_ts = now - 500;
|
||||||
|
const aliceBeaconInfo2 = makeBeaconInfoEvent(
|
||||||
|
aliceId,
|
||||||
|
roomId,
|
||||||
|
{ isLive: true },
|
||||||
|
'$alice-room1-2',
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRoomWithStateEvents([aliceBeaconInfo1, aliceBeaconInfo2], { roomId, mockClient });
|
||||||
|
|
||||||
|
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
|
||||||
|
// beacon1 has been superceded by beacon2
|
||||||
|
expect(component.text()).toEqual("Live location ended");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders stopped UI when a beacon event is replaced', () => {
|
||||||
|
const aliceBeaconInfo1 = makeBeaconInfoEvent(
|
||||||
|
aliceId,
|
||||||
|
roomId,
|
||||||
|
// this one is a little older
|
||||||
|
{ isLive: true, timestamp: now - 500 },
|
||||||
|
'$alice-room1-1',
|
||||||
|
);
|
||||||
|
aliceBeaconInfo1.event.origin_server_ts = now - 500;
|
||||||
|
const aliceBeaconInfo2 = makeBeaconInfoEvent(
|
||||||
|
aliceId,
|
||||||
|
roomId,
|
||||||
|
{ isLive: true },
|
||||||
|
'$alice-room1-2',
|
||||||
|
);
|
||||||
|
|
||||||
|
const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient });
|
||||||
|
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
|
||||||
|
|
||||||
|
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo1));
|
||||||
|
// update alice's beacon with a new edition
|
||||||
|
// beacon instance emits
|
||||||
|
act(() => {
|
||||||
|
beaconInstance.update(aliceBeaconInfo2);
|
||||||
|
});
|
||||||
|
|
||||||
|
component.setProps({});
|
||||||
|
|
||||||
|
// beacon1 has been superceded by beacon2
|
||||||
|
expect(component.text()).toEqual("Live location ended");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
testBeaconStatuses();
|
||||||
|
|
||||||
describe('on liveness change', () => {
|
describe('on liveness change', () => {
|
||||||
it('renders stopped UI when a beacon stops being live', () => {
|
it('renders stopped UI when a beacon stops being live', () => {
|
||||||
|
@ -458,4 +466,34 @@ describe('<MBeaconBody />', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when map display is not configured', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// mock map utils to raise MapStyleUrlNotConfigured error
|
||||||
|
jest.spyOn(mapUtilHooks, 'useMap').mockImplementation(
|
||||||
|
({ onError }) => {
|
||||||
|
onError(new Error(LocationShareError.MapStyleUrlNotConfigured));
|
||||||
|
return mockMap;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders maps unavailable error for a live beacon with location', () => {
|
||||||
|
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
|
||||||
|
roomId,
|
||||||
|
{ isLive: true },
|
||||||
|
'$alice-room1-1',
|
||||||
|
);
|
||||||
|
const location1 = makeBeaconEvent(
|
||||||
|
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
makeRoomWithBeacons(roomId, mockClient, [beaconInfoEvent], [location1]);
|
||||||
|
|
||||||
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
|
expect(component.find(MapError)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// test that statuses display as expected with a map display error
|
||||||
|
testBeaconStatuses();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<MBeaconBody /> when map display is not configured renders maps unavailable error for a live beacon with location 1`] = `
|
||||||
|
<MapError
|
||||||
|
className="mx_MBeaconBody_mapError mx_MBeaconBody_mapErrorInteractive"
|
||||||
|
error="MapStyleUrlNotConfigured"
|
||||||
|
isMinimised={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mx_MapError mx_MBeaconBody_mapError mx_MBeaconBody_mapErrorInteractive mx_MapError_isMinimised"
|
||||||
|
data-test-id="map-rendering-error"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mx_MapError_icon"
|
||||||
|
/>
|
||||||
|
<Heading
|
||||||
|
className="mx_MapError_heading"
|
||||||
|
size="h3"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
className="mx_Heading_h3 mx_MapError_heading"
|
||||||
|
>
|
||||||
|
Unable to load map
|
||||||
|
</h3>
|
||||||
|
</Heading>
|
||||||
|
<p
|
||||||
|
className="mx_MapError_message"
|
||||||
|
>
|
||||||
|
This homeserver is not configured to display maps.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MapError>
|
||||||
|
`;
|
Loading…
Reference in New Issue