2022-03-01 20:00:07 +03:00
|
|
|
/*
|
|
|
|
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';
|
2022-03-15 18:04:52 +03:00
|
|
|
import { mount, ReactWrapper } from 'enzyme';
|
2022-03-02 02:51:05 +03:00
|
|
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
2022-03-02 16:00:40 +03:00
|
|
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
|
|
|
import { mocked } from 'jest-mock';
|
|
|
|
import { act } from 'react-dom/test-utils';
|
2022-03-18 12:52:24 +03:00
|
|
|
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
|
2022-03-10 21:03:31 +03:00
|
|
|
import { M_ASSET, LocationAssetType } from 'matrix-js-sdk/src/@types/location';
|
2022-03-18 12:52:24 +03:00
|
|
|
import { logger } from 'matrix-js-sdk/src/logger';
|
2022-03-01 20:00:07 +03:00
|
|
|
|
|
|
|
import LocationShareMenu from '../../../../src/components/views/location/LocationShareMenu';
|
|
|
|
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
|
|
|
|
import { ChevronFace } from '../../../../src/components/structures/ContextMenu';
|
2022-03-02 16:00:40 +03:00
|
|
|
import SettingsStore from '../../../../src/settings/SettingsStore';
|
|
|
|
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
|
2022-03-09 20:14:07 +03:00
|
|
|
import { LocationShareType } from '../../../../src/components/views/location/shareLocation';
|
2022-03-18 12:52:24 +03:00
|
|
|
import { findByTagAndTestId, flushPromises } from '../../../test-utils';
|
|
|
|
import Modal from '../../../../src/Modal';
|
2022-03-21 14:42:58 +03:00
|
|
|
import { DEFAULT_DURATION_MS } from '../../../../src/components/views/location/LiveDurationDropdown';
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-25 17:36:22 +03:00
|
|
|
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
|
2022-03-02 16:00:40 +03:00
|
|
|
findMapStyleUrl: jest.fn().mockReturnValue('test'),
|
|
|
|
}));
|
|
|
|
|
|
|
|
jest.mock('../../../../src/settings/SettingsStore', () => ({
|
|
|
|
getValue: jest.fn(),
|
|
|
|
monitorSetting: jest.fn(),
|
|
|
|
}));
|
|
|
|
|
|
|
|
jest.mock('../../../../src/stores/OwnProfileStore', () => ({
|
|
|
|
OwnProfileStore: {
|
|
|
|
instance: {
|
|
|
|
displayName: 'Ernie',
|
|
|
|
getHttpAvatarUrl: jest.fn().mockReturnValue('image.com/img'),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}));
|
2022-03-01 20:00:07 +03:00
|
|
|
|
2022-03-18 12:52:24 +03:00
|
|
|
jest.mock('../../../../src/Modal', () => ({
|
|
|
|
createTrackedDialog: jest.fn(),
|
|
|
|
}));
|
|
|
|
|
2022-03-01 20:00:07 +03:00
|
|
|
describe('<LocationShareMenu />', () => {
|
2022-03-02 16:00:40 +03:00
|
|
|
const userId = '@ernie:server.org';
|
2022-03-01 20:00:07 +03:00
|
|
|
const mockClient = {
|
|
|
|
on: jest.fn(),
|
2022-03-03 13:30:46 +03:00
|
|
|
off: jest.fn(),
|
2022-03-02 16:00:40 +03:00
|
|
|
removeListener: jest.fn(),
|
|
|
|
getUserId: jest.fn().mockReturnValue(userId),
|
|
|
|
getClientWellKnown: jest.fn().mockResolvedValue({
|
|
|
|
map_style_url: 'maps.com',
|
|
|
|
}),
|
2022-03-09 20:14:07 +03:00
|
|
|
sendMessage: jest.fn(),
|
2022-03-18 12:52:24 +03:00
|
|
|
unstable_createLiveBeacon: jest.fn().mockResolvedValue({}),
|
2022-03-01 20:00:07 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const defaultProps = {
|
|
|
|
menuPosition: {
|
|
|
|
top: 1, left: 1,
|
|
|
|
chevronFace: ChevronFace.Bottom,
|
|
|
|
},
|
|
|
|
onFinished: jest.fn(),
|
|
|
|
openMenu: jest.fn(),
|
|
|
|
roomId: '!room:server.org',
|
2022-03-02 16:00:40 +03:00
|
|
|
sender: new RoomMember('!room:server.org', userId),
|
2022-03-01 20:00:07 +03:00
|
|
|
};
|
2022-03-09 20:14:07 +03:00
|
|
|
|
|
|
|
const position = {
|
|
|
|
coords: {
|
|
|
|
latitude: -36.24484561954707,
|
|
|
|
longitude: 175.46884959563613,
|
|
|
|
accuracy: 10,
|
|
|
|
},
|
|
|
|
timestamp: 1646305006802,
|
|
|
|
type: 'geolocate',
|
|
|
|
};
|
|
|
|
|
2022-03-01 20:00:07 +03:00
|
|
|
const getComponent = (props = {}) =>
|
|
|
|
mount(<LocationShareMenu {...defaultProps} {...props} />, {
|
|
|
|
wrappingComponent: MatrixClientContext.Provider,
|
|
|
|
wrappingComponentProps: { value: mockClient },
|
|
|
|
});
|
|
|
|
|
2022-03-02 16:00:40 +03:00
|
|
|
beforeEach(() => {
|
2022-03-18 12:52:24 +03:00
|
|
|
jest.spyOn(logger, 'error').mockRestore();
|
2022-03-15 18:04:52 +03:00
|
|
|
mocked(SettingsStore).getValue.mockReturnValue(false);
|
2022-03-09 20:14:07 +03:00
|
|
|
mockClient.sendMessage.mockClear();
|
2022-03-18 12:52:24 +03:00
|
|
|
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue(undefined);
|
2022-03-02 16:00:40 +03:00
|
|
|
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
|
2022-03-18 12:52:24 +03:00
|
|
|
mocked(Modal).createTrackedDialog.mockClear();
|
2022-03-02 16:00:40 +03:00
|
|
|
});
|
|
|
|
|
2022-03-15 18:04:52 +03:00
|
|
|
const getShareTypeOption = (component: ReactWrapper, shareType: LocationShareType) =>
|
2022-03-16 12:37:09 +03:00
|
|
|
findByTagAndTestId(component, `share-location-option-${shareType}`, 'button');
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
const getBackButton = (component: ReactWrapper) =>
|
2022-03-16 12:37:09 +03:00
|
|
|
findByTagAndTestId(component, 'share-dialog-buttons-back', 'button');
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
const getCancelButton = (component: ReactWrapper) =>
|
2022-03-16 12:37:09 +03:00
|
|
|
findByTagAndTestId(component, 'share-dialog-buttons-cancel', 'button');
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
const getSubmitButton = (component: ReactWrapper) =>
|
2022-03-16 12:37:09 +03:00
|
|
|
findByTagAndTestId(component, 'location-picker-submit-button', 'button');
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
const setLocation = (component: ReactWrapper) => {
|
2022-03-09 20:14:07 +03:00
|
|
|
// set the location
|
|
|
|
const locationPickerInstance = component.find('LocationPicker').instance();
|
|
|
|
act(() => {
|
|
|
|
// @ts-ignore
|
|
|
|
locationPickerInstance.onGeolocate(position);
|
|
|
|
// make sure button gets enabled
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
};
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-15 18:04:52 +03:00
|
|
|
const setShareType = (component: ReactWrapper, shareType: LocationShareType) =>
|
|
|
|
act(() => {
|
|
|
|
getShareTypeOption(component, shareType).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-15 18:04:52 +03:00
|
|
|
describe('when only Own share type is enabled', () => {
|
|
|
|
beforeEach(() => enableSettings([]));
|
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
it('renders location picker when only Own share type is enabled', () => {
|
|
|
|
const component = getComponent();
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('ShareType').length).toBe(0);
|
|
|
|
expect(component.find('LocationPicker').length).toBe(1);
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render back button when only Own share type is enabled', () => {
|
|
|
|
const component = getComponent();
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(getBackButton(component).length).toBe(0);
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('clicking cancel button from location picker closes dialog', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
act(() => {
|
|
|
|
getCancelButton(component).at(0).simulate('click');
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(onFinished).toHaveBeenCalled();
|
|
|
|
});
|
2022-03-09 20:14:07 +03:00
|
|
|
|
|
|
|
it('creates static own location share event on submission', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
|
|
|
|
|
|
|
setLocation(component);
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getSubmitButton(component).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(onFinished).toHaveBeenCalled();
|
|
|
|
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
|
|
|
|
expect(messageRoomId).toEqual(defaultProps.roomId);
|
|
|
|
expect(relation).toEqual(null);
|
|
|
|
expect(messageBody).toEqual(expect.objectContaining({
|
2022-03-10 21:03:31 +03:00
|
|
|
[M_ASSET.name]: {
|
2022-03-09 20:14:07 +03:00
|
|
|
type: LocationAssetType.Self,
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
});
|
2022-03-02 16:00:40 +03:00
|
|
|
});
|
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
describe('with pin drop share type enabled', () => {
|
2022-03-15 18:04:52 +03:00
|
|
|
beforeEach(() => enableSettings(["feature_location_share_pin_drop"]));
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
it('renders share type switch with own and pin drop options', () => {
|
|
|
|
const component = getComponent();
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('LocationPicker').length).toBe(0);
|
2022-03-03 13:30:46 +03:00
|
|
|
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
|
|
|
|
expect(getShareTypeOption(component, LocationShareType.Pin).length).toBe(1);
|
2022-03-02 16:00:40 +03:00
|
|
|
});
|
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
it('does not render back button on share type screen', () => {
|
|
|
|
const component = getComponent();
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(getBackButton(component).length).toBe(0);
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('clicking cancel button from share type screen closes dialog', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getCancelButton(component).at(0).simulate('click');
|
|
|
|
});
|
2022-03-02 16:00:40 +03:00
|
|
|
|
2022-03-03 13:30:46 +03:00
|
|
|
expect(onFinished).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('selecting own location share type advances to location picker', () => {
|
|
|
|
const component = getComponent();
|
|
|
|
|
2022-03-09 20:14:07 +03:00
|
|
|
setShareType(component, LocationShareType.Own);
|
2022-03-03 13:30:46 +03:00
|
|
|
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('LocationPicker').length).toBe(1);
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('clicking back button from location picker screen goes back to share screen', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
|
|
|
|
|
|
|
// advance to location picker
|
2022-03-09 20:14:07 +03:00
|
|
|
setShareType(component, LocationShareType.Own);
|
2022-03-03 13:30:46 +03:00
|
|
|
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('LocationPicker').length).toBe(1);
|
2022-03-03 13:30:46 +03:00
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getBackButton(component).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
|
|
|
|
// back to share type
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('ShareType').length).toBe(1);
|
2022-03-03 13:30:46 +03:00
|
|
|
});
|
2022-03-09 20:14:07 +03:00
|
|
|
|
|
|
|
it('creates pin drop location share event on submission', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
|
|
|
|
|
|
|
// advance to location picker
|
|
|
|
setShareType(component, LocationShareType.Pin);
|
|
|
|
|
|
|
|
setLocation(component);
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getSubmitButton(component).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(onFinished).toHaveBeenCalled();
|
|
|
|
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
|
|
|
|
expect(messageRoomId).toEqual(defaultProps.roomId);
|
|
|
|
expect(relation).toEqual(null);
|
|
|
|
expect(messageBody).toEqual(expect.objectContaining({
|
2022-03-10 21:03:31 +03:00
|
|
|
[M_ASSET.name]: {
|
2022-03-09 20:14:07 +03:00
|
|
|
type: LocationAssetType.Pin,
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
});
|
2022-03-01 20:00:07 +03:00
|
|
|
});
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
describe('with live location and pin drop enabled', () => {
|
|
|
|
beforeEach(() => enableSettings([
|
|
|
|
"feature_location_share_pin_drop",
|
|
|
|
"feature_location_share_live",
|
|
|
|
]));
|
|
|
|
|
|
|
|
it('renders share type switch with all 3 options', () => {
|
|
|
|
// Given pin and live feature flags are enabled
|
|
|
|
// When I click Location
|
|
|
|
const component = getComponent();
|
|
|
|
|
|
|
|
// The the Location picker is not visible yet
|
2022-03-16 12:37:09 +03:00
|
|
|
expect(component.find('LocationPicker').length).toBe(0);
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
// And all 3 buttons are visible on the LocationShare dialog
|
|
|
|
expect(
|
|
|
|
getShareTypeOption(component, LocationShareType.Own).length,
|
2022-03-16 12:37:09 +03:00
|
|
|
).toBe(1);
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
expect(
|
|
|
|
getShareTypeOption(component, LocationShareType.Pin).length,
|
2022-03-16 12:37:09 +03:00
|
|
|
).toBe(1);
|
2022-03-15 18:04:52 +03:00
|
|
|
|
2022-03-16 12:37:09 +03:00
|
|
|
const liveButton = getShareTypeOption(component, LocationShareType.Live);
|
|
|
|
expect(liveButton.length).toBe(1);
|
|
|
|
|
|
|
|
// The live location button is enabled
|
|
|
|
expect(liveButton.hasClass("mx_AccessibleButton_disabled")).toBeFalsy();
|
2022-03-15 18:04:52 +03:00
|
|
|
});
|
|
|
|
});
|
2022-03-18 12:52:24 +03:00
|
|
|
|
|
|
|
describe('Live location share', () => {
|
|
|
|
beforeEach(() => enableSettings(["feature_location_share_live"]));
|
|
|
|
|
|
|
|
it('creates beacon info event on submission', () => {
|
|
|
|
const onFinished = jest.fn();
|
|
|
|
const component = getComponent({ onFinished });
|
|
|
|
|
|
|
|
// advance to location picker
|
|
|
|
setShareType(component, LocationShareType.Live);
|
|
|
|
setLocation(component);
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getSubmitButton(component).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(onFinished).toHaveBeenCalled();
|
|
|
|
const [eventRoomId, eventContent, eventTypeSuffix] = mockClient.unstable_createLiveBeacon.mock.calls[0];
|
|
|
|
expect(eventRoomId).toEqual(defaultProps.roomId);
|
|
|
|
expect(eventTypeSuffix).toBeTruthy();
|
|
|
|
expect(eventContent).toEqual(expect.objectContaining({
|
|
|
|
[M_BEACON_INFO.name]: {
|
|
|
|
// default timeout
|
2022-03-21 14:42:58 +03:00
|
|
|
timeout: DEFAULT_DURATION_MS,
|
2022-03-18 12:52:24 +03:00
|
|
|
description: `Ernie's live location`,
|
|
|
|
live: true,
|
|
|
|
},
|
|
|
|
[M_ASSET.name]: {
|
|
|
|
type: LocationAssetType.Self,
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('opens error dialog when beacon creation fails ', async () => {
|
|
|
|
// stub logger to keep console clean from expected error
|
|
|
|
const logSpy = jest.spyOn(logger, 'error').mockReturnValue(undefined);
|
|
|
|
const error = new Error('oh no');
|
|
|
|
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
|
|
|
|
const component = getComponent();
|
|
|
|
|
|
|
|
// advance to location picker
|
|
|
|
setShareType(component, LocationShareType.Live);
|
|
|
|
setLocation(component);
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
getSubmitButton(component).at(0).simulate('click');
|
|
|
|
component.setProps({});
|
|
|
|
});
|
|
|
|
|
|
|
|
await flushPromises();
|
|
|
|
|
|
|
|
expect(logSpy).toHaveBeenCalledWith("We couldn't start sharing your live location", error);
|
|
|
|
expect(mocked(Modal).createTrackedDialog).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2022-03-01 20:00:07 +03:00
|
|
|
});
|
2022-03-15 18:04:52 +03:00
|
|
|
|
|
|
|
function enableSettings(settings: string[]) {
|
|
|
|
mocked(SettingsStore).getValue.mockReturnValue(false);
|
|
|
|
mocked(SettingsStore).getValue.mockImplementation(
|
|
|
|
(settingName: string) => settings.includes(settingName),
|
|
|
|
);
|
|
|
|
}
|