From 09db26df7932f372973f7b76cd683c8251108137 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 21 Nov 2022 21:54:24 -0600 Subject: [PATCH] Fix `getRelationsForEvent` under TypeScript `strict` mode (#9558) * Fix getRelationsForEvent tsc strictness * Use shared type for GetRelationsForEvent * Fix lint * Add alternative type * getRelationsForEvent is not required * Relations are optional * Reactions are optional * We expect relations in these tests * Add more protection if the eventID is not defined * Allow null too * Better test typing * User ID is not necessary unless something is selected * It's okay to [].includes(null) * Null is as good as undefined here * Null or undefined is good here * We have some expectations for the tests * The room and user can be undefined too * Protec * Reactions are optional * Try match signatures * Null or undefined * More null or undefined * Protec * Fix typo (wrong variable) * Remove optional params See https://github.com/matrix-org/matrix-react-sdk/pull/9558#discussion_r1017515913 * Fix up last maaaaybe relevant lint Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MessagePanel.tsx | 5 +- src/components/structures/TimelinePanel.tsx | 2 +- .../context_menus/MessageContextMenu.tsx | 2 +- .../views/dialogs/EndPollDialog.tsx | 8 +- src/components/views/elements/ReplyChain.tsx | 6 +- .../views/emojipicker/ReactionPicker.tsx | 2 +- src/components/views/messages/IBodyProps.ts | 4 +- src/components/views/messages/MBeaconBody.tsx | 10 +- src/components/views/messages/MPollBody.tsx | 218 ++++++++++++------ .../views/messages/MessageActionBar.tsx | 17 +- .../views/messages/MessageEvent.tsx | 5 +- .../views/messages/ReactionsRow.tsx | 4 +- src/components/views/rooms/EventTile.tsx | 10 +- src/components/views/rooms/ReplyTile.tsx | 6 +- .../views/messages/MPollBody-test.tsx | 10 +- 15 files changed, 182 insertions(+), 127 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 56c396750ff..d6e37eed18b 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -20,7 +20,6 @@ import classNames from 'classnames'; import { Room } from 'matrix-js-sdk/src/models/room'; import { EventType } from 'matrix-js-sdk/src/@types/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; -import { Relations } from "matrix-js-sdk/src/models/relations"; import { logger } from 'matrix-js-sdk/src/logger'; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; @@ -35,7 +34,7 @@ import SettingsStore from '../../settings/SettingsStore'; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; import { Layout } from "../../settings/enums/Layout"; import { _t } from "../../languageHandler"; -import EventTile, { UnwrappedEventTile, IReadReceiptProps } from "../views/rooms/EventTile"; +import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile"; import { hasText } from "../../TextForEvent"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import DMRoomMap from "../../utils/DMRoomMap"; @@ -186,7 +185,7 @@ interface IProps { // helper function to access relations for an event onUnfillRequest?(backwards: boolean, scrollToken: string): void; - getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations; + getRelationsForEvent?: GetRelationsForEvent; hideThreadedMessages?: boolean; disableGrouping?: boolean; diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index bf8f1187397..9f9d1acd3b7 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1714,7 +1714,7 @@ class TimelinePanel extends React.Component { private getRelationsForEvent = ( eventId: string, - relationType: RelationType, + relationType: RelationType | string, eventType: EventType | string, ) => this.props.timelineSet.relations?.getChildEventsForEvent(eventId, relationType, eventType); diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index f6961b627ea..60c91d7b039 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -130,7 +130,7 @@ interface IProps extends IPosition { // True if the menu is being used as a right click menu rightClick?: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` - reactions?: Relations; + reactions?: Relations | null | undefined; // A permalink to this event or an href of an anchor element the user has clicked link?: string; diff --git a/src/components/views/dialogs/EndPollDialog.tsx b/src/components/views/dialogs/EndPollDialog.tsx index 9a4b32cf798..4313a47cc76 100644 --- a/src/components/views/dialogs/EndPollDialog.tsx +++ b/src/components/views/dialogs/EndPollDialog.tsx @@ -17,7 +17,6 @@ limitations under the License. import React from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import { Relations } from "matrix-js-sdk/src/models/relations"; import { PollEndEvent } from "matrix-events-sdk"; import { _t } from "../../../languageHandler"; @@ -26,16 +25,13 @@ import QuestionDialog from "./QuestionDialog"; import { findTopAnswer } from "../messages/MPollBody"; import Modal from "../../../Modal"; import ErrorDialog from "./ErrorDialog"; +import { GetRelationsForEvent } from "../rooms/EventTile"; interface IProps extends IDialogProps { matrixClient: MatrixClient; event: MatrixEvent; onFinished: (success: boolean) => void; - getRelationsForEvent?: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations; + getRelationsForEvent?: GetRelationsForEvent; } export default class EndPollDialog extends React.Component { diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index 6ecfc91d842..0647fb7260b 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -19,7 +19,6 @@ import React from 'react'; import classNames from 'classnames'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { Room } from 'matrix-js-sdk/src/models/room'; -import { Relations } from 'matrix-js-sdk/src/models/relations'; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { _t } from '../../../languageHandler'; @@ -36,6 +35,7 @@ import AccessibleButton, { ButtonEvent } from './AccessibleButton'; import { getParentEventId, shouldDisplayReply } from '../../../utils/Reply'; import RoomContext from "../../../contexts/RoomContext"; import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { GetRelationsForEvent } from "../rooms/EventTile"; /** * This number is based on the previous behavior - if we have message of height @@ -56,9 +56,7 @@ interface IProps { forExport?: boolean; isQuoteExpanded?: boolean; setQuoteExpanded: (isExpanded: boolean) => void; - getRelationsForEvent?: ( - (eventId: string, relationType: string, eventType: string) => Relations - ); + getRelationsForEvent?: GetRelationsForEvent; } interface IState { diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 51479cd5177..36099f67e95 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -29,7 +29,7 @@ import { FocusComposerPayload } from '../../../dispatcher/payloads/FocusComposer interface IProps { mxEvent: MatrixEvent; - reactions?: Relations; + reactions?: Relations | null | undefined; onFinished(): void; } diff --git a/src/components/views/messages/IBodyProps.ts b/src/components/views/messages/IBodyProps.ts index 1e66d1d1933..e16de3ccd88 100644 --- a/src/components/views/messages/IBodyProps.ts +++ b/src/components/views/messages/IBodyProps.ts @@ -16,11 +16,11 @@ limitations under the License. import React, { LegacyRef } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { Relations } from "matrix-js-sdk/src/models/relations"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import EditorStateTransfer from "../../../utils/EditorStateTransfer"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; +import { GetRelationsForEvent } from "../rooms/EventTile"; export interface IBodyProps { mxEvent: MatrixEvent; @@ -52,7 +52,7 @@ export interface IBodyProps { isSeeingThroughMessageHiddenForModeration?: boolean; // helper function to access relations for this event - getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations; + getRelationsForEvent?: GetRelationsForEvent; ref?: React.RefObject | LegacyRef; } diff --git a/src/components/views/messages/MBeaconBody.tsx b/src/components/views/messages/MBeaconBody.tsx index 7f8c9b8c8af..84faa0d53f9 100644 --- a/src/components/views/messages/MBeaconBody.tsx +++ b/src/components/views/messages/MBeaconBody.tsx @@ -101,8 +101,8 @@ const useUniqueId = (eventId: string): string => { // remove related beacon locations on beacon redaction const useHandleBeaconRedaction = ( event: MatrixEvent, - getRelationsForEvent: GetRelationsForEvent, - cli: MatrixClient, + matrixClient: MatrixClient, + getRelationsForEvent?: GetRelationsForEvent, ): void => { const onBeforeBeaconInfoRedaction = useCallback((_event: MatrixEvent, redactionEvent: MatrixEvent) => { const relations = getRelationsForEvent ? @@ -110,14 +110,14 @@ const useHandleBeaconRedaction = ( undefined; relations?.getRelations()?.forEach(locationEvent => { - cli.redactEvent( + matrixClient.redactEvent( locationEvent.getRoomId(), locationEvent.getId(), undefined, redactionEvent.getContent(), ); }); - }, [event, getRelationsForEvent, cli]); + }, [event, matrixClient, getRelationsForEvent]); useEffect(() => { event.addListener(MatrixEventEvent.BeforeRedaction, onBeforeBeaconInfoRedaction); @@ -151,7 +151,7 @@ const MBeaconBody: React.FC = React.forwardRef(({ mxEvent, getRelati const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined; const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId(); - useHandleBeaconRedaction(mxEvent, getRelationsForEvent, matrixClient); + useHandleBeaconRedaction(mxEvent, matrixClient, getRelationsForEvent); const onClick = () => { if (displayStatus !== BeaconDisplayStatus.Active) { diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index 7a48c7212a6..b39d286a0ce 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; import classNames from 'classnames'; +import { logger } from "matrix-js-sdk/src/logger"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { Relations, RelationsEvent } from 'matrix-js-sdk/src/models/relations'; import { MatrixClient } from 'matrix-js-sdk/src/matrix'; @@ -43,49 +44,59 @@ import PollCreateDialog from "../elements/PollCreateDialog"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; interface IState { - selected?: string; // Which option was clicked by the local user + selected?: string | null | undefined; // Which option was clicked by the local user voteRelations: RelatedRelations; // Voting (response) events endRelations: RelatedRelations; // Poll end events } export function createVoteRelations( - getRelationsForEvent: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations, + getRelationsForEvent: GetRelationsForEvent, eventId: string, ) { - return new RelatedRelations([ - getRelationsForEvent( - eventId, - "m.reference", - M_POLL_RESPONSE.name, - ), - getRelationsForEvent( - eventId, - "m.reference", - M_POLL_RESPONSE.altName, - ), - ]); + const relationsList: Relations[] = []; + + const pollResponseRelations = getRelationsForEvent( + eventId, + "m.reference", + M_POLL_RESPONSE.name, + ); + if (pollResponseRelations) { + relationsList.push(pollResponseRelations); + } + + const pollResposnseAltRelations = getRelationsForEvent( + eventId, + "m.reference", + M_POLL_RESPONSE.altName, + ); + if (pollResposnseAltRelations) { + relationsList.push(pollResposnseAltRelations); + } + + return new RelatedRelations(relationsList); } export function findTopAnswer( pollEvent: MatrixEvent, matrixClient: MatrixClient, - getRelationsForEvent?: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations, + getRelationsForEvent?: GetRelationsForEvent, ): string { if (!getRelationsForEvent) { return ""; } + const pollEventId = pollEvent.getId(); + if (!pollEventId) { + logger.warn( + "findTopAnswer: Poll event needs an event ID to fetch relations in order to determine " + + "the top answer - assuming no best answer", + ); + return ""; + } + const poll = pollEvent.unstableExtensibleEvent as PollStartEvent; if (!poll?.isEquivalentTo(M_POLL_START)) { - console.warn("Failed to parse poll to determine top answer - assuming no best answer"); + logger.warn("Failed to parse poll to determine top answer - assuming no best answer"); return ""; } @@ -93,25 +104,32 @@ export function findTopAnswer( return poll.answers.find(a => a.id === answerId)?.text ?? ""; }; - const voteRelations = createVoteRelations(getRelationsForEvent, pollEvent.getId()); - - const endRelations = new RelatedRelations([ - getRelationsForEvent( - pollEvent.getId(), - "m.reference", - M_POLL_END.name, - ), - getRelationsForEvent( - pollEvent.getId(), - "m.reference", - M_POLL_END.altName, - ), - ]); + const voteRelations = createVoteRelations(getRelationsForEvent, pollEventId); + + const relationsList: Relations[] = []; + + const pollEndRelations = getRelationsForEvent( + pollEventId, + "m.reference", + M_POLL_END.name, + ); + if (pollEndRelations) { + relationsList.push(pollEndRelations); + } + + const pollEndAltRelations = getRelationsForEvent( + pollEventId, + "m.reference", + M_POLL_END.altName, + ); + if (pollEndAltRelations) { + relationsList.push(pollEndAltRelations); + } + + const endRelations = new RelatedRelations(relationsList); const userVotes: Map = collectUserVotes( allVotes(pollEvent, matrixClient, voteRelations, endRelations), - matrixClient.getUserId(), - null, ); const votes: Map = countVotes(userVotes, poll); @@ -132,36 +150,60 @@ export function findTopAnswer( export function isPollEnded( pollEvent: MatrixEvent, matrixClient: MatrixClient, - getRelationsForEvent?: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations, + getRelationsForEvent?: GetRelationsForEvent, ): boolean { if (!getRelationsForEvent) { return false; } - const roomCurrentState = matrixClient.getRoom(pollEvent.getRoomId()).currentState; + const pollEventId = pollEvent.getId(); + if (!pollEventId) { + logger.warn( + "isPollEnded: Poll event must have event ID in order to determine whether it has ended " + + "- assuming poll has not ended", + ); + return false; + } + + const roomId = pollEvent.getRoomId(); + if (!roomId) { + logger.warn( + "isPollEnded: Poll event must have room ID in order to determine whether it has ended " + + "- assuming poll has not ended", + ); + return false; + } + + const roomCurrentState = matrixClient.getRoom(roomId)?.currentState; function userCanRedact(endEvent: MatrixEvent) { - return roomCurrentState.maySendRedactionForEvent( + const endEventSender = endEvent.getSender(); + return endEventSender && roomCurrentState && roomCurrentState.maySendRedactionForEvent( pollEvent, - endEvent.getSender(), + endEventSender, ); } - const endRelations = new RelatedRelations([ - getRelationsForEvent( - pollEvent.getId(), - "m.reference", - M_POLL_END.name, - ), - getRelationsForEvent( - pollEvent.getId(), - "m.reference", - M_POLL_END.altName, - ), - ]); + const relationsList: Relations[] = []; + + const pollEndRelations = getRelationsForEvent( + pollEventId, + "m.reference", + M_POLL_END.name, + ); + if (pollEndRelations) { + relationsList.push(pollEndRelations); + } + + const pollEndAltRelations = getRelationsForEvent( + pollEventId, + "m.reference", + M_POLL_END.altName, + ); + if (pollEndAltRelations) { + relationsList.push(pollEndAltRelations); + } + + const endRelations = new RelatedRelations(relationsList); if (!endRelations) { return false; @@ -175,7 +217,10 @@ export function isPollEnded( export function pollAlreadyHasVotes(mxEvent: MatrixEvent, getRelationsForEvent?: GetRelationsForEvent): boolean { if (!getRelationsForEvent) return false; - const voteRelations = createVoteRelations(getRelationsForEvent, mxEvent.getId()); + const eventId = mxEvent.getId(); + if (!eventId) return false; + + const voteRelations = createVoteRelations(getRelationsForEvent, eventId); return voteRelations.getRelations().length > 0; } @@ -335,18 +380,35 @@ export default class MPollBody extends React.Component { private fetchRelations(eventType: NamespacedValue): RelatedRelations | null { if (this.props.getRelationsForEvent) { - return new RelatedRelations([ - this.props.getRelationsForEvent( - this.props.mxEvent.getId(), - "m.reference", - eventType.name, - ), - this.props.getRelationsForEvent( - this.props.mxEvent.getId(), + const relationsList: Relations[] = []; + + const eventId = this.props.mxEvent.getId(); + if (!eventId) { + return null; + } + + const relations = this.props.getRelationsForEvent( + eventId, + "m.reference", + eventType.name, + ); + if (relations) { + relationsList.push(relations); + } + + // If there is an alternatve experimental event type, also look for that + if (eventType.altName) { + const altRelations = this.props.getRelationsForEvent( + eventId, "m.reference", eventType.altName, - ), - ]); + ); + if (altRelations) { + relationsList.push(altRelations); + } + } + + return new RelatedRelations(relationsList); } else { return null; } @@ -380,7 +442,7 @@ export default class MPollBody extends React.Component { const newEvents: MatrixEvent[] = this.state.voteRelations.getRelations() .filter(isPollResponse) .filter((mxEvent: MatrixEvent) => - !this.seenEventIds.includes(mxEvent.getId())); + !this.seenEventIds.includes(mxEvent.getId()!)); let newSelected = this.state.selected; if (newEvents.length > 0) { @@ -422,7 +484,7 @@ export default class MPollBody extends React.Component { const totalVotes = this.totalVotes(votes); const winCount = Math.max(...votes.values()); const userId = this.context.getUserId(); - const myVote = userVotes.get(userId)?.answers[0]; + const myVote = userVotes?.get(userId!)?.answers[0]; const disclosed = M_POLL_KIND_DISCLOSED.matches(poll.kind.name); // Disclosed: votes are hidden until I vote or the poll ends @@ -655,12 +717,16 @@ function isPollResponse(responseEvent: MatrixEvent): boolean { /** * Figure out the correct vote for each user. + * @param userResponses current vote responses in the poll + * @param {string?} userId The userId for which the `selected` option will apply to. + * Should be set to the current user ID. + * @param {string?} selected Local echo selected option for the userId * @returns a Map of user ID to their vote info */ function collectUserVotes( userResponses: Array, - userId: string, - selected?: string, + userId?: string | null | undefined, + selected?: string | null | undefined, ): Map { const userVotes: Map = new Map(); @@ -671,7 +737,7 @@ function collectUserVotes( } } - if (selected) { + if (selected && userId) { userVotes.set(userId, new UserVote(0, userId, [selected])); } diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index c1637b9a0cd..a6d9e2f13a8 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -59,6 +59,7 @@ import { Action } from '../../../dispatcher/actions'; import SdkConfig from "../../../SdkConfig"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import useFavouriteMessages from '../../../hooks/useFavouriteMessages'; +import { GetRelationsForEvent } from '../rooms/EventTile'; interface IOptionsButtonProps { mxEvent: MatrixEvent; @@ -67,11 +68,7 @@ interface IOptionsButtonProps { getReplyChain: () => ReplyChain; permalinkCreator: RoomPermalinkCreator; onFocusChange: (menuDisplayed: boolean) => void; - getRelationsForEvent?: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations; + getRelationsForEvent?: GetRelationsForEvent; } const OptionsButton: React.FC = ({ @@ -135,7 +132,7 @@ const OptionsButton: React.FC = ({ interface IReactButtonProps { mxEvent: MatrixEvent; - reactions: Relations; + reactions?: Relations | null | undefined; onFocusChange: (menuDisplayed: boolean) => void; } @@ -299,7 +296,7 @@ const FavouriteButton = ({ mxEvent }: IFavouriteButtonProp) => { interface IMessageActionBarProps { mxEvent: MatrixEvent; - reactions?: Relations; + reactions?: Relations | null | undefined; // TODO: Types getTile: () => any | null; getReplyChain: () => ReplyChain | undefined; @@ -307,11 +304,7 @@ interface IMessageActionBarProps { onFocusChange?: (menuDisplayed: boolean) => void; toggleThreadExpanded: () => void; isQuoteExpanded?: boolean; - getRelationsForEvent?: ( - eventId: string, - relationType: RelationType | string, - eventType: string - ) => Relations; + getRelationsForEvent?: GetRelationsForEvent; } export default class MessageActionBar extends React.PureComponent { diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 858bf0eb6c5..df850993b09 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -16,7 +16,6 @@ limitations under the License. import React, { createRef } from 'react'; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; -import { Relations } from 'matrix-js-sdk/src/models/relations'; import { M_BEACON_INFO } from 'matrix-js-sdk/src/@types/beacon'; import { M_LOCATION } from 'matrix-js-sdk/src/@types/location'; import { M_POLL_START } from "matrix-events-sdk"; @@ -41,7 +40,7 @@ import MPollBody from "./MPollBody"; import MLocationBody from "./MLocationBody"; import MjolnirBody from "./MjolnirBody"; import MBeaconBody from "./MBeaconBody"; -import { IEventTileOps } from "../rooms/EventTile"; +import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile"; import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from '../../../voice-broadcast'; // onMessageAllowed is handled internally @@ -51,7 +50,7 @@ interface IProps extends Omit; // helper function to access relations for this event - getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations; + getRelationsForEvent?: GetRelationsForEvent; isSeeingThroughMessageHiddenForModeration?: boolean; } diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index d0190fd5842..4f24a0280be 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -65,7 +65,7 @@ interface IProps { // The event we're displaying reactions for mxEvent: MatrixEvent; // The Relations model from the JS SDK for reactions to `mxEvent` - reactions?: Relations; + reactions?: Relations | null | undefined; } interface IState { @@ -114,7 +114,7 @@ export default class ReactionsRow extends React.PureComponent { } componentDidUpdate(prevProps: IProps) { - if (prevProps.reactions !== this.props.reactions) { + if (this.props.reactions && prevProps.reactions !== this.props.reactions) { this.props.reactions.on(RelationsEvent.Add, this.onReactionsChange); this.props.reactions.on(RelationsEvent.Remove, this.onReactionsChange); this.props.reactions.on(RelationsEvent.Redaction, this.onReactionsChange); diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 2b3cc82d105..8730037bd20 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { createRef, forwardRef, MouseEvent, RefObject } from 'react'; import classNames from "classnames"; -import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; +import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { Relations } from "matrix-js-sdk/src/models/relations"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; @@ -87,7 +87,11 @@ import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom'; import { ElementCall } from "../../../models/Call"; import { UnreadNotificationBadge } from './NotificationBadge/UnreadNotificationBadge'; -export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations; +export type GetRelationsForEvent = ( + eventId: string, + relationType: RelationType | string, + eventType: EventType | string, +) => Relations | null | undefined; // Our component structure for EventTiles on the timeline is: // @@ -233,7 +237,7 @@ interface IState { // Whether onRequestKeysClick has been called since mounting. previouslyRequestedKeys: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` - reactions: Relations; + reactions?: Relations | null | undefined; hover: boolean; diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index 2b973abfca5..5f8a8c1caf8 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -19,7 +19,6 @@ import classNames from 'classnames'; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event'; import { logger } from "matrix-js-sdk/src/logger"; -import { Relations } from 'matrix-js-sdk/src/models/relations'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; @@ -33,6 +32,7 @@ import MFileBody from "../messages/MFileBody"; import MVoiceMessageBody from "../messages/MVoiceMessageBody"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { renderReplyTile } from "../../../events/EventTileFactory"; +import { GetRelationsForEvent } from "../rooms/EventTile"; interface IProps { mxEvent: MatrixEvent; @@ -41,9 +41,7 @@ interface IProps { highlightLink?: string; onHeightChanged?(): void; toggleExpandedQuote?: () => void; - getRelationsForEvent?: ( - (eventId: string, relationType: string, eventType: string) => Relations - ); + getRelationsForEvent?: GetRelationsForEvent; } export default class ReplyTile extends React.PureComponent { diff --git a/test/components/views/messages/MPollBody-test.tsx b/test/components/views/messages/MPollBody-test.tsx index e462584098e..f2ffabfac66 100644 --- a/test/components/views/messages/MPollBody-test.tsx +++ b/test/components/views/messages/MPollBody-test.tsx @@ -321,12 +321,13 @@ describe("MPollBody", () => { const votes = [responseEvent("@me:example.com", "pizza", 100)]; const body = newMPollBody(votes); const props: IBodyProps = body.instance().props as IBodyProps; - const voteRelations: Relations = props.getRelationsForEvent( + const voteRelations = props!.getRelationsForEvent!( "$mypoll", "m.reference", M_POLL_RESPONSE.name); + expect(voteRelations).toBeDefined(); clickRadio(body, "pizza"); // When a new vote from me comes in - voteRelations.addEvent(responseEvent("@me:example.com", "wings", 101)); + voteRelations!.addEvent(responseEvent("@me:example.com", "wings", 101)); // Then the new vote is counted, not the old one expect(votesCount(body, "pizza")).toBe("0 votes"); @@ -342,12 +343,13 @@ describe("MPollBody", () => { const votes = [responseEvent("@me:example.com", "pizza")]; const body = newMPollBody(votes); const props: IBodyProps = body.instance().props as IBodyProps; - const voteRelations: Relations = props.getRelationsForEvent( + const voteRelations = props!.getRelationsForEvent!( "$mypoll", "m.reference", M_POLL_RESPONSE.name); + expect(voteRelations).toBeDefined(); clickRadio(body, "pizza"); // When a new vote from someone else comes in - voteRelations.addEvent(responseEvent("@xx:example.com", "wings", 101)); + voteRelations!.addEvent(responseEvent("@xx:example.com", "wings", 101)); // Then my vote is still for pizza // NOTE: the new event does not affect the counts for other people -