From c225b8ec293a6e6e36eef14b7be7bc31cd733141 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 27 Mar 2023 08:01:09 +0100 Subject: [PATCH] Conform more code to `strictNullChecks` (#10444 * Conform more code to `strictNullChecks` * Fix tests * Fix tests --- src/components/structures/MessagePanel.tsx | 14 ++++------ src/components/structures/RoomView.tsx | 26 +++++++++---------- src/components/structures/ThreadView.tsx | 14 +++++----- src/components/structures/TimelinePanel.tsx | 2 +- .../views/dialogs/BetaFeedbackDialog.tsx | 1 + .../views/dialogs/BulkRedactDialog.tsx | 4 +-- .../views/dialogs/DevtoolsDialog.tsx | 2 +- .../views/dialogs/ForwardDialog.tsx | 5 ++-- .../dialogs/RegistrationEmailPromptDialog.tsx | 1 + .../views/dialogs/ServerPickerDialog.tsx | 2 +- src/components/views/dialogs/TermsDialog.tsx | 8 +++--- .../views/dialogs/TextInputDialog.tsx | 2 +- .../spotlight/RoomResultContextMenus.tsx | 4 +-- .../dialogs/spotlight/SpotlightDialog.tsx | 2 +- .../views/elements/EditableText.tsx | 20 +++++++------- .../views/elements/EventListSummary.tsx | 6 ++--- src/components/views/elements/ReplyChain.tsx | 7 ++--- .../views/messages/EditHistoryMessage.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 2 +- src/components/views/right_panel/UserInfo.tsx | 6 ++--- src/components/views/rooms/EmojiButton.tsx | 2 +- .../views/rooms/MessageComposerButtons.tsx | 4 +-- src/hooks/usePermalinkEvent.ts | 4 +-- src/hooks/usePermalinkTargetRoom.ts | 2 +- src/toasts/AnalyticsToast.tsx | 3 +-- src/utils/SortMembers.ts | 5 +++- .../structures/MessagePanel-test.tsx | 2 +- .../views/dialogs/ForwardDialog-test.tsx | 2 ++ .../views/right_panel/UserInfo-test.tsx | 6 ++++- 29 files changed, 85 insertions(+), 75 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 650313b967..37b733f895 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -24,8 +24,6 @@ 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"; import { isSupportedReceiptType } from "matrix-js-sdk/src/utils"; -import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt"; -import { ListenerMap } from "matrix-js-sdk/src/models/typed-event-emitter"; import shouldHideEvent from "../../shouldHideEvent"; import { wantsDateSeparator } from "../../DateUtils"; @@ -543,7 +541,7 @@ export default class MessagePanel extends React.Component<IProps, IState> { return null; } - private collectGhostReadMarker = (node: HTMLElement): void => { + private collectGhostReadMarker = (node: HTMLElement | null): void => { if (node) { // now the element has appeared, change the style which will trigger the CSS transition requestAnimationFrame(() => { @@ -788,13 +786,13 @@ export default class MessagePanel extends React.Component<IProps, IState> { continuation={continuation} isRedacted={mxEv.isRedacted()} replacingEventId={mxEv.replacingEventId()} - editState={isEditing && this.props.editState} + editState={isEditing ? this.props.editState : undefined} onHeightChanged={this.onHeightChanged} readReceipts={readReceipts} readReceiptMap={this.readReceiptMap} showUrlPreview={this.props.showUrlPreview} checkUnmounting={this.isUnmounting} - eventSendStatus={mxEv.getAssociatedStatus()} + eventSendStatus={mxEv.getAssociatedStatus() ?? undefined} isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} last={last} @@ -836,9 +834,7 @@ export default class MessagePanel extends React.Component<IProps, IState> { return null; } - const receiptDestination: ReadReceipt<string, ListenerMap<string>> = this.context.threadId - ? room.getThread(this.context.threadId) - : room; + const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room; const receipts: IReadReceiptProps[] = []; @@ -1215,7 +1211,7 @@ class CreationGrouper extends BaseGrouper { let summaryText: string; const roomId = ev.getRoomId(); - const creator = ev.sender ? ev.sender.name : ev.getSender(); + const creator = ev.sender?.name ?? ev.getSender(); if (DMRoomMap.shared().getUserIdForRoomId(roomId)) { summaryText = _t("%(creator)s created this DM.", { creator }); } else { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ccb22253af..9af69100ab 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -377,7 +377,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { private roomView = createRef<HTMLElement>(); private searchResultsPanel = createRef<ScrollPanel>(); - private messagePanel?: TimelinePanel; + private messagePanel: TimelinePanel | null = null; private roomViewBody = createRef<HTMLDivElement>(); public static contextType = SDKContext; @@ -611,11 +611,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { const newState: Partial<IRoomState> = { roomId: roomId ?? undefined, - roomAlias: this.context.roomViewStore.getRoomAlias(), + roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined, roomLoading: this.context.roomViewStore.isRoomLoading(), - roomLoadError: this.context.roomViewStore.getRoomLoadError(), + roomLoadError: this.context.roomViewStore.getRoomLoadError() ?? undefined, joining: this.context.roomViewStore.isJoining(), - replyToEvent: this.context.roomViewStore.getQuotingEvent(), + replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined, // we should only peek once we have a ready client shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), @@ -654,7 +654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { // and the root event. // The rest will be lost for now, until the aggregation API on the server // becomes available to fetch a whole thread - if (!initialEvent) { + if (!initialEvent && this.context.client) { initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined; } @@ -848,7 +848,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { isPeeking: true, // this will change to false if peeking fails }); this.context.client - .peekInRoom(roomId) + ?.peekInRoom(roomId) .then((room) => { if (this.unmounted) { return; @@ -883,7 +883,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { }); } else if (room) { // Stop peeking because we have joined this room previously - this.context.client.stopPeeking(); + this.context.client?.stopPeeking(); this.setState({ isPeeking: false }); } } @@ -909,9 +909,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { this.onRoomViewStoreUpdate(true); const call = this.getCallForRoom(); - const callState = call ? call.state : null; + const callState = call?.state; this.setState({ - callState: callState, + callState, }); this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState); @@ -959,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { } if (this.state.shouldPeek) { - this.context.client.stopPeeking(); + this.context.client?.stopPeeking(); } // stop tracking room changes to format permalinks @@ -1010,7 +1010,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { if (this.viewsLocalRoom) { // clean up if this was a local room - this.context.client.store.removeRoom(this.state.room.roomId); + this.context.client?.store.removeRoom(this.state.room.roomId); } } @@ -1469,7 +1469,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { private updatePermissions(room: Room): void { if (room) { - const me = this.context.client.getUserId(); + const me = this.context.client.getSafeUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me); const canSendMessages = room.maySendMessage(); @@ -1866,7 +1866,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - private gatherTimelinePanelRef = (r?: TimelinePanel): void => { + private gatherTimelinePanelRef = (r: TimelinePanel | null): void => { this.messagePanel = r; }; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8eaf3f2be9..346883f640 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -171,7 +171,7 @@ export default class ThreadView extends React.Component<IProps, IState> { if (payload.event && !payload.event.getThread()) return; this.setState( { - editState: payload.event ? new EditorStateTransfer(payload.event) : null, + editState: payload.event ? new EditorStateTransfer(payload.event) : undefined, }, () => { if (payload.event) { @@ -213,9 +213,11 @@ export default class ThreadView extends React.Component<IProps, IState> { }; private get threadLastReply(): MatrixEvent | undefined { - return this.state.thread?.lastReply((ev: MatrixEvent) => { - return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; - }); + return ( + this.state.thread?.lastReply((ev: MatrixEvent) => { + return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; + }) ?? undefined + ); } private updateThread = (thread?: Thread): void => { @@ -245,7 +247,7 @@ export default class ThreadView extends React.Component<IProps, IState> { private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void { if (oldThread) { - this.state.thread.off(ThreadEvent.NewReply, this.updateThreadRelation); + this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation); this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation); } if (thread) { @@ -344,7 +346,7 @@ export default class ThreadView extends React.Component<IProps, IState> { }; public render(): React.ReactNode { - const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null; + const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : undefined; const threadRelation = this.threadRelation; diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 719bba71df..b496454c7e 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -79,7 +79,7 @@ interface IProps { // representing. This may or may not have a room, depending on what it's // a timeline representing. If it has a room, we maintain RRs etc for // that room. - timelineSet?: EventTimelineSet; + timelineSet: EventTimelineSet; // overlay events from a second timelineset on the main timeline // added to support virtual rooms // events from the overlay timeline set will be added by localTimestamp diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx index 8223fde929..a262fd4f5e 100644 --- a/src/components/views/dialogs/BetaFeedbackDialog.tsx +++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx @@ -33,6 +33,7 @@ interface IProps { const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => { const info = SettingsStore.getBetaInfo(featureId); + if (!info) return null; return ( <GenericFeatureFeedbackDialog diff --git a/src/components/views/dialogs/BulkRedactDialog.tsx b/src/components/views/dialogs/BulkRedactDialog.tsx index ba12f21039..87dbcfda07 100644 --- a/src/components/views/dialogs/BulkRedactDialog.tsx +++ b/src/components/views/dialogs/BulkRedactDialog.tsx @@ -42,7 +42,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => { const { matrixClient: cli, room, member, onFinished } = props; const [keepStateEvents, setKeepStateEvents] = useState(true); - let timeline = room.getLiveTimeline(); + let timeline: EventTimeline | null = room.getLiveTimeline(); let eventsToRedact: MatrixEvent[] = []; while (timeline) { eventsToRedact = [ @@ -93,7 +93,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => { await Promise.all( eventsToRedact.reverse().map(async (event): Promise<void> => { try { - await cli.redactEvent(room.roomId, event.getId()); + await cli.redactEvent(room.roomId, event.getId()!); } catch (err) { // log and swallow errors logger.error("Could not redact", event.getId()); diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 3fd45c17a1..655b175edf 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -71,7 +71,7 @@ interface IProps { type ToolInfo = [label: string, tool: Tool]; const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => { - const [tool, setTool] = useState<ToolInfo>(null); + const [tool, setTool] = useState<ToolInfo | null>(null); let body: JSX.Element; let onBack: () => void; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index af758507d2..75af82d86a 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -51,6 +51,7 @@ import { ButtonEvent } from "../elements/AccessibleButton"; import { isLocationEvent } from "../../../utils/EventUtils"; import { isSelfLocation, locationEventGeoUri } from "../../../utils/location"; import { RoomContextDetails } from "../rooms/RoomContextDetails"; +import { filterBoolean } from "../../../utils/arrays"; const AVATAR_SIZE = 30; @@ -194,7 +195,7 @@ const transformEvent = (event: MatrixEvent): { type: string; content: IContent } }; const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => { - const userId = cli.getUserId(); + const userId = cli.getSafeUserId(); const [profileInfo, setProfileInfo] = useState<any>({}); useEffect(() => { cli.getProfileInfo(userId).then((info) => setProfileInfo(info)); @@ -242,7 +243,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr if (lcQuery) { rooms = new QueryMatcher<Room>(rooms, { keys: ["name"], - funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)], + funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])], shouldMatchWordsOnly: false, }).match(lcQuery); } diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index a175e14606..9d8adc9d99 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -34,6 +34,7 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => { const onSubmit = async (e: SyntheticEvent): Promise<void> => { e.preventDefault(); + if (!fieldRef.current) return; if (email) { const valid = await fieldRef.current.validate({}); diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 75436e03a8..2b208b29f3 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -141,7 +141,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta return !error; }, invalid: function ({ error }) { - return error; + return error ?? null; }, }, ], diff --git a/src/components/views/dialogs/TermsDialog.tsx b/src/components/views/dialogs/TermsDialog.tsx index 57efd54b99..b0835c3afc 100644 --- a/src/components/views/dialogs/TermsDialog.tsx +++ b/src/components/views/dialogs/TermsDialog.tsx @@ -49,7 +49,7 @@ interface ITermsDialogProps { /** * urls that the user has already agreed to */ - agreedUrls?: string[]; + agreedUrls: string[]; /** * Called with: @@ -127,7 +127,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps, }; public render(): React.ReactNode { - const rows = []; + const rows: JSX.Element[] = []; for (const policiesAndService of this.props.policiesAndServicePairs) { const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl); @@ -135,8 +135,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps, for (let i = 0; i < policyValues.length; ++i) { const termDoc = policyValues[i]; const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version")); - let serviceName; - let summary; + let serviceName: JSX.Element | undefined; + let summary: JSX.Element | undefined; if (i === 0) { serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host); summary = this.summaryForServiceType(policiesAndService.service.serviceType); diff --git a/src/components/views/dialogs/TextInputDialog.tsx b/src/components/views/dialogs/TextInputDialog.tsx index 9c22e8b2e3..ed9f377508 100644 --- a/src/components/views/dialogs/TextInputDialog.tsx +++ b/src/components/views/dialogs/TextInputDialog.tsx @@ -100,7 +100,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> { }; private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { - const result = await this.props.validator(fieldState); + const result = await this.props.validator!(fieldState); this.setState({ valid: !!result.valid, }); diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx index 8d95860ef8..096138e056 100644 --- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx +++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx @@ -38,7 +38,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null); const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null); - let generalMenu: JSX.Element; + let generalMenu: JSX.Element | undefined; if (generalMenuPosition !== null) { if (room.isSpaceRoom()) { generalMenu = ( @@ -59,7 +59,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { } } - let notificationMenu: JSX.Element; + let notificationMenu: JSX.Element | undefined; if (notificationMenuPosition !== null) { notificationMenu = ( <RoomNotificationContextMenu diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index 6e2b113ee6..37c2bace25 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -440,7 +440,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n // Sort results by most recent activity - const myUserId = cli.getUserId(); + const myUserId = cli.getSafeUserId(); for (const resultArray of Object.values(results)) { resultArray.sort((a: Result, b: Result) => { if (isRoomResult(a) || isRoomResult(b)) { diff --git a/src/components/views/elements/EditableText.tsx b/src/components/views/elements/EditableText.tsx index b4695d552b..5c11e52ecc 100644 --- a/src/components/views/elements/EditableText.tsx +++ b/src/components/views/elements/EditableText.tsx @@ -27,17 +27,17 @@ enum Phases { interface IProps { onValueChanged?: (value: string, shouldSubmit: boolean) => void; - initialValue?: string; - label?: string; - placeholder?: string; - className?: string; + initialValue: string; + label: string; + placeholder: string; + className: string; labelClassName?: string; - placeholderClassName?: string; + placeholderClassName: string; // Overrides blurToSubmit if true blurToCancel?: boolean; // Will cause onValueChanged(value, true) to fire on blur - blurToSubmit?: boolean; - editable?: boolean; + blurToSubmit: boolean; + editable: boolean; } interface IState { @@ -109,11 +109,11 @@ export default class EditableText extends React.Component<IProps, IState> { this.value = this.props.initialValue; this.showPlaceholder(!this.value); this.onValueChanged(false); - this.editableDiv.current.blur(); + this.editableDiv.current?.blur(); }; private onValueChanged = (shouldSubmit: boolean): void => { - this.props.onValueChanged(this.value, shouldSubmit); + this.props.onValueChanged?.(this.value, shouldSubmit); }; private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => { @@ -171,7 +171,7 @@ export default class EditableText extends React.Component<IProps, IState> { private onFinish = ( ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>, - shouldSubmit?: boolean, + shouldSubmit = false, ): void => { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 5498773777..882e68c9f8 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -187,8 +187,8 @@ export default class EventListSummary extends React.Component<IProps> { let transition = t; - if (i < transitions.length - 1 && modMap[t] && modMap[t].after === t2) { - transition = modMap[t].newTransition; + if (i < transitions.length - 1 && modMap[t] && modMap[t]!.after === t2) { + transition = modMap[t]!.newTransition; i++; } @@ -380,7 +380,7 @@ export default class EventListSummary extends React.Component<IProps> { return res ?? null; } - private static getTransitionSequence(events: IUserEvents[]): TransitionType[] { + private static getTransitionSequence(events: IUserEvents[]): Array<TransitionType | null> { return events.map(EventListSummary.getTransition); } diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index be415027c4..e4a720ce4c 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -148,6 +148,7 @@ export default class ReplyChain extends React.Component<IProps, IState> { private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> { try { const inReplyToEventId = getParentEventId(ev); + if (!inReplyToEventId) return null; return await this.getEvent(inReplyToEventId); } catch (e) { return null; @@ -196,7 +197,7 @@ export default class ReplyChain extends React.Component<IProps, IState> { }; private getReplyChainColorClass(ev: MatrixEvent): string { - return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyChain"); + return getUserNameColorClass(ev.getSender()!).replace("Username", "ReplyChain"); } public render(): React.ReactNode { @@ -231,8 +232,8 @@ export default class ReplyChain extends React.Component<IProps, IState> { pill: ( <Pill type={PillType.UserMention} - room={room} - url={makeUserPermalink(ev.getSender())} + room={room ?? undefined} + url={makeUserPermalink(ev.getSender()!)} shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")} /> ), diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx index 906867d6a8..312e694786 100644 --- a/src/components/views/messages/EditHistoryMessage.tsx +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -63,7 +63,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta const event = this.props.mxEvent; const room = cli.getRoom(event.getRoomId()); event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged); - const canRedact = room.currentState.maySendRedactionForEvent(event, userId); + const canRedact = room?.currentState.maySendRedactionForEvent(event, userId) ?? false; this.state = { canRedact, sendStatus: event.getAssociatedStatus() }; } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index aa4434427c..ca1bf2ed9d 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -71,7 +71,7 @@ export const usePinnedEvents = (room?: Room): string[] => { }; function getReadPinnedEventIds(room?: Room): Set<string> { - return new Set(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); + return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); } export const useReadPinnedEvents = (room?: Room): Set<string> => { diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index a3dd4cdd39..8519fb86e3 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -448,7 +448,7 @@ export const UserOptionsSection: React.FC<{ const inviter = new MultiInviter(roomId || ""); await inviter.invite([member.userId]).then(() => { if (inviter.getCompletionState(member.userId) !== "invited") { - throw new Error(inviter.getErrorText(member.userId)); + throw new Error(inviter.getErrorText(member.userId) ?? undefined); } }); } catch (err) { @@ -766,8 +766,8 @@ export const BanToggleButton = ({ const myMember = child.getMember(cli.credentials.userId || ""); const theirMember = child.getMember(member.userId); return ( - myMember && - theirMember && + !!myMember && + !!theirMember && theirMember.membership !== "ban" && myMember.powerLevel > theirMember.powerLevel && child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel) diff --git a/src/components/views/rooms/EmojiButton.tsx b/src/components/views/rooms/EmojiButton.tsx index 013feeee24..db7accb62c 100644 --- a/src/components/views/rooms/EmojiButton.tsx +++ b/src/components/views/rooms/EmojiButton.tsx @@ -25,7 +25,7 @@ import { OverflowMenuContext } from "./MessageComposerButtons"; interface IEmojiButtonProps { addEmoji: (unicode: string) => boolean; - menuPosition: MenuProps; + menuPosition?: MenuProps; className?: string; } diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 30856cabc7..abcb82ee63 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -73,8 +73,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => { return null; } - let mainButtons: ReactElement[]; - let moreButtons: ReactElement[]; + let mainButtons: ReactNode[]; + let moreButtons: ReactNode[]; if (narrow) { mainButtons = [ isWysiwygLabEnabled ? ( diff --git a/src/hooks/usePermalinkEvent.ts b/src/hooks/usePermalinkEvent.ts index 39892c7958..1cb2b05e3b 100644 --- a/src/hooks/usePermalinkEvent.ts +++ b/src/hooks/usePermalinkEvent.ts @@ -75,8 +75,8 @@ export const usePermalinkEvent = ( const fetchRoomEvent = async (): Promise<void> => { try { const eventData = await MatrixClientPeg.get().fetchRoomEvent( - parseResult.roomIdOrAlias, - parseResult.eventId, + parseResult.roomIdOrAlias!, + parseResult.eventId!, ); setEvent(new MatrixEvent(eventData)); } catch {} diff --git a/src/hooks/usePermalinkTargetRoom.ts b/src/hooks/usePermalinkTargetRoom.ts index b9f6dd6dec..826f995bbe 100644 --- a/src/hooks/usePermalinkTargetRoom.ts +++ b/src/hooks/usePermalinkTargetRoom.ts @@ -83,7 +83,7 @@ const findRoom = (roomIdOrAlias: string): Room | null => { export const usePermalinkTargetRoom = ( type: PillType | null, parseResult: PermalinkParts | null, - permalinkRoom: Room | null, + permalinkRoom: Room | undefined, ): Room | null => { // The listed permalink types require a room. // If it cannot be initially determined, it will be looked up later by a memo hook. diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx index d13d489e00..0a5b00cd58 100644 --- a/src/toasts/AnalyticsToast.tsx +++ b/src/toasts/AnalyticsToast.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import React from "react"; -import { Optional } from "matrix-events-sdk"; import { _t } from "../languageHandler"; import SdkConfig from "../SdkConfig"; @@ -75,7 +74,7 @@ const onLearnMorePreviouslyOptedIn = (): void => { const TOAST_KEY = "analytics"; -export function getPolicyUrl(): Optional<string> { +export function getPolicyUrl(): string | undefined { return SdkConfig.get("privacy_policy_url"); } diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts index 2f63dacd7b..d19b461b1e 100644 --- a/src/utils/SortMembers.ts +++ b/src/utils/SortMembers.ts @@ -22,7 +22,10 @@ import { Member } from "./direct-messages"; import DMRoomMap from "./DMRoomMap"; export const compareMembers = - (activityScores: Record<string, IActivityScore>, memberScores: Record<string, IMemberScore>) => + ( + activityScores: Record<string, IActivityScore | undefined>, + memberScores: Record<string, IMemberScore | undefined>, + ) => (a: Member | RoomMember, b: Member | RoomMember): number => { const aActivityScore = activityScores[a.userId]?.score ?? 0; const aMemberScore = memberScores[a.userId]?.score ?? 0; diff --git a/test/components/structures/MessagePanel-test.tsx b/test/components/structures/MessagePanel-test.tsx index 79d6ce963c..e4239533f1 100644 --- a/test/components/structures/MessagePanel-test.tsx +++ b/test/components/structures/MessagePanel-test.tsx @@ -716,7 +716,7 @@ describe("MessagePanel", function () { // Increase the length of the loop here to test performance issues with // rendering - const events = []; + const events: MatrixEvent[] = []; for (let i = 0; i < 100; i++) { events.push( TestUtilsMatrix.mkMembership({ diff --git a/test/components/views/dialogs/ForwardDialog-test.tsx b/test/components/views/dialogs/ForwardDialog-test.tsx index 748c0b193d..036e98b038 100644 --- a/test/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/components/views/dialogs/ForwardDialog-test.tsx @@ -54,6 +54,7 @@ describe("ForwardDialog", () => { }); const mockClient = getMockClientWithEventEmitter({ getUserId: jest.fn().mockReturnValue(aliceId), + getSafeUserId: jest.fn().mockReturnValue(aliceId), isGuest: jest.fn().mockReturnValue(false), getVisibleRooms: jest.fn().mockReturnValue([]), getRoom: jest.fn(), @@ -92,6 +93,7 @@ describe("ForwardDialog", () => { DMRoomMap.makeShared(); jest.clearAllMocks(); mockClient.getUserId.mockReturnValue("@bob:example.org"); + mockClient.getSafeUserId.mockReturnValue("@bob:example.org"); mockClient.sendEvent.mockReset(); }); diff --git a/test/components/views/right_panel/UserInfo-test.tsx b/test/components/views/right_panel/UserInfo-test.tsx index 7ca7daa8f2..68274d9bf8 100644 --- a/test/components/views/right_panel/UserInfo-test.tsx +++ b/test/components/views/right_panel/UserInfo-test.tsx @@ -112,6 +112,7 @@ const mockClient = mocked({ setIgnoredUsers: jest.fn(), isCryptoEnabled: jest.fn(), getUserId: jest.fn(), + getSafeUserId: jest.fn(), on: jest.fn(), off: jest.fn(), isSynapseAdministrator: jest.fn().mockResolvedValue(false), @@ -348,6 +349,7 @@ describe("<DeviceItem />", () => { }); it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => { + mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId); mockClient.getUserId.mockReturnValueOnce(defaultUserId); renderComponent(); @@ -431,6 +433,7 @@ describe("<UserOptionsSection />", () => { }); it("does not show ignore or direct message buttons when member userId matches client userId", () => { + mockClient.getSafeUserId.mockReturnValueOnce(member.userId); mockClient.getUserId.mockReturnValueOnce(member.userId); renderComponent(); @@ -676,6 +679,7 @@ describe("<PowerLevelEditor />", () => { content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 }, }); mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent); + mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId); mockClient.getUserId.mockReturnValueOnce(defaultUserId); mockClient.setPowerLevel.mockResolvedValueOnce({ event_id: "123" }); renderComponent(); @@ -879,7 +883,7 @@ describe("<BanToggleButton />", () => { }, }; - expect(callback(mockRoom)).toBe(null); + expect(callback(mockRoom)).toBe(false); expect(callback(mockRoom)).toBe(true); });