mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-25 09:58:11 +08:00
9ba55d1d14
* extract location markers into generic Marker Signed-off-by: Kerry Archibald <kerrya@element.io> * wrap marker in smartmarker Signed-off-by: Kerry Archibald <kerrya@element.io> * test smartmarker Signed-off-by: Kerry Archibald <kerrya@element.io> * working map in location body Signed-off-by: Kerry Archibald <kerrya@element.io> * test Map Signed-off-by: Kerry Archibald <kerrya@element.io> * remove skinned sdk Signed-off-by: Kerry Archibald <kerrya@element.io> * update snaps with new mocks Signed-off-by: Kerry Archibald <kerrya@element.io> * use new ZoomButtons in MLocationBody Signed-off-by: Kerry Archibald <kerrya@element.io> * make LocationViewDialog map interactive Signed-off-by: Kerry Archibald <kerrya@element.io> * test MLocationBody Signed-off-by: Kerry Archibald <kerrya@element.io> * test LocationViewDialog Signed-off-by: Kerry Archibald <kerrya@element.io> * add copyrights, shrink snapshot Signed-off-by: Kerry Archibald <kerrya@element.io> * update comment Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io>
326 lines
13 KiB
TypeScript
326 lines
13 KiB
TypeScript
/*
|
|
Copyright 2021 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 maplibregl from "maplibre-gl";
|
|
import { mount } from "enzyme";
|
|
import { act } from 'react-dom/test-utils';
|
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
|
import { mocked } from 'jest-mock';
|
|
import { logger } from 'matrix-js-sdk/src/logger';
|
|
|
|
import LocationPicker from "../../../../src/components/views/location/LocationPicker";
|
|
import { LocationShareType } from "../../../../src/components/views/location/shareLocation";
|
|
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
|
|
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
|
|
import { findById, findByTestId, mockPlatformPeg } from '../../../test-utils';
|
|
import { findMapStyleUrl, LocationShareError } from '../../../../src/utils/location';
|
|
|
|
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
|
|
findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'),
|
|
}));
|
|
|
|
// dropdown uses this
|
|
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
|
|
|
describe("LocationPicker", () => {
|
|
describe('<LocationPicker />', () => {
|
|
const roomId = '!room:server.org';
|
|
const userId = '@user:server.org';
|
|
const sender = new RoomMember(roomId, userId);
|
|
const defaultProps = {
|
|
sender,
|
|
shareType: LocationShareType.Own,
|
|
onChoose: jest.fn(),
|
|
onFinished: jest.fn(),
|
|
};
|
|
const mockClient = {
|
|
on: jest.fn(),
|
|
removeListener: jest.fn(),
|
|
off: jest.fn(),
|
|
isGuest: jest.fn(),
|
|
getClientWellKnown: jest.fn(),
|
|
};
|
|
const getComponent = (props = {}) => mount(<LocationPicker {...defaultProps} {...props} />, {
|
|
wrappingComponent: MatrixClientContext.Provider,
|
|
wrappingComponentProps: { value: mockClient },
|
|
});
|
|
|
|
const mockMap = new maplibregl.Map();
|
|
const mockGeolocate = new maplibregl.GeolocateControl();
|
|
const mockMarker = new maplibregl.Marker();
|
|
|
|
const mockGeolocationPosition = {
|
|
coords: {
|
|
latitude: 43.2,
|
|
longitude: 12.4,
|
|
altitude: 12.3,
|
|
accuracy: 21,
|
|
},
|
|
timestamp: 123,
|
|
};
|
|
const mockClickEvent = {
|
|
lngLat: {
|
|
lat: 43.2,
|
|
lng: 12.4,
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(logger, 'error').mockRestore();
|
|
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', () => {
|
|
// suppress expected error log
|
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
|
const wrapper = getComponent();
|
|
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockMap).emit('error', { error: 'Something went wrong' });
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
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', () => {
|
|
// suppress expected error log
|
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
|
|
|
// throw an error
|
|
mocked(mockMap).addControl.mockImplementation(() => { throw new Error('oups'); });
|
|
|
|
const wrapper = getComponent();
|
|
wrapper.setProps({});
|
|
|
|
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', () => {
|
|
getComponent();
|
|
|
|
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockMap).emit('load');
|
|
});
|
|
|
|
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
|
});
|
|
|
|
const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => {
|
|
describe('user location behaviours', () => {
|
|
it('closes and displays error when geolocation errors', () => {
|
|
// suppress expected error log
|
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
|
const onFinished = jest.fn();
|
|
getComponent({ onFinished, shareType });
|
|
|
|
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
|
act(() => {
|
|
// @ts-ignore
|
|
mockMap.emit('load');
|
|
// @ts-ignore
|
|
mockGeolocate.emit('error', {});
|
|
});
|
|
|
|
// dialog is closed on error
|
|
expect(onFinished).toHaveBeenCalled();
|
|
});
|
|
|
|
it('sets position on geolocate event', () => {
|
|
const wrapper = getComponent({ shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
// marker added
|
|
expect(maplibregl.Marker).toHaveBeenCalled();
|
|
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
|
|
12.4, 43.2,
|
|
));
|
|
// submit button is enabled when position is truthy
|
|
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
|
|
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
|
|
});
|
|
|
|
it('submits location', () => {
|
|
const onChoose = jest.fn();
|
|
const wrapper = getComponent({ onChoose, shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
|
// make sure button is enabled
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
act(() => {
|
|
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
|
|
});
|
|
|
|
// content of this call is tested in LocationShareMenu-test
|
|
expect(onChoose).toHaveBeenCalled();
|
|
});
|
|
});
|
|
};
|
|
|
|
describe('for Own location share type', () => {
|
|
testUserLocationShareTypes(LocationShareType.Own);
|
|
});
|
|
|
|
describe('for Live location share type', () => {
|
|
const shareType = LocationShareType.Live;
|
|
testUserLocationShareTypes(shareType);
|
|
|
|
const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0);
|
|
const getDropdown = wrapper => findByTestId(wrapper, 'live-duration-dropdown');
|
|
const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value');
|
|
|
|
const openDropdown = (wrapper) => act(() => {
|
|
const dropdown = getDropdown(wrapper);
|
|
dropdown.find('[role="button"]').at(0).simulate('click');
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
it('renders live duration dropdown with default option', () => {
|
|
const wrapper = getComponent({ shareType });
|
|
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 15m');
|
|
});
|
|
|
|
it('updates selected duration', () => {
|
|
const wrapper = getComponent({ shareType });
|
|
|
|
openDropdown(wrapper);
|
|
const dropdown = getDropdown(wrapper);
|
|
act(() => {
|
|
getOption(dropdown, 3600000).simulate('click');
|
|
});
|
|
|
|
// value updated
|
|
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 1h');
|
|
});
|
|
});
|
|
|
|
describe('for Pin drop location share type', () => {
|
|
const shareType = LocationShareType.Pin;
|
|
it('initiates map with geolocation', () => {
|
|
getComponent({ shareType });
|
|
|
|
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockMap).emit('load');
|
|
});
|
|
|
|
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
|
});
|
|
|
|
it('removes geolocation control on geolocation error', () => {
|
|
// suppress expected error log
|
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
|
const onFinished = jest.fn();
|
|
getComponent({ onFinished, shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mockMap.emit('load');
|
|
// @ts-ignore
|
|
mockGeolocate.emit('error', {});
|
|
});
|
|
|
|
expect(mockMap.removeControl).toHaveBeenCalledWith(mockGeolocate);
|
|
// dialog is not closed
|
|
expect(onFinished).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not set position on geolocate event', () => {
|
|
mocked(maplibregl.Marker).mockClear();
|
|
const wrapper = getComponent({ shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
|
});
|
|
|
|
// marker not added
|
|
expect(wrapper.find('Marker').length).toBeFalsy();
|
|
});
|
|
|
|
it('sets position on click event', () => {
|
|
const wrapper = getComponent({ shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockMap).emit('click', mockClickEvent);
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
// marker added
|
|
expect(maplibregl.Marker).toHaveBeenCalled();
|
|
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
|
|
12.4, 43.2,
|
|
));
|
|
|
|
// marker is set, icon not avatar
|
|
expect(wrapper.find('.mx_Marker_icon').length).toBeTruthy();
|
|
});
|
|
|
|
it('submits location', () => {
|
|
const onChoose = jest.fn();
|
|
const wrapper = getComponent({ onChoose, shareType });
|
|
act(() => {
|
|
// @ts-ignore
|
|
mocked(mockMap).emit('click', mockClickEvent);
|
|
wrapper.setProps({});
|
|
});
|
|
|
|
act(() => {
|
|
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
|
|
});
|
|
|
|
// content of this call is tested in LocationShareMenu-test
|
|
expect(onChoose).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
});
|