From 60dc126caab68e264bf70d5747390659c250eafb Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 30 May 2022 18:59:45 +0200 Subject: [PATCH 1/8] test most basic paths in messageactionbar Signed-off-by: Kerry Archibald --- .../views/messages/MessageActionBar-test.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index 3d624187b8c..bc55ebd28a9 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -15,13 +15,20 @@ limitations under the License. */ import React from 'react'; +<<<<<<< HEAD import { render, fireEvent } from '@testing-library/react'; +======= +import { mount } from 'enzyme'; +>>>>>>> 00723661f1 (test most basic paths in messageactionbar) import { act } from 'react-test-renderer'; import { EventType, EventStatus, MatrixEvent, +<<<<<<< HEAD MatrixEventEvent, +======= +>>>>>>> 00723661f1 (test most basic paths in messageactionbar) MsgType, Room, } from 'matrix-js-sdk/src/matrix'; @@ -31,12 +38,19 @@ import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsEvents, +<<<<<<< HEAD +======= + findByAriaLabel, +>>>>>>> 00723661f1 (test most basic paths in messageactionbar) } from '../../../test-utils'; import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks'; import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext'; import { IRoomState } from '../../../../src/components/structures/RoomView'; import dispatcher from '../../../../src/dispatcher/dispatcher'; +<<<<<<< HEAD import SettingsStore from '../../../../src/settings/SettingsStore'; +======= +>>>>>>> 00723661f1 (test most basic paths in messageactionbar) jest.mock('../../../../src/dispatcher/dispatcher'); From 928da17f5c92fa58a659d090c6d253573754f665 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 1 Jun 2022 10:02:02 +0200 Subject: [PATCH 2/8] tidy Signed-off-by: Kerry Archibald --- .../views/messages/MessageActionBar-test.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index bc55ebd28a9..2b7cf34c6a7 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -15,20 +15,13 @@ limitations under the License. */ import React from 'react'; -<<<<<<< HEAD import { render, fireEvent } from '@testing-library/react'; -======= -import { mount } from 'enzyme'; ->>>>>>> 00723661f1 (test most basic paths in messageactionbar) import { act } from 'react-test-renderer'; import { EventType, EventStatus, MatrixEvent, -<<<<<<< HEAD MatrixEventEvent, -======= ->>>>>>> 00723661f1 (test most basic paths in messageactionbar) MsgType, Room, } from 'matrix-js-sdk/src/matrix'; @@ -38,19 +31,12 @@ import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsEvents, -<<<<<<< HEAD -======= - findByAriaLabel, ->>>>>>> 00723661f1 (test most basic paths in messageactionbar) } from '../../../test-utils'; import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks'; import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext'; import { IRoomState } from '../../../../src/components/structures/RoomView'; import dispatcher from '../../../../src/dispatcher/dispatcher'; -<<<<<<< HEAD import SettingsStore from '../../../../src/settings/SettingsStore'; -======= ->>>>>>> 00723661f1 (test most basic paths in messageactionbar) jest.mock('../../../../src/dispatcher/dispatcher'); @@ -222,6 +208,21 @@ describe('', () => { }); }); + it('kills event listeners on unmount', () => { + const offSpy = jest.spyOn(alicesMessageEvent, 'off').mockClear(); + const wrapper = getComponent({ mxEvent: alicesMessageEvent }); + + act(() => { + wrapper.unmount(); + }); + + expect(offSpy.mock.calls[0][0]).toEqual(MatrixEventEvent.Status); + expect(offSpy.mock.calls[1][0]).toEqual(MatrixEventEvent.Decrypted); + expect(offSpy.mock.calls[2][0]).toEqual(MatrixEventEvent.BeforeRedaction); + + expect(client.decryptEventIfNeeded).toHaveBeenCalled(); + }); + describe('options button', () => { it('renders options menu', () => { const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); From e328ce5c4afb27fe56eef691aabba29b1c1fb749 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 1 Jun 2022 18:16:35 +0200 Subject: [PATCH 3/8] use rtl for MessageActionBar test Signed-off-by: Kerry Archibald --- .../views/messages/MessageActionBar-test.tsx | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index 2b7cf34c6a7..3d624187b8c 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -208,21 +208,6 @@ describe('', () => { }); }); - it('kills event listeners on unmount', () => { - const offSpy = jest.spyOn(alicesMessageEvent, 'off').mockClear(); - const wrapper = getComponent({ mxEvent: alicesMessageEvent }); - - act(() => { - wrapper.unmount(); - }); - - expect(offSpy.mock.calls[0][0]).toEqual(MatrixEventEvent.Status); - expect(offSpy.mock.calls[1][0]).toEqual(MatrixEventEvent.Decrypted); - expect(offSpy.mock.calls[2][0]).toEqual(MatrixEventEvent.BeforeRedaction); - - expect(client.decryptEventIfNeeded).toHaveBeenCalled(); - }); - describe('options button', () => { it('renders options menu', () => { const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); From 98f0ad93aa5463613d0b74590ec28b4f957bdbbf Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 30 May 2022 17:39:44 +0200 Subject: [PATCH 4/8] make beacon_info events semi actionable Signed-off-by: Kerry Archibald --- src/components/views/messages/MBeaconBody.tsx | 2 ++ src/components/views/messages/MessageActionBar.tsx | 11 +++++++++-- src/utils/EventUtils.ts | 8 ++++++-- test/utils/EventUtils-test.ts | 11 ++++++++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index bd581d1bce8..8da25f29404 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -87,6 +87,8 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent }, ref) => } = useBeaconState(mxEvent); const mapId = useUniqueId(mxEvent.getId()); + console.log('hhh', mxEvent, mxEvent.isRelation()); + const matrixClient = useContext(MatrixClientContext); const [error, setError] = useState(); const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error); diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 7bd7fd719e8..690d4d60bb9 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -21,6 +21,7 @@ import { EventStatus, MatrixEvent, MatrixEventEvent } from 'matrix-js-sdk/src/mo import classNames from 'classnames'; import { MsgType, RelationType } from 'matrix-js-sdk/src/@types/event'; import { Thread } from 'matrix-js-sdk/src/models/thread'; +import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; import type { Relations } from 'matrix-js-sdk/src/models/relations'; import { _t } from '../../../languageHandler'; @@ -329,8 +330,14 @@ export default class MessageActionBar extends React.PureComponent { export function canForward(event: MatrixEvent): boolean { return !( - M_POLL_START.matches(event.getType()) + M_POLL_START.matches(event.getType()) || + // disallow forwarding until psf-1044 + M_BEACON_INFO.matches(event.getType()) ); } diff --git a/test/utils/EventUtils-test.ts b/test/utils/EventUtils-test.ts index df65b7e5f35..49bc26b4efc 100644 --- a/test/utils/EventUtils-test.ts +++ b/test/utils/EventUtils-test.ts @@ -62,7 +62,11 @@ describe('EventUtils', () => { }); redactedEvent.makeRedacted(redactedEvent); - const stateEvent = makeBeaconInfoEvent(userId, roomId); + const stateEvent = new MatrixEvent({ + type: EventType.RoomTopic, + state_key: '', + }); + const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId); const roomMemberEvent = new MatrixEvent({ type: EventType.RoomMember, @@ -155,6 +159,7 @@ describe('EventUtils', () => { ['poll start event', pollStartEvent], ['event with empty content body', emptyContentBody], ['event with a content body', niceTextMessage], + ['beacon_info event', beaconInfoEvent], ])('returns true for %s', (_description, event) => { expect(isContentActionable(event)).toBe(true); }); @@ -325,6 +330,10 @@ describe('EventUtils', () => { const event = makePollStartEvent('Who?', userId); expect(canForward(event)).toBe(false); }); + it('returns false for a beacon_info event', () => { + const event = makeBeaconInfoEvent(userId, roomId); + expect(canForward(event)).toBe(false); + }); it('returns true for a room message event', () => { const event = new MatrixEvent({ type: EventType.RoomMessage, From a8233f6c42ea1bea4070a17eccb823a22645bda5 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 30 May 2022 17:43:05 +0200 Subject: [PATCH 5/8] remove log Signed-off-by: Kerry Archibald --- src/components/views/messages/MBeaconBody.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index 8da25f29404..bd581d1bce8 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -87,8 +87,6 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent }, ref) => } = useBeaconState(mxEvent); const mapId = useUniqueId(mxEvent.getId()); - console.log('hhh', mxEvent, mxEvent.isRelation()); - const matrixClient = useContext(MatrixClientContext); const [error, setError] = useState(); const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error); From 97256c1b25b0b539821593481d3c1e9e67fe36a1 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 2 Jun 2022 12:18:51 +0200 Subject: [PATCH 6/8] test thread exception for beacon Signed-off-by: Kerry Archibald --- .../views/messages/MessageActionBar-test.tsx | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index 3d624187b8c..97b5c0c0770 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -25,20 +25,28 @@ import { MsgType, Room, } from 'matrix-js-sdk/src/matrix'; +import { Thread } from 'matrix-js-sdk/src/models/thread'; import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar'; import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsEvents, + makeBeaconInfoEvent, } from '../../../test-utils'; import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks'; import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext'; import { IRoomState } from '../../../../src/components/structures/RoomView'; import dispatcher from '../../../../src/dispatcher/dispatcher'; import SettingsStore from '../../../../src/settings/SettingsStore'; +import { Action } from '../../../../src/dispatcher/actions'; +import { UserTab } from '../../../../src/components/views/dialogs/UserTab'; +import { showThread } from '../../../../src/dispatcher/dispatch-actions/threads'; jest.mock('../../../../src/dispatcher/dispatcher'); +jest.mock('../../../../src/dispatcher/dispatch-actions/threads', () => ({ + showThread: jest.fn(), +})); describe('', () => { const userId = '@alice:server.org'; @@ -360,4 +368,100 @@ describe('', () => { it.todo('unsends event on cancel click'); it.todo('retrys event on retry click'); }); + + describe('thread button', () => { + beforeEach(() => { + Thread.setServerSideSupport(true, false); + }); + + describe('when threads feature is not enabled', () => { + it('does not render thread button when threads does not have server support', () => { + jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false); + Thread.setServerSideSupport(false, false); + const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + expect(queryByLabelText('Reply in thread')).toBeFalsy(); + }); + + it('renders thread button when threads has server support', () => { + jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false); + const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + expect(queryByLabelText('Reply in thread')).toBeTruthy(); + }); + + it('opens user settings on click', () => { + jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false); + const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + + act(() => { + fireEvent.click(getByLabelText('Reply in thread')); + }); + + expect(dispatcher.dispatch).toHaveBeenCalledWith({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Labs, + }); + }); + }); + + describe('when threads feature is enabled', () => { + beforeEach(() => { + jest.spyOn(SettingsStore, 'getValue').mockImplementation(setting => setting === 'feature_thread'); + }); + + it('renders thread button on own actionable event', () => { + const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + expect(queryByLabelText('Reply in thread')).toBeTruthy(); + }); + + it('does not render thread button for a beacon_info event', () => { + const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId); + const { queryByLabelText } = getComponent({ mxEvent: beaconInfoEvent }); + expect(queryByLabelText('Reply in thread')).toBeFalsy(); + }); + + it('opens thread on click', () => { + const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + + act(() => { + fireEvent.click(getByLabelText('Reply in thread')); + }); + + expect(showThread).toHaveBeenCalledWith({ + rootEvent: alicesMessageEvent, + push: false, + }); + }); + + it('opens parent thread for a thread reply message', () => { + const threadReplyEvent = new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + room_id: roomId, + content: { + msgtype: MsgType.Text, + body: 'this is a thread reply', + }, + }); + // mock the thread stuff + jest.spyOn(threadReplyEvent, 'isThreadRelation', 'get').mockReturnValue(true); + // set alicesMessageEvent as the root event + jest.spyOn(threadReplyEvent, 'getThread').mockReturnValue( + { rootEvent: alicesMessageEvent } as unknown as Thread, + ); + const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent }); + + act(() => { + fireEvent.click(getByLabelText('Reply in thread')); + }); + + expect(showThread).toHaveBeenCalledWith({ + rootEvent: alicesMessageEvent, + initialEvent: threadReplyEvent, + highlighted: true, + scroll_into_view: true, + push: false, + }); + }); + }); + }); }); From a889eb6dea14921d6aa5c1043fb62cbae933f6fe Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 2 Jun 2022 13:02:47 +0200 Subject: [PATCH 7/8] eat click events in beacon status to stop jumping from reply tile Signed-off-by: Kerry Archibald --- src/components/views/beacon/OwnBeaconStatus.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/views/beacon/OwnBeaconStatus.tsx b/src/components/views/beacon/OwnBeaconStatus.tsx index 3ef2de7a72a..87603761317 100644 --- a/src/components/views/beacon/OwnBeaconStatus.tsx +++ b/src/components/views/beacon/OwnBeaconStatus.tsx @@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler'; import { useOwnLiveBeacons } from '../../../utils/beacon'; import BeaconStatus from './BeaconStatus'; import { BeaconDisplayStatus } from './displayStatus'; -import AccessibleButton from '../elements/AccessibleButton'; +import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; interface Props { displayStatus: BeaconDisplayStatus; @@ -45,6 +45,14 @@ const OwnBeaconStatus: React.FC> = ({ onResetLocationPublishError, } = useOwnLiveBeacons([beacon?.identifier]); + // eat events here to avoid 1) the map and 2) reply or thread tiles + // moving under the beacon status on stop/retry click + const preventDefaultWrapper = (callback: () => void) => (e?: ButtonEvent) => { + e?.stopPropagation(); + e?.preventDefault(); + callback(); + }; + // combine display status with errors that only occur for user's own beacons const ownDisplayStatus = hasLocationPublishError || hasStopSharingError ? BeaconDisplayStatus.Error : @@ -60,7 +68,7 @@ const OwnBeaconStatus: React.FC> = ({ { ownDisplayStatus === BeaconDisplayStatus.Active && @@ -70,7 +78,7 @@ const OwnBeaconStatus: React.FC> = ({ { hasLocationPublishError && { _t('Retry') } @@ -79,7 +87,7 @@ const OwnBeaconStatus: React.FC> = ({ { hasStopSharingError && { _t('Retry') } From 1d6cd7316717736357d38702780145b2d834b133 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 2 Jun 2022 17:24:54 +0200 Subject: [PATCH 8/8] set max width on beaconbody for render in thread panel --- res/css/components/views/messages/_MBeaconBody.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/components/views/messages/_MBeaconBody.scss b/res/css/components/views/messages/_MBeaconBody.scss index 5654f14a057..64d7908df06 100644 --- a/res/css/components/views/messages/_MBeaconBody.scss +++ b/res/css/components/views/messages/_MBeaconBody.scss @@ -17,7 +17,8 @@ limitations under the License. .mx_MBeaconBody { position: relative; height: 220px; - width: 325px; + max-width: 325px; + width: 100%; border-radius: $timeline-image-border-radius; overflow: hidden;