Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 79a2dfe

Browse files
author
Kerry
authored
Live location share - enable reply and react to tiles (#8721)
* test most basic paths in messageactionbar Signed-off-by: Kerry Archibald <[email protected]> * tidy Signed-off-by: Kerry Archibald <[email protected]> * use rtl for MessageActionBar test Signed-off-by: Kerry Archibald <[email protected]> * make beacon_info events semi actionable Signed-off-by: Kerry Archibald <[email protected]> * remove log Signed-off-by: Kerry Archibald <[email protected]> * test thread exception for beacon Signed-off-by: Kerry Archibald <[email protected]> * eat click events in beacon status to stop jumping from reply tile Signed-off-by: Kerry Archibald <[email protected]> * set max width on beaconbody for render in thread panel
1 parent a74b9a7 commit 79a2dfe

File tree

6 files changed

+143
-10
lines changed

6 files changed

+143
-10
lines changed

res/css/components/views/messages/_MBeaconBody.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ limitations under the License.
1717
.mx_MBeaconBody {
1818
position: relative;
1919
height: 220px;
20-
width: 325px;
20+
max-width: 325px;
21+
width: 100%;
2122

2223
border-radius: $timeline-image-border-radius;
2324
overflow: hidden;

src/components/views/beacon/OwnBeaconStatus.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler';
2121
import { useOwnLiveBeacons } from '../../../utils/beacon';
2222
import BeaconStatus from './BeaconStatus';
2323
import { BeaconDisplayStatus } from './displayStatus';
24-
import AccessibleButton from '../elements/AccessibleButton';
24+
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
2525

2626
interface Props {
2727
displayStatus: BeaconDisplayStatus;
@@ -45,6 +45,14 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
4545
onResetLocationPublishError,
4646
} = useOwnLiveBeacons([beacon?.identifier]);
4747

48+
// eat events here to avoid 1) the map and 2) reply or thread tiles
49+
// moving under the beacon status on stop/retry click
50+
const preventDefaultWrapper = (callback: () => void) => (e?: ButtonEvent) => {
51+
e?.stopPropagation();
52+
e?.preventDefault();
53+
callback();
54+
};
55+
4856
// combine display status with errors that only occur for user's own beacons
4957
const ownDisplayStatus = hasLocationPublishError || hasStopSharingError ?
5058
BeaconDisplayStatus.Error :
@@ -60,7 +68,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
6068
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
6169
data-test-id='beacon-status-stop-beacon'
6270
kind='link'
63-
onClick={onStopSharing}
71+
onClick={preventDefaultWrapper(onStopSharing)}
6472
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
6573
disabled={stoppingInProgress}
6674
>
@@ -70,7 +78,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
7078
{ hasLocationPublishError && <AccessibleButton
7179
data-test-id='beacon-status-reset-wire-error'
7280
kind='link'
73-
onClick={onResetLocationPublishError}
81+
onClick={preventDefaultWrapper(onResetLocationPublishError)}
7482
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
7583
>
7684
{ _t('Retry') }
@@ -79,7 +87,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
7987
{ hasStopSharingError && <AccessibleButton
8088
data-test-id='beacon-status-stop-beacon-retry'
8189
kind='link'
82-
onClick={onStopSharing}
90+
onClick={preventDefaultWrapper(onStopSharing)}
8391
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
8492
>
8593
{ _t('Retry') }

src/components/views/messages/MessageActionBar.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { EventStatus, MatrixEvent, MatrixEventEvent } from 'matrix-js-sdk/src/mo
2121
import classNames from 'classnames';
2222
import { MsgType, RelationType } from 'matrix-js-sdk/src/@types/event';
2323
import { Thread } from 'matrix-js-sdk/src/models/thread';
24+
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
2425

2526
import type { Relations } from 'matrix-js-sdk/src/models/relations';
2627
import { _t } from '../../../languageHandler';
@@ -329,8 +330,14 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
329330

330331
const inNotThreadTimeline = this.context.timelineRenderingType !== TimelineRenderingType.Thread;
331332

332-
const isAllowedMessageType = !this.forbiddenThreadHeadMsgType.includes(
333-
this.props.mxEvent.getContent().msgtype as MsgType,
333+
const isAllowedMessageType = (
334+
!this.forbiddenThreadHeadMsgType.includes(
335+
this.props.mxEvent.getContent().msgtype as MsgType) &&
336+
/** forbid threads from live location shares
337+
* until cross-platform support
338+
* (PSF-1041)
339+
*/
340+
!M_BEACON_INFO.matches(this.props.mxEvent.getType())
334341
);
335342

336343
return inNotThreadTimeline && isAllowedMessageType;

src/utils/EventUtils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { MatrixClient } from 'matrix-js-sdk/src/client';
2020
import { logger } from 'matrix-js-sdk/src/logger';
2121
import { M_POLL_START } from "matrix-events-sdk";
2222
import { M_LOCATION } from "matrix-js-sdk/src/@types/location";
23+
import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon';
2324

2425
import { MatrixClientPeg } from '../MatrixClientPeg';
2526
import shouldHideEvent from "../shouldHideEvent";
@@ -52,7 +53,8 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean {
5253
}
5354
} else if (
5455
mxEvent.getType() === 'm.sticker' ||
55-
M_POLL_START.matches(mxEvent.getType())
56+
M_POLL_START.matches(mxEvent.getType()) ||
57+
M_BEACON_INFO.matches(mxEvent.getType())
5658
) {
5759
return true;
5860
}
@@ -277,7 +279,9 @@ export const isLocationEvent = (event: MatrixEvent): boolean => {
277279

278280
export function canForward(event: MatrixEvent): boolean {
279281
return !(
280-
M_POLL_START.matches(event.getType())
282+
M_POLL_START.matches(event.getType()) ||
283+
// disallow forwarding until psf-1044
284+
M_BEACON_INFO.matches(event.getType())
281285
);
282286
}
283287

test/components/views/messages/MessageActionBar-test.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,28 @@ import {
2525
MsgType,
2626
Room,
2727
} from 'matrix-js-sdk/src/matrix';
28+
import { Thread } from 'matrix-js-sdk/src/models/thread';
2829

2930
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
3031
import {
3132
getMockClientWithEventEmitter,
3233
mockClientMethodsUser,
3334
mockClientMethodsEvents,
35+
makeBeaconInfoEvent,
3436
} from '../../../test-utils';
3537
import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks';
3638
import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext';
3739
import { IRoomState } from '../../../../src/components/structures/RoomView';
3840
import dispatcher from '../../../../src/dispatcher/dispatcher';
3941
import SettingsStore from '../../../../src/settings/SettingsStore';
42+
import { Action } from '../../../../src/dispatcher/actions';
43+
import { UserTab } from '../../../../src/components/views/dialogs/UserTab';
44+
import { showThread } from '../../../../src/dispatcher/dispatch-actions/threads';
4045

4146
jest.mock('../../../../src/dispatcher/dispatcher');
47+
jest.mock('../../../../src/dispatcher/dispatch-actions/threads', () => ({
48+
showThread: jest.fn(),
49+
}));
4250

4351
describe('<MessageActionBar />', () => {
4452
const userId = '@alice:server.org';
@@ -360,4 +368,100 @@ describe('<MessageActionBar />', () => {
360368
it.todo('unsends event on cancel click');
361369
it.todo('retrys event on retry click');
362370
});
371+
372+
describe('thread button', () => {
373+
beforeEach(() => {
374+
Thread.setServerSideSupport(true, false);
375+
});
376+
377+
describe('when threads feature is not enabled', () => {
378+
it('does not render thread button when threads does not have server support', () => {
379+
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
380+
Thread.setServerSideSupport(false, false);
381+
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
382+
expect(queryByLabelText('Reply in thread')).toBeFalsy();
383+
});
384+
385+
it('renders thread button when threads has server support', () => {
386+
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
387+
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
388+
expect(queryByLabelText('Reply in thread')).toBeTruthy();
389+
});
390+
391+
it('opens user settings on click', () => {
392+
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
393+
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
394+
395+
act(() => {
396+
fireEvent.click(getByLabelText('Reply in thread'));
397+
});
398+
399+
expect(dispatcher.dispatch).toHaveBeenCalledWith({
400+
action: Action.ViewUserSettings,
401+
initialTabId: UserTab.Labs,
402+
});
403+
});
404+
});
405+
406+
describe('when threads feature is enabled', () => {
407+
beforeEach(() => {
408+
jest.spyOn(SettingsStore, 'getValue').mockImplementation(setting => setting === 'feature_thread');
409+
});
410+
411+
it('renders thread button on own actionable event', () => {
412+
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
413+
expect(queryByLabelText('Reply in thread')).toBeTruthy();
414+
});
415+
416+
it('does not render thread button for a beacon_info event', () => {
417+
const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId);
418+
const { queryByLabelText } = getComponent({ mxEvent: beaconInfoEvent });
419+
expect(queryByLabelText('Reply in thread')).toBeFalsy();
420+
});
421+
422+
it('opens thread on click', () => {
423+
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
424+
425+
act(() => {
426+
fireEvent.click(getByLabelText('Reply in thread'));
427+
});
428+
429+
expect(showThread).toHaveBeenCalledWith({
430+
rootEvent: alicesMessageEvent,
431+
push: false,
432+
});
433+
});
434+
435+
it('opens parent thread for a thread reply message', () => {
436+
const threadReplyEvent = new MatrixEvent({
437+
type: EventType.RoomMessage,
438+
sender: userId,
439+
room_id: roomId,
440+
content: {
441+
msgtype: MsgType.Text,
442+
body: 'this is a thread reply',
443+
},
444+
});
445+
// mock the thread stuff
446+
jest.spyOn(threadReplyEvent, 'isThreadRelation', 'get').mockReturnValue(true);
447+
// set alicesMessageEvent as the root event
448+
jest.spyOn(threadReplyEvent, 'getThread').mockReturnValue(
449+
{ rootEvent: alicesMessageEvent } as unknown as Thread,
450+
);
451+
const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent });
452+
453+
act(() => {
454+
fireEvent.click(getByLabelText('Reply in thread'));
455+
});
456+
457+
expect(showThread).toHaveBeenCalledWith({
458+
rootEvent: alicesMessageEvent,
459+
initialEvent: threadReplyEvent,
460+
highlighted: true,
461+
scroll_into_view: true,
462+
push: false,
463+
});
464+
});
465+
});
466+
});
363467
});

test/utils/EventUtils-test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ describe('EventUtils', () => {
6262
});
6363
redactedEvent.makeRedacted(redactedEvent);
6464

65-
const stateEvent = makeBeaconInfoEvent(userId, roomId);
65+
const stateEvent = new MatrixEvent({
66+
type: EventType.RoomTopic,
67+
state_key: '',
68+
});
69+
const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId);
6670

6771
const roomMemberEvent = new MatrixEvent({
6872
type: EventType.RoomMember,
@@ -155,6 +159,7 @@ describe('EventUtils', () => {
155159
['poll start event', pollStartEvent],
156160
['event with empty content body', emptyContentBody],
157161
['event with a content body', niceTextMessage],
162+
['beacon_info event', beaconInfoEvent],
158163
])('returns true for %s', (_description, event) => {
159164
expect(isContentActionable(event)).toBe(true);
160165
});
@@ -325,6 +330,10 @@ describe('EventUtils', () => {
325330
const event = makePollStartEvent('Who?', userId);
326331
expect(canForward(event)).toBe(false);
327332
});
333+
it('returns false for a beacon_info event', () => {
334+
const event = makeBeaconInfoEvent(userId, roomId);
335+
expect(canForward(event)).toBe(false);
336+
});
328337
it('returns true for a room message event', () => {
329338
const event = new MatrixEvent({
330339
type: EventType.RoomMessage,

0 commit comments

Comments
 (0)