Live location sharing: set map bounds to include all locations (#8324)
* open a dialog with map centered around first beacon Signed-off-by: Kerry Archibald <kerrya@element.io> * test dialog opening from beacon body Signed-off-by: Kerry Archibald <kerrya@element.io> * test beaconmarker Signed-off-by: Kerry Archibald <kerrya@element.io> * add bounds to Map comp Signed-off-by: Kerry Archibald <kerrya@element.io> * add focusBeacon to beaconviewdialog, use bounds Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * use membercolor on beacon view markers Signed-off-by: Kerry Archibald <kerrya@element.io> * add lnglatbounds to maplibre mock Signed-off-by: Kerry Archibald <kerrya@element.io> * update snapshots for expanded maplibre Map mock Signed-off-by: Kerry Archibald <kerrya@element.io> * test map bounds Signed-off-by: Kerry Archibald <kerrya@element.io> * tidy copy paste comment Signed-off-by: Kerry Archibald <kerrya@element.io> * add fallback when no more live locations Signed-off-by: Kerry Archibald <kerrya@element.io> * accurate signature for getBoundsCenter Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
6b13988eaa
commit
f70186ea9b
16 changed files with 246 additions and 21 deletions
|
@ -1,5 +1,5 @@
|
||||||
const EventEmitter = require("events");
|
const EventEmitter = require("events");
|
||||||
const { LngLat, NavigationControl } = require('maplibre-gl');
|
const { LngLat, NavigationControl, LngLatBounds } = require('maplibre-gl');
|
||||||
|
|
||||||
class MockMap extends EventEmitter {
|
class MockMap extends EventEmitter {
|
||||||
addControl = jest.fn();
|
addControl = jest.fn();
|
||||||
|
@ -8,6 +8,7 @@ class MockMap extends EventEmitter {
|
||||||
zoomOut = jest.fn();
|
zoomOut = jest.fn();
|
||||||
setCenter = jest.fn();
|
setCenter = jest.fn();
|
||||||
setStyle = jest.fn();
|
setStyle = jest.fn();
|
||||||
|
fitBounds = jest.fn();
|
||||||
}
|
}
|
||||||
const MockMapInstance = new MockMap();
|
const MockMapInstance = new MockMap();
|
||||||
|
|
||||||
|
@ -24,5 +25,6 @@ module.exports = {
|
||||||
GeolocateControl: jest.fn().mockReturnValue(MockGeolocateInstance),
|
GeolocateControl: jest.fn().mockReturnValue(MockGeolocateInstance),
|
||||||
Marker: jest.fn().mockReturnValue(MockMarker),
|
Marker: jest.fn().mockReturnValue(MockMarker),
|
||||||
LngLat,
|
LngLat,
|
||||||
|
LngLatBounds,
|
||||||
NavigationControl,
|
NavigationControl,
|
||||||
};
|
};
|
||||||
|
|
|
@ -55,3 +55,25 @@ limitations under the License.
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_BeaconViewDialog_mapFallback {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background: url('$(res)/img/location/map.svg');
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BeaconViewDialog_mapFallbackIcon {
|
||||||
|
width: 65px;
|
||||||
|
margin-bottom: $spacing-16;
|
||||||
|
color: $quaternary-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BeaconViewDialog_mapFallbackMessage {
|
||||||
|
color: $secondary-content;
|
||||||
|
margin-bottom: $spacing-16;
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ const BeaconMarker: React.FC<Props> = ({ map, beacon }) => {
|
||||||
id={beacon.identifier}
|
id={beacon.identifier}
|
||||||
geoUri={geoUri}
|
geoUri={geoUri}
|
||||||
roomMember={markerRoomMember}
|
roomMember={markerRoomMember}
|
||||||
|
useMemberColor
|
||||||
/>;
|
/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,29 +29,43 @@ import { IDialogProps } from "../dialogs/IDialogProps";
|
||||||
import Map from '../location/Map';
|
import Map from '../location/Map';
|
||||||
import ZoomButtons from '../location/ZoomButtons';
|
import ZoomButtons from '../location/ZoomButtons';
|
||||||
import BeaconMarker from './BeaconMarker';
|
import BeaconMarker from './BeaconMarker';
|
||||||
|
import { Bounds, getBeaconBounds } from '../../../utils/beacon/bounds';
|
||||||
|
import { getGeoUri } from '../../../utils/beacon';
|
||||||
|
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
roomId: Room['roomId'];
|
roomId: Room['roomId'];
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
// open the map centered on this beacon's location
|
||||||
|
focusBeacon?: Beacon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO actual center is coming soon
|
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
||||||
// for now just center around first beacon in list
|
if (!bounds) {
|
||||||
const getMapCenterUri = (beacons: Beacon[]): string => {
|
return;
|
||||||
const firstBeaconWithLocation = beacons.find(beacon => beacon.latestLocationState);
|
}
|
||||||
|
return getGeoUri({
|
||||||
return firstBeaconWithLocation?.latestLocationState?.uri;
|
latitude: (bounds.north + bounds.south) / 2,
|
||||||
|
longitude: (bounds.east + bounds.west) / 2,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog to view live beacons maximised
|
* Dialog to view live beacons maximised
|
||||||
*/
|
*/
|
||||||
const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }) => {
|
const BeaconViewDialog: React.FC<IProps> = ({
|
||||||
|
focusBeacon,
|
||||||
|
roomId,
|
||||||
|
matrixClient,
|
||||||
|
onFinished,
|
||||||
|
}) => {
|
||||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||||
|
|
||||||
const mapCenterUri = getMapCenterUri(liveBeacons);
|
const bounds = getBeaconBounds(liveBeacons);
|
||||||
// TODO probably show loader or placeholder when there is no location
|
const centerGeoUri = focusBeacon?.latestLocationState?.uri || getBoundsCenter(bounds);
|
||||||
// to center the map on
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
|
@ -60,9 +74,10 @@ const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
>
|
>
|
||||||
<MatrixClientContext.Provider value={matrixClient}>
|
<MatrixClientContext.Provider value={matrixClient}>
|
||||||
<Map
|
{ !!bounds ? <Map
|
||||||
id='mx_BeaconViewDialog'
|
id='mx_BeaconViewDialog'
|
||||||
centerGeoUri={mapCenterUri}
|
bounds={bounds}
|
||||||
|
centerGeoUri={centerGeoUri}
|
||||||
interactive
|
interactive
|
||||||
className="mx_BeaconViewDialog_map"
|
className="mx_BeaconViewDialog_map"
|
||||||
>
|
>
|
||||||
|
@ -77,7 +92,22 @@ const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }
|
||||||
<ZoomButtons map={map} />
|
<ZoomButtons map={map} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Map>
|
</Map> :
|
||||||
|
<div
|
||||||
|
data-test-id='beacon-view-dialog-map-fallback'
|
||||||
|
className='mx_BeaconViewDialog_map mx_BeaconViewDialog_mapFallback'
|
||||||
|
>
|
||||||
|
<LocationIcon className='mx_BeaconViewDialog_mapFallbackIcon' />
|
||||||
|
<span className='mx_BeaconViewDialog_mapFallbackMessage'>{ _t('No live locations') }</span>
|
||||||
|
<AccessibleButton
|
||||||
|
kind='primary'
|
||||||
|
onClick={onFinished}
|
||||||
|
data-test-id='beacon-view-dialog-fallback-close'
|
||||||
|
>
|
||||||
|
{ _t('Close') }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</MatrixClientContext.Provider>
|
</MatrixClientContext.Provider>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { ReactNode, useContext, useEffect } from 'react';
|
import React, { ReactNode, useContext, useEffect } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import maplibregl from 'maplibre-gl';
|
||||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/matrix';
|
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/matrix';
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
|
|
||||||
|
@ -24,8 +25,9 @@ import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||||
import { parseGeoUri } from '../../../utils/location';
|
import { parseGeoUri } 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';
|
||||||
|
|
||||||
const useMapWithStyle = ({ id, centerGeoUri, onError, interactive }) => {
|
const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) => {
|
||||||
const bodyId = `mx_Map_${id}`;
|
const bodyId = `mx_Map_${id}`;
|
||||||
|
|
||||||
// style config
|
// style config
|
||||||
|
@ -55,6 +57,20 @@ const useMapWithStyle = ({ id, centerGeoUri, onError, interactive }) => {
|
||||||
}
|
}
|
||||||
}, [map, centerGeoUri]);
|
}, [map, centerGeoUri]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (map && bounds) {
|
||||||
|
try {
|
||||||
|
const lngLatBounds = new maplibregl.LngLatBounds(
|
||||||
|
[bounds.west, bounds.south],
|
||||||
|
[bounds.east, bounds.north],
|
||||||
|
);
|
||||||
|
map.fitBounds(lngLatBounds, { padding: 100 });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Invalid map bounds', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [map, bounds]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
map,
|
map,
|
||||||
bodyId,
|
bodyId,
|
||||||
|
@ -65,6 +81,7 @@ interface MapProps {
|
||||||
id: string;
|
id: string;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
centerGeoUri?: string;
|
centerGeoUri?: string;
|
||||||
|
bounds?: Bounds;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
|
@ -74,9 +91,15 @@ interface MapProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Map: React.FC<MapProps> = ({
|
const Map: React.FC<MapProps> = ({
|
||||||
centerGeoUri, className, id, onError, onClick, children, interactive,
|
bounds,
|
||||||
|
centerGeoUri,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
id,
|
||||||
|
interactive,
|
||||||
|
onError, onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive });
|
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds });
|
||||||
|
|
||||||
const onMapClick = (
|
const onMapClick = (
|
||||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||||
|
|
|
@ -32,8 +32,8 @@ import Spinner from '../elements/Spinner';
|
||||||
import Map from '../location/Map';
|
import Map from '../location/Map';
|
||||||
import SmartMarker from '../location/SmartMarker';
|
import SmartMarker from '../location/SmartMarker';
|
||||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||||
import { IBodyProps } from "./IBodyProps";
|
|
||||||
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
||||||
|
import { IBodyProps } from "./IBodyProps";
|
||||||
|
|
||||||
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
|
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
|
||||||
beacon?: Beacon;
|
beacon?: Beacon;
|
||||||
|
@ -105,6 +105,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
|
||||||
{
|
{
|
||||||
roomId: mxEvent.getRoomId(),
|
roomId: mxEvent.getRoomId(),
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
focusBeacon: beacon,
|
||||||
},
|
},
|
||||||
"mx_BeaconViewDialog_wrapper",
|
"mx_BeaconViewDialog_wrapper",
|
||||||
false, // isPriority
|
false, // isPriority
|
||||||
|
|
|
@ -2915,6 +2915,7 @@
|
||||||
"Loading live location...": "Loading live location...",
|
"Loading live location...": "Loading live location...",
|
||||||
"Live location ended": "Live location ended",
|
"Live location ended": "Live location ended",
|
||||||
"Live location error": "Live location error",
|
"Live location error": "Live location error",
|
||||||
|
"No live locations": "No live locations",
|
||||||
"An error occured whilst sharing your live location": "An error occured whilst sharing your live location",
|
"An error occured whilst sharing your live location": "An error occured whilst sharing your live location",
|
||||||
"You are sharing your live location": "You are sharing your live location",
|
"You are sharing your live location": "You are sharing your live location",
|
||||||
"%(timeRemaining)s left": "%(timeRemaining)s left",
|
"%(timeRemaining)s left": "%(timeRemaining)s left",
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
|
|
||||||
import BeaconViewDialog from '../../../../src/components/views/beacon/BeaconViewDialog';
|
import BeaconViewDialog from '../../../../src/components/views/beacon/BeaconViewDialog';
|
||||||
import {
|
import {
|
||||||
|
findByTestId,
|
||||||
getMockClientWithEventEmitter,
|
getMockClientWithEventEmitter,
|
||||||
makeBeaconEvent,
|
makeBeaconEvent,
|
||||||
makeBeaconInfoEvent,
|
makeBeaconInfoEvent,
|
||||||
|
@ -118,4 +119,37 @@ describe('<BeaconViewDialog />', () => {
|
||||||
// two markers now!
|
// two markers now!
|
||||||
expect(component.find('BeaconMarker').length).toEqual(2);
|
expect(component.find('BeaconMarker').length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders a fallback when no live beacons remain', () => {
|
||||||
|
const onFinished = jest.fn();
|
||||||
|
const room = makeRoomWithStateEvents([defaultEvent]);
|
||||||
|
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
|
||||||
|
beacon.addLocations([location1]);
|
||||||
|
const component = getComponent({ onFinished });
|
||||||
|
expect(component.find('BeaconMarker').length).toEqual(1);
|
||||||
|
|
||||||
|
// this will replace the defaultEvent
|
||||||
|
// leading to no more live beacons
|
||||||
|
const anotherBeaconEvent = makeBeaconInfoEvent(aliceId,
|
||||||
|
roomId,
|
||||||
|
{ isLive: false },
|
||||||
|
'$bob-room1-1',
|
||||||
|
);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
// emits RoomStateEvent.BeaconLiveness
|
||||||
|
room.currentState.setStateEvents([anotherBeaconEvent]);
|
||||||
|
});
|
||||||
|
|
||||||
|
component.setProps({});
|
||||||
|
|
||||||
|
// map placeholder
|
||||||
|
expect(findByTestId(component, 'beacon-view-dialog-map-fallback')).toMatchSnapshot();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
findByTestId(component, 'beacon-view-dialog-fallback-close').at(0).simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onFinished).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,6 +61,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
"_eventsCount": 0,
|
"_eventsCount": 0,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction],
|
"setCenter": [MockFunction],
|
||||||
"setStyle": [MockFunction],
|
"setStyle": [MockFunction],
|
||||||
|
@ -79,6 +80,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
"_eventsCount": 0,
|
"_eventsCount": 0,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction],
|
"setCenter": [MockFunction],
|
||||||
"setStyle": [MockFunction],
|
"setStyle": [MockFunction],
|
||||||
|
@ -111,6 +113,7 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
Symbol(kCapture): false,
|
Symbol(kCapture): false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
useMemberColor={true}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<ForwardRef
|
<ForwardRef
|
||||||
|
@ -139,9 +142,10 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
|
||||||
Symbol(kCapture): false,
|
Symbol(kCapture): false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
useMemberColor={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx_Marker mx_Marker_defaultColor"
|
className="mx_Marker mx_Username_color4"
|
||||||
id="!room:server_@alice:server"
|
id="!room:server_@alice:server"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<BeaconViewDialog /> renders a fallback when no live beacons remain 1`] = `
|
||||||
|
<div
|
||||||
|
className="mx_BeaconViewDialog_map mx_BeaconViewDialog_mapFallback"
|
||||||
|
data-test-id="beacon-view-dialog-map-fallback"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mx_BeaconViewDialog_mapFallbackIcon"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="mx_BeaconViewDialog_mapFallbackMessage"
|
||||||
|
>
|
||||||
|
No live locations
|
||||||
|
</span>
|
||||||
|
<AccessibleButton
|
||||||
|
data-test-id="beacon-view-dialog-fallback-close"
|
||||||
|
element="div"
|
||||||
|
kind="primary"
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||||
|
data-test-id="beacon-view-dialog-fallback-close"
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
onKeyUp={[Function]}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -115,6 +115,38 @@ describe('<Map />', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('map bounds', () => {
|
||||||
|
it('does not try to fit map bounds when no bounds provided', () => {
|
||||||
|
getComponent({ bounds: null });
|
||||||
|
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fits map to bounds', () => {
|
||||||
|
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
||||||
|
getComponent({ bounds });
|
||||||
|
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds([bounds.west, bounds.south],
|
||||||
|
[bounds.east, bounds.north]), { padding: 100 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles invalid bounds', () => {
|
||||||
|
const logSpy = jest.spyOn(logger, 'error').mockImplementation();
|
||||||
|
const bounds = { north: 'a', south: 'b', east: 42, west: 41 };
|
||||||
|
getComponent({ bounds });
|
||||||
|
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Invalid map bounds', new Error('Invalid LngLat object: (41, NaN)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates map bounds when bounds prop changes', () => {
|
||||||
|
const component = getComponent({ centerGeoUri: 'geo:51,42' });
|
||||||
|
|
||||||
|
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
||||||
|
const bounds2 = { north: 53, south: 51, east: 45, west: 44 };
|
||||||
|
component.setProps({ bounds });
|
||||||
|
component.setProps({ bounds: bounds2 });
|
||||||
|
expect(mockMap.fitBounds).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('children', () => {
|
describe('children', () => {
|
||||||
it('renders without children', () => {
|
it('renders without children', () => {
|
||||||
const component = getComponent({ children: null });
|
const component = getComponent({ children: null });
|
||||||
|
|
|
@ -24,6 +24,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
"_eventsCount": 1,
|
"_eventsCount": 1,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction] {
|
"setCenter": [MockFunction] {
|
||||||
"calls": Array [
|
"calls": Array [
|
||||||
|
@ -76,6 +77,7 @@ exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||||
"_eventsCount": 1,
|
"_eventsCount": 1,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction] {
|
"setCenter": [MockFunction] {
|
||||||
"calls": Array [
|
"calls": Array [
|
||||||
|
|
|
@ -9,6 +9,7 @@ exports[`<SmartMarker /> creates a marker on mount 1`] = `
|
||||||
"_eventsCount": 0,
|
"_eventsCount": 0,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction],
|
"setCenter": [MockFunction],
|
||||||
"setStyle": [MockFunction],
|
"setStyle": [MockFunction],
|
||||||
|
@ -45,6 +46,7 @@ exports[`<SmartMarker /> removes marker on unmount 1`] = `
|
||||||
"_eventsCount": 0,
|
"_eventsCount": 0,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction],
|
"setCenter": [MockFunction],
|
||||||
"setStyle": [MockFunction],
|
"setStyle": [MockFunction],
|
||||||
|
|
|
@ -8,6 +8,7 @@ exports[`<ZoomButtons /> renders buttons 1`] = `
|
||||||
"_eventsCount": 0,
|
"_eventsCount": 0,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction],
|
"setCenter": [MockFunction],
|
||||||
"setStyle": [MockFunction],
|
"setStyle": [MockFunction],
|
||||||
|
|
|
@ -86,7 +86,6 @@ describe('<MBeaconBody />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const modalSpy = jest.spyOn(Modal, 'createTrackedDialog').mockReturnValue(undefined);
|
const modalSpy = jest.spyOn(Modal, 'createTrackedDialog').mockReturnValue(undefined);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
@ -123,7 +122,6 @@ describe('<MBeaconBody />', () => {
|
||||||
);
|
);
|
||||||
makeRoomWithStateEvents([beaconInfoEvent]);
|
makeRoomWithStateEvents([beaconInfoEvent]);
|
||||||
const component = getComponent({ mxEvent: beaconInfoEvent });
|
const component = getComponent({ mxEvent: beaconInfoEvent });
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
component.find('.mx_MBeaconBody_map').simulate('click');
|
component.find('.mx_MBeaconBody_map').simulate('click');
|
||||||
});
|
});
|
||||||
|
@ -268,6 +266,40 @@ describe('<MBeaconBody />', () => {
|
||||||
expect(modalSpy).toHaveBeenCalled();
|
expect(modalSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does nothing on click when a beacon has no location', () => {
|
||||||
|
makeRoomWithStateEvents([aliceBeaconInfo]);
|
||||||
|
const component = getComponent({ mxEvent: aliceBeaconInfo });
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
component.find('.mx_MBeaconBody_map').simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(modalSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a live beacon with a location correctly', () => {
|
||||||
|
const room = makeRoomWithStateEvents([aliceBeaconInfo]);
|
||||||
|
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
|
||||||
|
beaconInstance.addLocations([location1]);
|
||||||
|
const component = getComponent({ mxEvent: aliceBeaconInfo });
|
||||||
|
|
||||||
|
expect(component.find('Map').length).toBeTruthy;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens maximised map view on click when beacon has a live location', () => {
|
||||||
|
const room = makeRoomWithStateEvents([aliceBeaconInfo]);
|
||||||
|
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
|
||||||
|
beaconInstance.addLocations([location1]);
|
||||||
|
const component = getComponent({ mxEvent: aliceBeaconInfo });
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
component.find('Map').simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
// opens modal
|
||||||
|
expect(modalSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('updates latest location', () => {
|
it('updates latest location', () => {
|
||||||
const room = makeRoomWithStateEvents([aliceBeaconInfo]);
|
const room = makeRoomWithStateEvents([aliceBeaconInfo]);
|
||||||
const component = getComponent({ mxEvent: aliceBeaconInfo });
|
const component = getComponent({ mxEvent: aliceBeaconInfo });
|
||||||
|
|
|
@ -124,6 +124,7 @@ exports[`MLocationBody <MLocationBody> without error renders map correctly 1`] =
|
||||||
"_eventsCount": 1,
|
"_eventsCount": 1,
|
||||||
"_maxListeners": undefined,
|
"_maxListeners": undefined,
|
||||||
"addControl": [MockFunction],
|
"addControl": [MockFunction],
|
||||||
|
"fitBounds": [MockFunction],
|
||||||
"removeControl": [MockFunction],
|
"removeControl": [MockFunction],
|
||||||
"setCenter": [MockFunction] {
|
"setCenter": [MockFunction] {
|
||||||
"calls": Array [
|
"calls": Array [
|
||||||
|
|
Loading…
Reference in a new issue