From 30f330847c737365dd599974fcc84ec020e4ddbf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Apr 2023 10:26:32 +0100 Subject: [PATCH 1/5] Conform more of the codebase to `strictNullChecks` --- src/autocomplete/Autocompleter.ts | 2 +- src/components/views/avatars/RoomAvatar.tsx | 1 + src/components/views/dialogs/ReportEventDialog.tsx | 4 ++-- src/components/views/right_panel/UserInfo.tsx | 3 ++- src/components/views/rooms/BasicMessageComposer.tsx | 6 +++--- src/components/views/rooms/EditMessageComposer.tsx | 8 ++++---- src/components/views/rooms/LinkPreviewWidget.tsx | 4 +++- src/customisations/Media.ts | 2 +- src/resizer/resizer.ts | 10 +++++----- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index 73933a23a9b..51c160320b4 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -39,7 +39,7 @@ export interface ICompletion { type?: "at-room" | "command" | "community" | "room" | "user"; completion: string; completionId?: string; - component?: ReactElement; + component: ReactElement; range: ISelectionRange; command?: string; suffix?: string; diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 69ee06a2cc2..ab434e1a69b 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -110,6 +110,7 @@ export default class RoomAvatar extends React.Component { private onRoomAvatarClick = (): void => { const avatarUrl = Avatar.avatarUrlForRoom(this.props.room ?? null, undefined, undefined, undefined); + if (!avatarUrl) return; const params = { src: avatarUrl, name: this.props.room?.name, diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx index 02ec9d4a356..8eaa64bc34b 100644 --- a/src/components/views/dialogs/ReportEventDialog.tsx +++ b/src/components/views/dialogs/ReportEventDialog.tsx @@ -255,7 +255,7 @@ export default class ReportEventDialog extends React.Component { }); } else { // Report to homeserver admin through the dedicated Matrix API. - await client.reportEvent(ev.getRoomId(), ev.getId(), -100, this.state.reason.trim()); + await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); } // if the user should also be ignored, do that @@ -340,7 +340,7 @@ export default class ReportEventDialog extends React.Component { ); break; case NonStandardValue.Admin: - if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) { + if (client.isRoomEncrypted(this.props.mxEvent.getRoomId()!)) { subtitle = _t( "This room is dedicated to illegal or toxic content " + "or the moderators fail to moderate illegal or toxic content.\n" + diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index c0fb6dd443f..270e837a703 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -1504,9 +1504,10 @@ export const UserInfoHeader: React.FC<{ const avatarUrl = (member as RoomMember).getMxcAvatarUrl ? (member as RoomMember).getMxcAvatarUrl() : (member as User).avatarUrl; - if (!avatarUrl) return; const httpUrl = mediaFromMxc(avatarUrl).srcHttp; + if (!httpUrl) return; + const params = { src: httpUrl, name: (member as RoomMember).name || (member as User).displayName, diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 2eb19e9e3c6..d7d25356101 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -765,7 +765,7 @@ export default class BasicMessageEditor extends React.Component public render(): React.ReactNode { let autoComplete: JSX.Element | undefined; - if (this.state.autoComplete) { + if (this.state.autoComplete && this.state.query) { const query = this.state.query; const queryLen = query.length; autoComplete = ( @@ -800,8 +800,8 @@ export default class BasicMessageEditor extends React.Component const { completionIndex } = this.state; const hasAutocomplete = Boolean(this.state.autoComplete); let activeDescendant: string | undefined; - if (hasAutocomplete && completionIndex >= 0) { - activeDescendant = generateCompletionDomId(completionIndex); + if (hasAutocomplete && completionIndex! >= 0) { + activeDescendant = generateCompletionDomId(completionIndex!); } return ( diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index bed42fd0a89..96c66dddfb6 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -229,11 +229,11 @@ class EditMessageComposer extends React.Component { const item = SendHistoryManager.createItem(this.model); this.clearPreviousEdit(); - localStorage.setItem(this.editorRoomKey, this.props.editState.getEvent().getId()); + localStorage.setItem(this.editorRoomKey, this.props.editState.getEvent().getId()!); localStorage.setItem(this.editorStateKey, JSON.stringify(item)); }; @@ -329,7 +329,7 @@ class EditMessageComposer extends React.Component { if (ev.button != 0 || ev.metaKey) return; ev.preventDefault(); - let src = p["og:image"]; + let src: string | null | undefined = p["og:image"]; if (src?.startsWith("mxc://")) { src = mediaFromMxc(src).srcHttp; } + if (!src) return; + const params: Omit, "onFinished"> = { src: src, width: p["og:image:width"], diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts index 9c07288b182..e9cade175fb 100644 --- a/src/customisations/Media.ts +++ b/src/customisations/Media.ts @@ -77,7 +77,7 @@ export class Media { */ public get srcHttp(): string | null { // eslint-disable-next-line no-restricted-properties - return this.client.mxcUrlToHttp(this.srcMxc); + return this.client.mxcUrlToHttp(this.srcMxc) || null; } /** diff --git a/src/resizer/resizer.ts b/src/resizer/resizer.ts index d45898b525d..07e74337a77 100644 --- a/src/resizer/resizer.ts +++ b/src/resizer/resizer.ts @@ -44,7 +44,7 @@ export default class Resizer { // TODO move vertical/horizontal to config option/container class // as it doesn't make sense to mix them within one container/Resizer public constructor( - public container: HTMLElement, + public container: HTMLElement | null, private readonly distributorCtor: { new (item: ResizeItem): FixedDistributor; createItem( @@ -53,7 +53,7 @@ export default class Resizer { sizer: Sizer, container?: HTMLElement, ): ResizeItem; - createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer; + createSizer(containerElement: HTMLElement | null, vertical: boolean, reverse: boolean): Sizer; }, public readonly config?: C, ) { @@ -71,13 +71,13 @@ export default class Resizer { public attach(): void { const attachment = this?.config?.handler?.parentElement ?? this.container; - attachment.addEventListener("mousedown", this.onMouseDown, false); + attachment?.addEventListener("mousedown", this.onMouseDown, false); window.addEventListener("resize", this.onResize); } public detach(): void { const attachment = this?.config?.handler?.parentElement ?? this.container; - attachment.removeEventListener("mousedown", this.onMouseDown, false); + attachment?.removeEventListener("mousedown", this.onMouseDown, false); window.removeEventListener("resize", this.onResize); } @@ -194,7 +194,7 @@ export default class Resizer { const Distributor = this.distributorCtor; const useItemContainer = this.config?.handler ? this.container : undefined; const sizer = Distributor.createSizer(this.container, vertical, reverse); - const item = Distributor.createItem(resizeHandle, this, sizer, useItemContainer); + const item = Distributor.createItem(resizeHandle, this, sizer, useItemContainer ?? undefined); const distributor = new Distributor(item); return { sizer, distributor }; } From 9bc5cc87f32368add93c3b8c3a2683932e58fc37 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Apr 2023 10:33:18 +0100 Subject: [PATCH 2/5] Conform more of the codebase to `strictNullChecks` --- src/components/views/dialogs/CreateSubspaceDialog.tsx | 6 +++--- src/components/views/dialogs/ExportDialog.tsx | 6 +++--- .../views/dialogs/RegistrationEmailPromptDialog.tsx | 4 ++-- src/components/views/dialogs/spotlight/SpotlightDialog.tsx | 4 ++-- src/components/views/elements/MiniAvatarUploader.tsx | 4 ++-- src/components/views/elements/RoomTopic.tsx | 4 ++-- src/components/views/rooms/AuxPanel.tsx | 4 ++-- src/components/views/rooms/MessageComposerButtons.tsx | 4 ++-- src/components/views/rooms/ReadReceiptGroup.tsx | 2 +- src/components/views/rooms/ReadReceiptMarker.tsx | 2 +- src/components/views/rooms/RecentlyViewedButton.tsx | 4 ++-- src/components/views/rooms/ReplyTile.tsx | 2 +- src/components/views/spaces/SpaceBasicSettings.tsx | 4 ++-- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 447702bdae0..d994b9fc642 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { RefObject, useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { logger } from "matrix-js-sdk/src/logger"; @@ -41,9 +41,9 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick const [busy, setBusy] = useState(false); const [name, setName] = useState(""); - const spaceNameField = useRef() as RefObject; + const spaceNameField = useRef(null); const [alias, setAlias] = useState(""); - const spaceAliasField = useRef() as RefObject; + const spaceAliasField = useRef(null); const [avatar, setAvatar] = useState(); const [topic, setTopic] = useState(""); diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 7402c3413cc..7e01def9e18 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef, useState, Dispatch, SetStateAction, RefObject } from "react"; +import React, { useRef, useState, Dispatch, SetStateAction } from "react"; import { Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -104,8 +104,8 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { } = useExportFormState(); const [isExporting, setExporting] = useState(false); - const sizeLimitRef = useRef() as RefObject; - const messageCountRef = useRef() as RefObject; + const sizeLimitRef = useRef(null); + const messageCountRef = useRef(null); const [exportProgressText, setExportProgressText] = useState(_t("Processing…")); const [displayCancel, setCancelWarning] = useState(false); const [exportCancelled, setExportCancelled] = useState(false); diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index 426b1770381..df204093e9f 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import { RefObject, SyntheticEvent, useRef, useState } from "react"; +import { SyntheticEvent, useRef, useState } from "react"; import { _t, _td } from "../../../languageHandler"; import Field from "../elements/Field"; @@ -30,7 +30,7 @@ interface IProps { const RegistrationEmailPromptDialog: React.FC = ({ onFinished }) => { const [email, setEmail] = useState(""); - const fieldRef = useRef() as RefObject; + const fieldRef = useRef(null); const onSubmit = async (e: SyntheticEvent): Promise => { e.preventDefault(); diff --git a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx index b45b0582b86..05ab8c1749a 100644 --- a/src/components/views/dialogs/spotlight/SpotlightDialog.tsx +++ b/src/components/views/dialogs/spotlight/SpotlightDialog.tsx @@ -288,8 +288,8 @@ interface IDirectoryOpts { } const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = null, onFinished }) => { - const inputRef = useRef() as RefObject; - const scrollContainerRef = useRef() as RefObject; + const inputRef = useRef(null); + const scrollContainerRef = useRef(null); const cli = MatrixClientPeg.get(); const rovingContext = useContext(RovingTabIndexContext); const [query, _setQuery] = useState(initialText); diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx index 464c89ba237..663c8fd3d08 100644 --- a/src/components/views/elements/MiniAvatarUploader.tsx +++ b/src/components/views/elements/MiniAvatarUploader.tsx @@ -16,7 +16,7 @@ limitations under the License. import classNames from "classnames"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import React, { useContext, useRef, useState, MouseEvent, ReactNode, RefObject } from "react"; +import React, { useContext, useRef, useState, MouseEvent, ReactNode } from "react"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RoomContext from "../../../contexts/RoomContext"; @@ -59,7 +59,7 @@ const MiniAvatarUploader: React.FC = ({ setShow(false); }, 13000); // hide after being shown for 10 seconds - const uploadRef = useRef() as RefObject; + const uploadRef = useRef(null); const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel; diff --git a/src/components/views/elements/RoomTopic.tsx b/src/components/views/elements/RoomTopic.tsx index ad59012c78d..eb9ae028a48 100644 --- a/src/components/views/elements/RoomTopic.tsx +++ b/src/components/views/elements/RoomTopic.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { RefObject, useCallback, useContext, useRef } from "react"; +import React, { useCallback, useContext, useRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; import { EventType } from "matrix-js-sdk/src/@types/event"; @@ -38,7 +38,7 @@ interface IProps extends React.HTMLProps { export default function RoomTopic({ room, ...props }: IProps): JSX.Element { const client = useContext(MatrixClientContext); - const ref = useRef() as RefObject; + const ref = useRef(null); const topic = useTopic(room); const body = topicToHtml(topic?.text, topic?.html, ref); diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 66ce83d6e86..dfcc6f27a16 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -100,14 +100,14 @@ export default class AuxPanel extends React.Component { if (this.props.room && SettingsStore.getValue("feature_state_counters")) { const stateEvs = this.props.room.currentState.getStateEvents("re.jki.counter"); - stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey(), b.getStateKey())); + stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey()!, b.getStateKey()!)); for (const ev of stateEvs) { const title = ev.getContent().title; const value = ev.getContent().value; const link = ev.getContent().link; const severity = ev.getContent().severity || "normal"; - const stateKey = ev.getStateKey(); + const stateKey = ev.getStateKey()!; // We want a non-empty title but can accept falsy values (e.g. // zero) diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index 9572a118eb5..17990d3aa66 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -17,7 +17,7 @@ limitations under the License. import classNames from "classnames"; import { IEventRelation } from "matrix-js-sdk/src/models/event"; import { M_POLL_START } from "matrix-js-sdk/src/@types/polls"; -import React, { createContext, MouseEventHandler, ReactElement, ReactNode, RefObject, useContext, useRef } from "react"; +import React, { createContext, MouseEventHandler, ReactElement, ReactNode, useContext, useRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; @@ -180,7 +180,7 @@ interface IUploadButtonProps { const UploadButtonContextProvider: React.FC = ({ roomId, relation, children }) => { const cli = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); - const uploadInput = useRef() as RefObject; + const uploadInput = useRef(null); const onUploadClick = (): void => { if (cli?.isGuest()) { diff --git a/src/components/views/rooms/ReadReceiptGroup.tsx b/src/components/views/rooms/ReadReceiptGroup.tsx index 3472ee8db31..7eb625de478 100644 --- a/src/components/views/rooms/ReadReceiptGroup.tsx +++ b/src/components/views/rooms/ReadReceiptGroup.tsx @@ -293,7 +293,7 @@ interface ISectionHeaderProps { } function SectionHeader({ className, children }: PropsWithChildren): JSX.Element { - const ref = useRef(); + const ref = useRef(null); const [onFocus] = useRovingTabIndex(ref); return ( diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index e5e2fafdd75..6d453085d54 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -124,7 +124,7 @@ export default class ReadReceiptMarker extends React.PureComponent { - const tooltipRef = useRef() as RefObject; + const tooltipRef = useRef(null); const crumbs = useEventEmitterState(BreadcrumbsStore.instance, UPDATE_EVENT, () => BreadcrumbsStore.instance.rooms); const content = ( diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index b9a5766dd9a..f3fbf73db81 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -134,7 +134,7 @@ export default class ReplyTile extends React.PureComponent { let permalink = "#"; if (this.props.permalinkCreator) { - permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()); + permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()!); } let sender; diff --git a/src/components/views/spaces/SpaceBasicSettings.tsx b/src/components/views/spaces/SpaceBasicSettings.tsx index 5ce844cc863..30c05c8ddd5 100644 --- a/src/components/views/spaces/SpaceBasicSettings.tsx +++ b/src/components/views/spaces/SpaceBasicSettings.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ChangeEvent, RefObject, useRef, useState } from "react"; +import React, { ChangeEvent, useRef, useState } from "react"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; @@ -38,7 +38,7 @@ export const SpaceAvatar: React.FC { - const avatarUploadRef = useRef() as RefObject; + const avatarUploadRef = useRef(null); const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache let avatarSection; From 2b4bf1e518b9b9aff4e3519479b559e942689f04 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Apr 2023 10:35:58 +0100 Subject: [PATCH 3/5] Fix types --- src/components/structures/LoggedInView.tsx | 2 +- .../structures/AutocompleteInput-test.tsx | 16 ++++++++++++++-- .../utils/autocomplete-test.ts | 2 ++ .../views/settings/AddPrivilegedUsers-test.tsx | 18 +++++++++++++++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index c14e081a80f..9c72b269f1c 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -231,7 +231,7 @@ class LoggedInView extends React.Component { }; private createResizer(): Resizer { - let panelSize: number; + let panelSize: number | null; let panelCollapsed: boolean; const collapseConfig: ICollapseConfig = { // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel diff --git a/test/components/structures/AutocompleteInput-test.tsx b/test/components/structures/AutocompleteInput-test.tsx index 9827ba2ddae..76a007a25e7 100644 --- a/test/components/structures/AutocompleteInput-test.tsx +++ b/test/components/structures/AutocompleteInput-test.tsx @@ -24,8 +24,20 @@ import { AutocompleteInput } from "../../../src/components/structures/Autocomple describe("AutocompleteInput", () => { const mockCompletion: ICompletion[] = [ - { type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } }, + { + type: "user", + completion: "user_1", + completionId: "@user_1:host.local", + range: { start: 1, end: 1 }, + component:
, + }, + { + type: "user", + completion: "user_2", + completionId: "@user_2:host.local", + range: { start: 1, end: 1 }, + component:
, + }, ]; const constructMockProvider = (data: ICompletion[]) => diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 7629a5a2913..1c58608b498 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { mocked } from "jest-mock"; +import React from "react"; import { ICompletion } from "../../../../../../src/autocomplete/Autocompleter"; import { @@ -34,6 +35,7 @@ const createMockCompletion = (props: Partial): ICompletion => { return { completion: "mock", range: { beginning: true, start: 0, end: 0 }, + component: React.createElement("div"), ...props, }; }; diff --git a/test/components/views/settings/AddPrivilegedUsers-test.tsx b/test/components/views/settings/AddPrivilegedUsers-test.tsx index 68acb7395db..118529349ce 100644 --- a/test/components/views/settings/AddPrivilegedUsers-test.tsx +++ b/test/components/views/settings/AddPrivilegedUsers-test.tsx @@ -32,9 +32,21 @@ import { ICompletion } from "../../../../src/autocomplete/Autocompleter"; jest.mock("../../../../src/autocomplete/UserProvider"); const completions: ICompletion[] = [ - { type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } }, - { type: "user", completion: "user_without_completion_id", range: { start: 1, end: 1 } }, + { + component:
, + type: "user", + completion: "user_1", + completionId: "@user_1:host.local", + range: { start: 1, end: 1 }, + }, + { + component:
, + type: "user", + completion: "user_2", + completionId: "@user_2:host.local", + range: { start: 1, end: 1 }, + }, + { component:
, type: "user", completion: "user_without_completion_id", range: { start: 1, end: 1 } }, ]; describe("", () => { From 4f53babedff861e6598d0a77508355eb80d8677e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Apr 2023 13:03:56 +0100 Subject: [PATCH 4/5] Conform more of the codebase to `strictNullChecks` --- src/components/structures/MessagePanel.tsx | 23 +++++++++++------- .../elements/IRCTimelineProfileResizer.tsx | 2 +- src/components/views/settings/BridgeTile.tsx | 2 +- .../views/settings/ChangePassword.tsx | 2 +- .../views/settings/CrossSigningPanel.tsx | 6 ++--- .../views/settings/DevicesPanelEntry.tsx | 6 ++--- .../views/settings/EventIndexPanel.tsx | 24 +++++++------------ .../views/settings/FontScalingPanel.tsx | 4 ++-- .../views/settings/JoinRuleSettings.tsx | 10 ++++---- .../views/settings/ProfileSettings.tsx | 11 ++++----- .../views/settings/SecureBackupPanel.tsx | 6 ++--- .../views/settings/devices/DeviceDetails.tsx | 6 ++--- .../views/settings/devices/DeviceTypeIcon.tsx | 4 ++-- 13 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index dd2ce65efb5..ab01fad9397 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -327,7 +327,11 @@ export default class MessagePanel extends React.Component { } private shouldHideSender(): boolean { - return this.props.room?.getInvitedAndJoinedMemberCount() <= 2 && this.props.layout === Layout.Bubble; + return ( + !!this.props.room && + this.props.room.getInvitedAndJoinedMemberCount() <= 2 && + this.props.layout === Layout.Bubble + ); } private calculateRoomMembersCount = (): void => { @@ -465,7 +469,7 @@ export default class MessagePanel extends React.Component { } } - if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender())) { + if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender()!)) { return false; // ignored = no show (only happens if the ignore happens after an event was received) } @@ -647,7 +651,7 @@ export default class MessagePanel extends React.Component { for (let i = 0; i < events.length; i++) { const eventAndShouldShow = events[i]; const { event, shouldShow } = eventAndShouldShow; - const eventId = event.getId(); + const eventId = event.getId()!; const last = event === lastShownEvent; const { nextEventAndShouldShow, nextTile } = this.getNextEventInfo(events, i); @@ -745,7 +749,7 @@ export default class MessagePanel extends React.Component { !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents, this.context.timelineRenderingType); - const eventId = mxEv.getId(); + const eventId = mxEv.getId()!; const highlight = eventId === this.props.highlightedEventId; const readReceipts = this.readReceiptsByEvent.get(eventId); @@ -1075,7 +1079,7 @@ abstract class BaseGrouper { public readonly nextEventTile?: MatrixEvent | null, ) { this.readMarker = panel.readMarkerForEvent( - firstEventAndShouldShow.event.getId(), + firstEventAndShouldShow.event.getId()!, firstEventAndShouldShow.event === lastShownEvent, ); } @@ -1143,7 +1147,7 @@ class CreationGrouper extends BaseGrouper { public add({ event: ev, shouldShow }: EventAndShouldShow): void { const panel = this.panel; - this.readMarker = this.readMarker || panel.readMarkerForEvent(ev.getId(), ev === this.lastShownEvent); + this.readMarker = this.readMarker || panel.readMarkerForEvent(ev.getId()!, ev === this.lastShownEvent); if (!shouldShow) { return; } @@ -1295,7 +1299,7 @@ class MainGrouper extends BaseGrouper { // We can ignore any events that don't actually have a message to display if (!hasText(ev, this.panel.showHiddenEvents)) return; } - this.readMarker = this.readMarker || this.panel.readMarkerForEvent(ev.getId(), ev === this.lastShownEvent); + this.readMarker = this.readMarker || this.panel.readMarkerForEvent(ev.getId()!, ev === this.lastShownEvent); if (!this.panel.showHiddenEvents && !shouldShow) { // absorb hidden events to not split the summary return; @@ -1331,7 +1335,10 @@ class MainGrouper extends BaseGrouper { // This will prevent it from being re-created unnecessarily, and instead will allow new props to be provided. // In turn, the shouldComponentUpdate method on ELS can be used to prevent unnecessary renderings. const keyEvent = this.events.find((e) => this.panel.grouperKeyMap.get(e)); - const key = keyEvent ? this.panel.grouperKeyMap.get(keyEvent) : this.generateKey(); + const key = + keyEvent && this.panel.grouperKeyMap.has(keyEvent) + ? this.panel.grouperKeyMap.get(keyEvent)! + : this.generateKey(); if (!keyEvent) { // Populate the weak map with the key. // Note that we only set the key on the specific event it refers to, since this group might get diff --git a/src/components/views/elements/IRCTimelineProfileResizer.tsx b/src/components/views/elements/IRCTimelineProfileResizer.tsx index 48bf1a188dc..bae76d3f879 100644 --- a/src/components/views/elements/IRCTimelineProfileResizer.tsx +++ b/src/components/views/elements/IRCTimelineProfileResizer.tsx @@ -22,7 +22,7 @@ import { SettingLevel } from "../../../settings/SettingLevel"; interface IProps { // Current room - roomId: string; + roomId: string | null; minWidth: number; maxWidth: number; } diff --git a/src/components/views/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx index de40898f0d7..673337f39ba 100644 --- a/src/components/views/settings/BridgeTile.tsx +++ b/src/components/views/settings/BridgeTile.tsx @@ -97,7 +97,7 @@ export default class BridgeTile extends React.PureComponent { ), diff --git a/src/components/views/settings/ChangePassword.tsx b/src/components/views/settings/ChangePassword.tsx index 9978985ad87..8ea83263250 100644 --- a/src/components/views/settings/ChangePassword.tsx +++ b/src/components/views/settings/ChangePassword.tsx @@ -213,7 +213,7 @@ export default class ChangePassword extends React.Component { const modal = Modal.createDialog(SetEmailDialog, { title: _t("Do you want to set an email address?"), }); - return modal.finished.then(([confirmed]) => confirmed); + return modal.finished.then(([confirmed]) => !!confirmed); } private onExportE2eKeysClicked = (): void => { diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index 0ba8f580f58..e3f62d4ba26 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -94,9 +94,9 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { const secretStorage = cli.crypto!.secretStorage; const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId()); const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage)); - const masterPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("master"))); - const selfSigningPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("self_signing"))); - const userSigningPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("user_signing"))); + const masterPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("master")); + const selfSigningPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("self_signing")); + const userSigningPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("user_signing")); const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature( "org.matrix.e2e_cross_signing", ); diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index 57f0bc24b29..95bcdb9e949 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -51,7 +51,7 @@ export default class DevicesPanelEntry extends React.Component { super(props); this.state = { renaming: false, - displayName: props.device.display_name, + displayName: props.device.display_name ?? "", }; } @@ -103,11 +103,11 @@ export default class DevicesPanelEntry extends React.Component { }); } else { const cli = MatrixClientPeg.get(); - const userId = cli.getUserId()!; + const userId = cli.getSafeUserId(); const verificationRequestPromise = cli.requestVerification(userId, [this.props.device.device_id]); Modal.createDialog(VerificationRequestDialog, { verificationRequestPromise, - member: cli.getUser(userId), + member: cli.getUser(userId) ?? undefined, onFinished: async (): Promise => { const request = await verificationRequestPromise; request.cancel(); diff --git a/src/components/views/settings/EventIndexPanel.tsx b/src/components/views/settings/EventIndexPanel.tsx index 8a1d93c98f5..ea4c2b8c5c4 100644 --- a/src/components/views/settings/EventIndexPanel.tsx +++ b/src/components/views/settings/EventIndexPanel.tsx @@ -26,7 +26,6 @@ import EventIndexPeg from "../../../indexing/EventIndexPeg"; import { SettingLevel } from "../../../settings/SettingLevel"; import SeshatResetDialog from "../dialogs/SeshatResetDialog"; import InlineSpinner from "../elements/InlineSpinner"; -import { IIndexStats } from "../../../indexing/BaseEventIndexManager"; interface IState { enabling: boolean; @@ -49,15 +48,9 @@ export default class EventIndexPanel extends React.Component<{}, IState> { public updateCurrentRoom = async (): Promise => { const eventIndex = EventIndexPeg.get(); - let stats: IIndexStats | undefined; - - try { - stats = await eventIndex?.getStats(); - } catch { - // This call may fail if sporadically, not a huge issue as we will - // try later again and probably succeed. - return; - } + const stats = await eventIndex?.getStats().catch(() => {}); + // This call may fail if sporadically, not a huge issue as we will try later again and probably succeed. + if (!stats) return; this.setState({ eventIndexSize: stats.size, @@ -88,14 +81,13 @@ export default class EventIndexPanel extends React.Component<{}, IState> { if (eventIndex !== null) { eventIndex.on("changedCheckpoint", this.updateCurrentRoom); - try { - const stats = await eventIndex.getStats(); + const stats = await eventIndex.getStats().catch(() => {}); + // This call may fail if sporadically, not a huge issue as we + // will try later again in the updateCurrentRoom call and + // probably succeed. + if (stats) { eventIndexSize = stats.size; roomCount = stats.roomCount; - } catch { - // This call may fail if sporadically, not a huge issue as we - // will try later again in the updateCurrentRoom call and - // probably succeed. } } diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx index 49fe5576718..03a8b963b8a 100644 --- a/src/components/views/settings/FontScalingPanel.tsx +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -83,7 +83,7 @@ export default class FontScalingPanel extends React.Component { }; private onValidateFontSize = async ({ value }: Pick): Promise => { - const parsedSize = parseFloat(value); + const parsedSize = parseFloat(value!); const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; @@ -98,7 +98,7 @@ export default class FontScalingPanel extends React.Component { }; } - SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, parseInt(value, 10) - FontWatcher.SIZE_DIFF); + SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, parseInt(value!, 10) - FontWatcher.SIZE_DIFF); return { valid: true, feedback: _t("Use between %(min)s pt and %(max)s pt", { min, max }) }; }; diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index 5b1c5e0bbf0..5706aa4dfaa 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -61,7 +61,7 @@ const JoinRuleSettings: React.FC = ({ const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli); - const [content, setContent] = useLocalEcho( + const [content, setContent] = useLocalEcho( () => room.currentState.getStateEvents(EventType.RoomJoinRules, "")?.getContent(), (content) => cli.sendStateEvent(room.roomId, EventType.RoomJoinRules, content, ""), onError, @@ -70,10 +70,10 @@ const JoinRuleSettings: React.FC = ({ const { join_rule: joinRule = JoinRule.Invite } = content || {}; const restrictedAllowRoomIds = joinRule === JoinRule.Restricted - ? content.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) + ? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) : undefined; - const editRestrictedRoomIds = async (): Promise => { + const editRestrictedRoomIds = async (): Promise => { let selected = restrictedAllowRoomIds; if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { selected = [SpaceStore.instance.activeSpaceRoom.roomId]; @@ -207,7 +207,7 @@ const JoinRuleSettings: React.FC = ({ "Anyone in can find and join. You can select other spaces too.", {}, { - spaceName: () => {SpaceStore.instance.activeSpaceRoom.name}, + spaceName: () => {SpaceStore.instance.activeSpaceRoom!.name}, }, ); } else { @@ -229,7 +229,7 @@ const JoinRuleSettings: React.FC = ({ } const onChange = async (joinRule: JoinRule): Promise => { - const beforeJoinRule = content.join_rule; + const beforeJoinRule = content?.join_rule; let restrictedAllowRoomIds: string[] | undefined; if (joinRule === JoinRule.Restricted) { diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index 4a36b44973f..d7fdd9c143f 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -31,7 +31,6 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds"; import PosthogTrackers from "../../../PosthogTrackers"; interface IState { - userId?: string; originalDisplayName: string; displayName: string; originalAvatarUrl: string | null; @@ -41,16 +40,16 @@ interface IState { } export default class ProfileSettings extends React.Component<{}, IState> { + private readonly userId: string; private avatarUpload: React.RefObject = createRef(); public constructor(props: {}) { super(props); - const client = MatrixClientPeg.get(); + this.userId = MatrixClientPeg.get().getSafeUserId(); let avatarUrl = OwnProfileStore.instance.avatarMxc; if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96); this.state = { - userId: client.getUserId()!, originalDisplayName: OwnProfileStore.instance.displayName ?? "", displayName: OwnProfileStore.instance.displayName ?? "", originalAvatarUrl: avatarUrl, @@ -150,7 +149,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { const reader = new FileReader(); reader.onload = (ev) => { this.setState({ - avatarUrl: ev.target?.result, + avatarUrl: ev.target?.result ?? undefined, avatarFile: file, enableProfileSave: true, }); @@ -159,7 +158,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { }; public render(): React.ReactNode { - const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.state.userId, { + const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.userId, { withDisplayName: true, }); @@ -198,7 +197,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
{ return
{sigStatus}
; }); - if (backupSigStatus.sigs.length === 0) { + if (!backupSigStatus?.sigs?.length) { backupSigStatuses = _t("Backup is not signed by any of your sessions"); } - let trustedLocally; - if (backupSigStatus.trusted_locally) { + let trustedLocally: string | undefined; + if (backupSigStatus?.trusted_locally) { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 56df38c6cfe..fea916eb24b 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -105,13 +105,13 @@ const DeviceDetails: React.FC = ({ const showPushNotificationSection = !!pusher || !!localNotificationSettings; - function isPushNotificationsEnabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean { - if (pusher) return pusher[PUSHER_ENABLED.name]; + function isPushNotificationsEnabled(pusher?: IPusher, notificationSettings?: LocalNotificationSettings): boolean { + if (pusher) return !!pusher[PUSHER_ENABLED.name]; if (localNotificationSettings) return !localNotificationSettings.is_silenced; return true; } - function isCheckboxDisabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean { + function isCheckboxDisabled(pusher?: IPusher, notificationSettings?: LocalNotificationSettings): boolean { if (localNotificationSettings) return false; if (pusher && !supportsMSC3881) return true; return false; diff --git a/src/components/views/settings/devices/DeviceTypeIcon.tsx b/src/components/views/settings/devices/DeviceTypeIcon.tsx index 06360c9a2f0..c2fc0f5f2ef 100644 --- a/src/components/views/settings/devices/DeviceTypeIcon.tsx +++ b/src/components/views/settings/devices/DeviceTypeIcon.tsx @@ -47,8 +47,8 @@ const deviceTypeLabel: Record = { }; export const DeviceTypeIcon: React.FC = ({ isVerified, isSelected, deviceType }) => { - const Icon = deviceTypeIcon[deviceType] || deviceTypeIcon[DeviceType.Unknown]; - const label = deviceTypeLabel[deviceType] || deviceTypeLabel[DeviceType.Unknown]; + const Icon = deviceTypeIcon[deviceType!] || deviceTypeIcon[DeviceType.Unknown]; + const label = deviceTypeLabel[deviceType!] || deviceTypeLabel[DeviceType.Unknown]; return (
Date: Fri, 14 Apr 2023 13:25:13 +0100 Subject: [PATCH 5/5] Conform more of the codebase to `strictNullChecks` --- src/components/views/dialogs/ExportDialog.tsx | 4 ++-- src/components/views/dialogs/FeedbackDialog.tsx | 2 +- src/components/views/spaces/SpaceCreateMenu.tsx | 4 ++-- src/components/views/spaces/SpacePanel.tsx | 4 ++-- src/stores/spaces/SpaceStore.ts | 5 +++++ test/components/structures/ThreadPanel-test.tsx | 14 +++++++------- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 7e01def9e18..7e01d96b425 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -182,7 +182,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { { key: "number", test: ({ value }) => { - const parsedSize = parseInt(value, 10); + const parsedSize = parseInt(value!, 10); return validateNumberInRange(1, 2000)(parsedSize); }, invalid: () => { @@ -218,7 +218,7 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { { key: "number", test: ({ value }) => { - const parsedSize = parseInt(value, 10); + const parsedSize = parseInt(value!, 10); return validateNumberInRange(1, 10 ** 8)(parsedSize); }, invalid: () => { diff --git a/src/components/views/dialogs/FeedbackDialog.tsx b/src/components/views/dialogs/FeedbackDialog.tsx index 9996e65d2e7..7ee24e05a45 100644 --- a/src/components/views/dialogs/FeedbackDialog.tsx +++ b/src/components/views/dialogs/FeedbackDialog.tsx @@ -38,7 +38,7 @@ interface IProps { } const FeedbackDialog: React.FC = (props: IProps) => { - const feedbackRef = useRef(); + const feedbackRef = useRef(null); const [comment, setComment] = useState(""); const [canContact, toggleCanContact] = useStateToggle(false); diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 344067203db..64fc408b774 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -212,9 +212,9 @@ const SpaceCreateMenu: React.FC<{ const [busy, setBusy] = useState(false); const [name, setName] = useState(""); - const spaceNameField = useRef(); + const spaceNameField = useRef(null); const [alias, setAlias] = useState(""); - const spaceAliasField = useRef(); + const spaceAliasField = useRef(null); const [avatar, setAvatar] = useState(undefined); const [topic, setTopic] = useState(""); diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index a138b909e0e..8e95f29667e 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -331,9 +331,9 @@ const InnerSpacePanel = React.memo( const SpacePanel: React.FC = () => { const [isPanelCollapsed, setPanelCollapsed] = useState(true); - const ref = useRef(); + const ref = useRef(null); useLayoutEffect(() => { - UIStore.instance.trackElementDimensions("SpacePanel", ref.current); + if (ref.current) UIStore.instance.trackElementDimensions("SpacePanel", ref.current); return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel"); }, []); diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 06dfa66971f..5d94f1e3b07 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -553,6 +553,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private rebuildSpaceHierarchy = (): void => { + if (!this.matrixClient) return; const visibleSpaces = this.matrixClient .getVisibleRooms(this._msc3946ProcessDynamicPredecessor) .filter((r) => r.isSpaceRoom()); @@ -589,6 +590,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private rebuildParentMap = (): void => { + if (!this.matrixClient) return; const joinedSpaces = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor).filter((r) => { return r.isSpaceRoom() && r.getMyMembership() === "join"; }); @@ -624,6 +626,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private rebuildMetaSpaces = (): void => { + if (!this.matrixClient) return; const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); @@ -658,6 +661,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private updateNotificationStates = (spaces?: SpaceKey[]): void => { + if (!this.matrixClient) return; const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); @@ -745,6 +749,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { }; private onRoomsUpdate = (): void => { + if (!this.matrixClient) return; const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); const prevRoomsBySpace = this.roomIdsBySpace; diff --git a/test/components/structures/ThreadPanel-test.tsx b/test/components/structures/ThreadPanel-test.tsx index a851d0cf9b7..b6cf8431479 100644 --- a/test/components/structures/ThreadPanel-test.tsx +++ b/test/components/structures/ThreadPanel-test.tsx @@ -133,7 +133,7 @@ describe("ThreadPanel", () => { jest.spyOn(mockClient, "getRoom").mockReturnValue(room); await room.createThreadsTimelineSets(); const [allThreads, myThreads] = room.threadsTimelineSets; - jest.spyOn(room, "createThreadsTimelineSets").mockReturnValue(Promise.resolve([allThreads, myThreads])); + jest.spyOn(room, "createThreadsTimelineSets").mockReturnValue(Promise.resolve([allThreads!, myThreads!])); }); function toggleThreadFilter(container: HTMLElement, newFilter: ThreadFilterType) { @@ -195,11 +195,11 @@ describe("ThreadPanel", () => { return event ? Promise.resolve(event) : Promise.reject(); }); const [allThreads, myThreads] = room.threadsTimelineSets; - allThreads.addLiveEvent(otherThread.rootEvent); - allThreads.addLiveEvent(mixedThread.rootEvent); - allThreads.addLiveEvent(ownThread.rootEvent); - myThreads.addLiveEvent(mixedThread.rootEvent); - myThreads.addLiveEvent(ownThread.rootEvent); + allThreads!.addLiveEvent(otherThread.rootEvent); + allThreads!.addLiveEvent(mixedThread.rootEvent); + allThreads!.addLiveEvent(ownThread.rootEvent); + myThreads!.addLiveEvent(mixedThread.rootEvent); + myThreads!.addLiveEvent(ownThread.rootEvent); let events: EventData[] = []; const renderResult = render(); @@ -245,7 +245,7 @@ describe("ThreadPanel", () => { return event ? Promise.resolve(event) : Promise.reject(); }); const [allThreads] = room.threadsTimelineSets; - allThreads.addLiveEvent(otherThread.rootEvent); + allThreads!.addLiveEvent(otherThread.rootEvent); let events: EventData[] = []; const renderResult = render();