From e8b92b308be8c1e975d52afba0d001fea33f6f6b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Feb 2023 09:38:44 +0000 Subject: [PATCH] Conform more code to strict null checking (#10169) * Conform more code to strict null checking * delint * Iterate * delint * Fix bad test --- src/components/structures/LeftPanel.tsx | 2 +- .../security/AccessSecretStorageDialog.tsx | 16 +++---- src/components/views/elements/Field.tsx | 12 +++--- .../room_settings/UrlPreviewSettings.tsx | 8 ++-- .../views/settings/LayoutSwitcher.tsx | 6 +-- .../views/settings/Notifications.tsx | 16 +++---- .../views/settings/ProfileSettings.tsx | 34 +++++++-------- .../views/settings/SetIntegrationManager.tsx | 2 +- .../views/settings/ThemeChoicePanel.tsx | 12 +++--- .../views/settings/UpdateCheckButton.tsx | 10 ++--- .../views/settings/devices/DeviceDetails.tsx | 8 ++-- .../settings/devices/LoginWithQRSection.tsx | 2 +- .../tabs/room/AdvancedRoomSettingsTab.tsx | 23 +++++----- .../settings/tabs/room/BridgeSettingsTab.tsx | 11 +++-- .../tabs/room/GeneralRoomSettingsTab.tsx | 11 +++-- .../tabs/room/NotificationSettingsTab.tsx | 6 +-- .../tabs/room/RolesRoomSettingsTab.tsx | 20 ++++----- .../tabs/room/SecurityRoomSettingsTab.tsx | 42 +++++++++---------- .../tabs/user/AppearanceUserSettingsTab.tsx | 15 +++---- .../tabs/user/HelpUserSettingsTab.tsx | 16 +++---- .../tabs/user/MjolnirUserSettingsTab.tsx | 8 ++-- .../tabs/user/SecurityUserSettingsTab.tsx | 6 +-- .../tabs/user/VoiceUserSettingsTab.tsx | 22 +++++----- .../views/spaces/QuickSettingsButton.tsx | 4 +- .../views/spaces/SpaceSettingsGeneralTab.tsx | 10 ++--- .../spaces/SpaceSettingsVisibilityTab.tsx | 6 +-- .../views/terms/InlineTermsAgreement.tsx | 2 +- .../views/toasts/VerificationRequestToast.tsx | 4 +- .../user-onboarding/UserOnboardingButton.tsx | 2 +- .../UserOnboardingFeedback.tsx | 2 +- .../user-onboarding/UserOnboardingTask.tsx | 2 +- src/components/views/voip/CallView.tsx | 6 +-- src/components/views/voip/LegacyCallView.tsx | 4 +- src/editor/dom.ts | 6 ++- src/editor/parts.ts | 4 +- src/hooks/useAsyncMemo.ts | 6 ++- src/hooks/useUserOnboardingTasks.ts | 2 +- src/languageHandler.tsx | 8 ++-- src/stores/VoiceRecordingStore.ts | 2 +- src/stores/widgets/StopGapWidgetDriver.ts | 20 ++++----- src/utils/beacon/bounds.ts | 10 +++-- src/widgets/CapabilityText.tsx | 6 +-- src/widgets/Jitsi.ts | 2 +- test/PosthogAnalytics-test.ts | 13 +++--- test/ScalarAuthClient-test.ts | 2 +- test/SlashCommands-test.tsx | 4 +- test/SlidingSyncManager-test.ts | 2 +- test/audio/VoiceMessageRecording-test.ts | 12 +++--- test/components/structures/RoomView-test.tsx | 4 +- .../components/structures/auth/Login-test.tsx | 4 +- .../structures/auth/Registration-test.tsx | 4 +- .../views/beacon/BeaconListItem-test.tsx | 4 +- .../views/beacon/ShareLatestLocation-test.tsx | 2 +- .../context_menus/MessageContextMenu-test.tsx | 2 +- .../AccessSecretStorageDialog-test.tsx | 1 - .../views/elements/EventListSummary-test.tsx | 12 +++--- .../views/elements/LearnMore-test.tsx | 5 ++- .../views/elements/TooltipTarget-test.tsx | 2 +- .../MKeyVerificationConclusion-test.tsx | 8 ++-- .../views/messages/MLocationBody-test.tsx | 4 +- .../views/messages/MessageEvent-test.tsx | 2 +- .../right_panel/PinnedMessagesCard-test.tsx | 2 +- .../views/rooms/MemberList-test.tsx | 2 +- .../views/rooms/MessageComposer-test.tsx | 2 +- .../views/rooms/RoomHeader-test.tsx | 2 +- .../views/settings/KeyboardShortcut-test.tsx | 2 +- .../settings/devices/DeviceDetails-test.tsx | 1 - .../devices/SelectableDeviceTile-test.tsx | 2 +- .../settings/devices/deleteDevices-test.tsx | 2 +- .../tabs/user/SessionManagerTab-test.tsx | 11 ++--- test/editor/deserialize-test.ts | 2 +- test/editor/roundtrip-test.ts | 2 +- test/hooks/useProfileInfo-test.tsx | 2 +- test/models/Call-test.ts | 2 +- test/modules/AppModule-test.ts | 2 +- test/modules/MockModule.ts | 2 +- test/stores/SpaceStore-test.ts | 6 +-- test/stores/VoiceRecordingStore-test.ts | 2 +- test/stores/room-list/RoomListStore-test.ts | 2 +- .../algorithms/RecentAlgorithm-test.ts | 1 + test/utils/beacon/geolocation-test.ts | 4 +- .../models/VoiceBroadcastRecording-test.ts | 2 +- .../VoiceBroadcastRecordingsStore-test.ts | 8 ++-- .../utils/VoiceBroadcastResumer-test.ts | 2 +- .../startNewVoiceBroadcastRecording-test.ts | 2 +- 85 files changed, 283 insertions(+), 287 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 564484090a..2ea8920a19 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -68,7 +68,7 @@ interface IState { export default class LeftPanel extends React.Component { private listContainerRef = createRef(); private roomListRef = createRef(); - private focusedElement: Element = null; + private focusedElement: Element | null = null; private isDoingStickyHeaders = false; public constructor(props: IProps) { diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index 4873173a3e..0cf19b4eb2 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -41,7 +41,7 @@ const KEY_FILE_MAX_SIZE = 128; const VALIDATION_THROTTLE_MS = 200; interface IProps extends IDialogProps { - keyInfo: ISecretStorageKeyInfo; + keyInfo?: ISecretStorageKeyInfo; checkPrivateKey: (k: { passphrase?: string; recoveryKey?: string }) => boolean; } @@ -137,7 +137,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent): Promise => { - if (ev.target.files.length === 0) return; + if (!ev.target.files?.length) return; const f = ev.target.files[0]; @@ -170,7 +170,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { - this.fileUpload.current.click(); + this.fileUpload.current?.click(); }; private onPassPhraseNext = async (ev: FormEvent | React.MouseEvent): Promise => { @@ -269,15 +269,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent - {_t("Forgotten or lost all recovery methods? Reset all", null, { + {_t("Forgotten or lost all recovery methods? Reset all", undefined, { a: (sub) => ( diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 1afe52810b..bcf8b00d4f 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -112,8 +112,8 @@ export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes { public constructor(props: PropShapes) { super(props); this.state = { - valid: undefined, - feedback: undefined, feedbackVisible: false, focused: false, }; @@ -199,7 +197,7 @@ export default class Field extends React.PureComponent { this.props.onBlur?.(ev); }; - public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise { + public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise { if (!this.props.onValidate) { return; } @@ -268,11 +266,11 @@ export default class Field extends React.PureComponent { const fieldInput = React.createElement(this.props.element, inputProps_, children); - let prefixContainer = null; + let prefixContainer: JSX.Element | undefined; if (prefixComponent) { prefixContainer = {prefixComponent}; } - let postfixContainer = null; + let postfixContainer: JSX.Element | undefined; if (postfixComponent) { postfixContainer = {postfixComponent}; } diff --git a/src/components/views/room_settings/UrlPreviewSettings.tsx b/src/components/views/room_settings/UrlPreviewSettings.tsx index d24d878b40..ead58aa328 100644 --- a/src/components/views/room_settings/UrlPreviewSettings.tsx +++ b/src/components/views/room_settings/UrlPreviewSettings.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { _t, _td } from "../../../languageHandler"; @@ -41,12 +41,12 @@ export default class UrlPreviewSettings extends React.Component { dis.fire(Action.ViewUserSettings); }; - public render(): React.ReactNode { + public render(): ReactNode { const roomId = this.props.room.roomId; const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); - let previewsForAccount = null; - let previewsForRoom = null; + let previewsForAccount: ReactNode | undefined; + let previewsForRoom: ReactNode | undefined; if (!isEncrypted) { // Only show account setting state and room state setting state in non-e2ee rooms where they apply diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx index 3e265857fc..462147df2e 100644 --- a/src/components/views/settings/LayoutSwitcher.tsx +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -28,10 +28,10 @@ import { SettingLevel } from "../../../settings/SettingLevel"; interface IProps { userId?: string; - displayName: string; - avatarUrl: string; + displayName?: string; + avatarUrl?: string; messagePreviewText: string; - onLayoutChanged?: (layout: Layout) => void; + onLayoutChanged: (layout: Layout) => void; } interface IState { diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index 2278425db8..b4ed0c680f 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import { logger } from "matrix-js-sdk/src/logger"; @@ -152,7 +152,7 @@ export default class Notifications extends React.PureComponent { // the master rule is *enabled* it means all other rules are *disabled* (or // inhibited). Conversely, when the master rule is *disabled* then all other rules // are *enabled* (or operate fine). - return this.state.masterPushRule?.enabled; + return !!this.state.masterPushRule?.enabled; } public componentDidMount(): void { @@ -622,12 +622,12 @@ export default class Notifications extends React.PureComponent { ); } - private renderCategory(category: RuleClass): JSX.Element { + private renderCategory(category: RuleClass): ReactNode { if (category !== RuleClass.VectorOther && this.isInhibited) { return null; // nothing to show for the section } - let clearNotifsButton: JSX.Element; + let clearNotifsButton: JSX.Element | undefined; if ( category === RuleClass.VectorOther && MatrixClientPeg.get() @@ -660,7 +660,7 @@ export default class Notifications extends React.PureComponent { return null; } - let keywordComposer: JSX.Element; + let keywordComposer: JSX.Element | undefined; if (category === RuleClass.VectorMentions) { keywordComposer = ( { /> ); - const fieldsetRows = this.state.vectorPushRules[category].map((r) => ( + const fieldsetRows = this.state.vectorPushRules[category]?.map((r) => (
{ ); } - private renderTargets(): JSX.Element { + private renderTargets(): ReactNode { if (this.isInhibited) return null; // no targets if there's no notifications - const rows = this.state.pushers.map((p) => ( + const rows = this.state.pushers?.map((p) => ( {p.app_display_name} {p.device_display_name} diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index 0a7bd4a042..5bba1c51b3 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -34,11 +34,11 @@ import PosthogTrackers from "../../../PosthogTrackers"; interface IState { userId?: string; - originalDisplayName?: string; - displayName?: string; - originalAvatarUrl?: string; + originalDisplayName: string; + displayName: string; + originalAvatarUrl: string | null; avatarUrl?: string | ArrayBuffer; - avatarFile?: File; + avatarFile?: File | null; enableProfileSave?: boolean; } @@ -52,25 +52,25 @@ export default class ProfileSettings extends React.Component<{}, IState> { 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, + userId: client.getUserId()!, + originalDisplayName: OwnProfileStore.instance.displayName ?? "", + displayName: OwnProfileStore.instance.displayName ?? "", originalAvatarUrl: avatarUrl, - avatarUrl: avatarUrl, + avatarUrl: avatarUrl ?? undefined, avatarFile: null, enableProfileSave: false, }; } private uploadAvatar = (): void => { - this.avatarUpload.current.click(); + this.avatarUpload.current?.click(); }; private removeAvatar = (): void => { // clear file upload field so same file can be selected this.avatarUpload.current.value = ""; this.setState({ - avatarUrl: null, + avatarUrl: undefined, avatarFile: null, enableProfileSave: true, }); @@ -84,7 +84,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { this.setState({ enableProfileSave: false, displayName: this.state.originalDisplayName, - avatarUrl: this.state.originalAvatarUrl, + avatarUrl: this.state.originalAvatarUrl ?? undefined, avatarFile: null, }); }; @@ -97,7 +97,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { this.setState({ enableProfileSave: false }); const client = MatrixClientPeg.get(); - const newState: IState = {}; + const newState: Partial = {}; const displayName = this.state.displayName.trim(); try { @@ -114,7 +114,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { ); const { content_uri: uri } = await client.uploadContent(this.state.avatarFile); await client.setAvatarUrl(uri); - newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96); + newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96) ?? undefined; newState.originalAvatarUrl = newState.avatarUrl; newState.avatarFile = null; } else if (this.state.originalAvatarUrl !== this.state.avatarUrl) { @@ -128,7 +128,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { }); } - this.setState(newState); + this.setState(newState); }; private onDisplayNameChanged = (e: React.ChangeEvent): void => { @@ -141,7 +141,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { private onAvatarChanged = (e: React.ChangeEvent): void => { if (!e.target.files || !e.target.files.length) { this.setState({ - avatarUrl: this.state.originalAvatarUrl, + avatarUrl: this.state.originalAvatarUrl ?? undefined, avatarFile: null, enableProfileSave: false, }); @@ -152,7 +152,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, avatarFile: file, enableProfileSave: true, }); @@ -162,7 +162,7 @@ export default class ProfileSettings extends React.Component<{}, IState> { public render(): React.ReactNode { const hostingSignupLink = getHostingLink("user-settings"); - let hostingSignup = null; + let hostingSignup: JSX.Element | undefined; if (hostingSignupLink) { hostingSignup = ( diff --git a/src/components/views/settings/SetIntegrationManager.tsx b/src/components/views/settings/SetIntegrationManager.tsx index 4013ed4c40..0290c4b7fe 100644 --- a/src/components/views/settings/SetIntegrationManager.tsx +++ b/src/components/views/settings/SetIntegrationManager.tsx @@ -27,7 +27,7 @@ import ToggleSwitch from "../elements/ToggleSwitch"; interface IProps {} interface IState { - currentManager: IntegrationManagerInstance; + currentManager: IntegrationManagerInstance | null; provisioningEnabled: boolean; } diff --git a/src/components/views/settings/ThemeChoicePanel.tsx b/src/components/views/settings/ThemeChoicePanel.tsx index afbe555137..df769a9456 100644 --- a/src/components/views/settings/ThemeChoicePanel.tsx +++ b/src/components/views/settings/ThemeChoicePanel.tsx @@ -162,7 +162,7 @@ export default class ThemeChoicePanel extends React.Component { this.setState({ customThemeUrl: e.target.value }); }; - private renderHighContrastCheckbox(): React.ReactElement { + private renderHighContrastCheckbox(): React.ReactElement | undefined { if ( !this.state.useSystemTheme && (findHighContrastTheme(this.state.theme) || isHighContrastTheme(this.state.theme)) @@ -181,7 +181,7 @@ export default class ThemeChoicePanel extends React.Component { } private highContrastThemeChanged(checked: boolean): void { - let newTheme: string; + let newTheme: string | undefined; if (checked) { newTheme = findHighContrastTheme(this.state.theme); } else { @@ -194,7 +194,7 @@ export default class ThemeChoicePanel extends React.Component { public render(): React.ReactElement { const themeWatcher = new ThemeWatcher(); - let systemThemeSection: JSX.Element; + let systemThemeSection: JSX.Element | undefined; if (themeWatcher.isSystemThemeSupported()) { systemThemeSection = (
@@ -208,9 +208,9 @@ export default class ThemeChoicePanel extends React.Component { ); } - let customThemeForm: JSX.Element; + let customThemeForm: JSX.Element | undefined; if (SettingsStore.getValue("feature_custom_themes")) { - let messageElement = null; + let messageElement: JSX.Element | undefined; if (this.state.customThemeMessage.text) { if (this.state.customThemeMessage.isError) { messageElement =
{this.state.customThemeMessage.text}
; @@ -268,7 +268,7 @@ export default class ThemeChoicePanel extends React.Component { ); } - public apparentSelectedThemeId(): string { + public apparentSelectedThemeId(): string | undefined { if (this.state.useSystemTheme) { return undefined; } diff --git a/src/components/views/settings/UpdateCheckButton.tsx b/src/components/views/settings/UpdateCheckButton.tsx index a39ba102e5..da88106749 100644 --- a/src/components/views/settings/UpdateCheckButton.tsx +++ b/src/components/views/settings/UpdateCheckButton.tsx @@ -27,7 +27,7 @@ import AccessibleButton from "../../../components/views/elements/AccessibleButto import { CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload"; function installUpdate(): void { - PlatformPeg.get().installUpdate(); + PlatformPeg.get()?.installUpdate(); } function getStatusText(status: UpdateCheckStatus, errorDetail?: string): ReactNode { @@ -58,11 +58,11 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string): ReactNo const doneStatuses = [UpdateCheckStatus.Ready, UpdateCheckStatus.Error, UpdateCheckStatus.NotAvailable]; const UpdateCheckButton: React.FC = () => { - const [state, setState] = useState(null); + const [state, setState] = useState(null); const onCheckForUpdateClick = (): void => { setState(null); - PlatformPeg.get().startUpdateCheck(); + PlatformPeg.get()?.startUpdateCheck(); }; useDispatcher(dis, ({ action, ...params }) => { @@ -71,9 +71,9 @@ const UpdateCheckButton: React.FC = () => { } }); - const busy = state && !doneStatuses.includes(state.status); + const busy = !!state && !doneStatuses.includes(state.status); - let suffix; + let suffix: JSX.Element | undefined; if (state) { suffix = ( diff --git a/src/components/views/settings/devices/DeviceDetails.tsx b/src/components/views/settings/devices/DeviceDetails.tsx index 1793fc8b5e..56df38c6cf 100644 --- a/src/components/views/settings/devices/DeviceDetails.tsx +++ b/src/components/views/settings/devices/DeviceDetails.tsx @@ -31,14 +31,14 @@ import { ExtendedDevice } from "./types"; interface Props { device: ExtendedDevice; - pusher?: IPusher | undefined; - localNotificationSettings?: LocalNotificationSettings | undefined; + pusher?: IPusher; + localNotificationSettings?: LocalNotificationSettings; isSigningOut: boolean; onVerifyDevice?: () => void; onSignOutDevice: () => void; saveDeviceName: (deviceName: string) => Promise; - setPushNotifications?: (deviceId: string, enabled: boolean) => Promise | undefined; - supportsMSC3881?: boolean | undefined; + setPushNotifications?: (deviceId: string, enabled: boolean) => Promise; + supportsMSC3881?: boolean; className?: string; isCurrentDevice?: boolean; } diff --git a/src/components/views/settings/devices/LoginWithQRSection.tsx b/src/components/views/settings/devices/LoginWithQRSection.tsx index 0faa3913c8..356084dec7 100644 --- a/src/components/views/settings/devices/LoginWithQRSection.tsx +++ b/src/components/views/settings/devices/LoginWithQRSection.tsx @@ -23,7 +23,7 @@ import SettingsSubsection from "../shared/SettingsSubsection"; interface IProps { onShowQr: () => void; - versions: IServerVersions; + versions?: IServerVersions; } export default class LoginWithQRSection extends React.Component { diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx index 943634f873..34153819fc 100644 --- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx @@ -40,6 +40,7 @@ interface IRecommendedVersion { } interface IState { + // This is eventually set to the value of room.getRecommendedVersion() upgradeRecommendation?: IRecommendedVersion; oldRoomId?: string; oldEventId?: string; @@ -52,14 +53,11 @@ export default class AdvancedRoomSettingsTab extends React.Component { + room?.getRecommendedVersion().then((v) => { const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, ""); const additionalStateChanges: Partial = {}; @@ -99,11 +97,10 @@ export default class AdvancedRoomSettingsTab extends React.Component{_t("This room is not accessible by remote Matrix servers")}
; } @@ -132,13 +129,13 @@ export default class AdvancedRoomSettingsTab extends React.Component{_t("Room version")}
{_t("Room version:")}  - {room.getVersion()} + {room?.getVersion()}
{oldRoomLink} {roomUpgradeButton} diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx index d33ef0e880..4eb676f411 100644 --- a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -34,17 +34,16 @@ interface IProps { } export default class BridgeSettingsTab extends React.Component { - private renderBridgeCard(event: MatrixEvent, room: Room): JSX.Element { + private renderBridgeCard(event: MatrixEvent, room: Room | null): ReactNode { const content = event.getContent(); - if (!content || !content.channel || !content.protocol) { - return null; - } + if (!room || !content?.channel || !content.protocol) return null; return ; } public static getBridgeStateEvents(roomId: string): MatrixEvent[] { const client = MatrixClientPeg.get(); - const roomState = client.getRoom(roomId).currentState; + const roomState = client.getRoom(roomId)?.currentState; + if (!roomState) return []; return BRIDGE_EVENT_TYPES.map((typeName) => roomState.getStateEvents(typeName)).flat(1); } diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx index e154687759..9c47a7432e 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx @@ -61,15 +61,14 @@ export default class GeneralRoomSettingsTab extends React.Component - ) : null; + const urlPreviewSettings = + room && SettingsStore.getValue(UIFeature.URLPreviews) ? : null; let leaveSection; - if (room.getMyMembership() === "join") { + if (room?.getMyMembership() === "join") { leaveSection = ( <> {_t("Leave room")} diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx index e52967d086..b9c481125a 100644 --- a/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.tsx @@ -40,7 +40,7 @@ interface IProps { interface IState { currentSound: string; - uploadedFile: File; + uploadedFile: File | null; } export default class NotificationsSettingsTab extends React.Component { @@ -71,7 +71,7 @@ export default class NotificationsSettingsTab extends React.Component): void => { @@ -156,7 +156,7 @@ export default class NotificationsSettingsTab extends React.Component diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 6705b5ee2a..db39176257 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -239,13 +239,13 @@ export default class RolesRoomSettingsTab extends React.Component { public render(): React.ReactNode { const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); - const isSpaceRoom = room.isSpaceRoom(); + const isSpaceRoom = room?.isSpaceRoom(); - const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, ""); + const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, ""); const plContent = plEvent ? plEvent.getContent() || {} : {}; - const canChangeLevels = room.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client); + const canChangeLevels = room?.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client); - const plEventsToLabels: Record = { + const plEventsToLabels: Record = { // These will be translated for us later. [EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"), [EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"), @@ -322,7 +322,7 @@ export default class RolesRoomSettingsTab extends React.Component { powerLevelDescriptors.users_default.defaultValue, ); - let currentUserLevel = userLevels[client.getUserId()]; + let currentUserLevel = userLevels[client.getUserId()!]; if (currentUserLevel === undefined) { currentUserLevel = defaultUserLevel; } @@ -391,16 +391,16 @@ export default class RolesRoomSettingsTab extends React.Component { } } - const banned = room.getMembersWithMembership("ban"); - let bannedUsersSection; - if (banned.length) { + const banned = room?.getMembersWithMembership("ban"); + let bannedUsersSection: JSX.Element | undefined; + if (banned?.length) { const canBanUsers = currentUserLevel >= banLevel; bannedUsersSection = (
    {banned.map((member) => { - const banEvent = member.events.member.getContent(); - const sender = room.getMember(member.events.member.getSender()); + const banEvent = member.events.member?.getContent(); + const sender = room?.getMember(member.events.member.getSender()); let bannedBy = member.events.member.getSender(); // start by falling back to mxid if (sender) bannedBy = sender.name; return ( diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 656d171638..4c8cc9d967 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; @@ -61,16 +61,16 @@ export default class SecurityRoomSettingsTab extends React.Component) { super(props, context); - const state = context.getRoom(this.props.roomId).currentState; + const state = context.getRoom(this.props.roomId)?.currentState; this.state = { guestAccess: this.pullContentPropertyFromEvent( - state.getStateEvents(EventType.RoomGuestAccess, ""), + state?.getStateEvents(EventType.RoomGuestAccess, ""), "guest_access", GuestAccess.Forbidden, ), history: this.pullContentPropertyFromEvent( - state.getStateEvents(EventType.RoomHistoryVisibility, ""), + state?.getStateEvents(EventType.RoomHistoryVisibility, ""), "history_visibility", HistoryVisibility.Shared, ), @@ -85,7 +85,7 @@ export default class SecurityRoomSettingsTab extends React.Component this.setState({ hasAliases })); } - private pullContentPropertyFromEvent(event: MatrixEvent, key: string, defaultValue: T): T { + private pullContentPropertyFromEvent(event: MatrixEvent | null | undefined, key: string, defaultValue: T): T { return event?.getContent()[key] || defaultValue; } @@ -117,7 +117,7 @@ export default class SecurityRoomSettingsTab extends React.Component {sub} }, )}{" "}

    @@ -126,7 +126,7 @@ export default class SecurityRoomSettingsTab extends React.Componentnew encrypted room for " + "the conversation you plan to have.", - null, + undefined, { a: (sub) => ( { - this.context.getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked); + this.context.getRoom(this.props.roomId)?.setBlacklistUnverifiedDevices(checked); }; private async hasAliases(): Promise { @@ -250,8 +250,8 @@ export default class SecurityRoomSettingsTab extends React.Component @@ -297,7 +297,7 @@ export default class SecurityRoomSettingsTab extends React.Component {sub} }, )}{" "}

    @@ -306,7 +306,7 @@ export default class SecurityRoomSettingsTab extends React.Componentnew public room for the conversation " + "you plan to have.", - null, + undefined, { a: (sub) => ( @@ -418,10 +418,10 @@ export default class SecurityRoomSettingsTab extends React.Component { @@ -58,16 +58,13 @@ export default class AppearanceUserSettingsTab extends React.Component { // Fetch the current user profile for the message preview const client = MatrixClientPeg.get(); - const userId = client.getUserId(); + const userId = client.getUserId()!; const profileInfo = await client.getProfileInfo(userId); if (this.unmounted) return; @@ -86,7 +83,7 @@ export default class AppearanceUserSettingsTab extends React.Component ) => { this.setState({ systemFont: value.target.value, diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 83b81aa3fd..0134062ca6 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { logger } from "matrix-js-sdk/src/logger"; import AccessibleButton from "../../../elements/AccessibleButton"; @@ -37,7 +37,7 @@ interface IProps { } interface IState { - appVersion: string; + appVersion: string | null; canUpdate: boolean; } @@ -53,13 +53,13 @@ export default class HelpUserSettingsTab extends React.Component public componentDidMount(): void { PlatformPeg.get() - .getAppVersion() + ?.getAppVersion() .then((ver) => this.setState({ appVersion: ver })) .catch((e) => { logger.error("Error getting vector version: ", e); }); PlatformPeg.get() - .canSelfUpdate() + ?.canSelfUpdate() .then((v) => this.setState({ canUpdate: v })) .catch((e) => { logger.error("Error getting self updatability: ", e); @@ -90,7 +90,7 @@ export default class HelpUserSettingsTab extends React.Component MatrixClientPeg.get() .store.deleteAllData() .then(() => { - PlatformPeg.get().reload(); + PlatformPeg.get()?.reload(); }); }; @@ -106,11 +106,11 @@ export default class HelpUserSettingsTab extends React.Component }); }; - private renderLegal(): JSX.Element { + private renderLegal(): ReactNode { const tocLinks = SdkConfig.get().terms_and_conditions_links; if (!tocLinks) return null; - const legalLinks = []; + const legalLinks: JSX.Element[] = []; for (const tocEntry of tocLinks) { legalLinks.push(
    @@ -248,7 +248,7 @@ export default class HelpUserSettingsTab extends React.Component ); } - let updateButton = null; + let updateButton: JSX.Element | undefined; if (this.state.canUpdate) { updateButton = ; } diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx index 98d7c41f0f..6f9f538237 100644 --- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx @@ -105,7 +105,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> this.setState({ busy: true }); try { const list = Mjolnir.sharedInstance().getPersonalList(); - await list.unbanEntity(rule.kind, rule.entity); + await list!.unbanEntity(rule.kind, rule.entity); } catch (e) { logger.error(e); @@ -142,7 +142,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> const renderRules = (rules: ListRule[]): JSX.Element => { if (rules.length === 0) return {_t("None")}; - const tiles = []; + const tiles: JSX.Element[] = []; for (const rule of rules) { tiles.push(
  • @@ -173,7 +173,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> const rules = list ? [...list.userRules, ...list.serverRules] : []; if (!list || rules.length <= 0) return {_t("You have not ignored anyone.")}; - const tiles = []; + const tiles: JSX.Element[] = []; for (const rule of rules) { tiles.push(
  • @@ -205,7 +205,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> }); if (!lists || lists.length <= 0) return {_t("You are not subscribed to any lists")}; - const tiles = []; + const tiles: JSX.Element[] = []; for (const list of lists) { const room = MatrixClientPeg.get().getRoom(list.roomId); const name = room ? ( diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index 2d2a086c61..9697af802b 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { sleep } from "matrix-js-sdk/src/utils"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { logger } from "matrix-js-sdk/src/logger"; @@ -169,7 +169,7 @@ export default class SecurityUserSettingsTab extends React.Component { - return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite"); + return r.hasMembershipState(MatrixClientPeg.get().getUserId()!, "invite"); }); }; @@ -247,7 +247,7 @@ export default class SecurityUserSettingsTab extends React.Component { }); } - private renderDropdown(kind: MediaDeviceKindEnum, label: string): JSX.Element { + private renderDropdown(kind: MediaDeviceKindEnum, label: string): ReactNode { const devices = this.state.mediaDevices[kind].slice(0); if (devices.length === 0) return null; @@ -121,11 +121,11 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> { ); } - public render(): React.ReactNode { - let requestButton = null; - let speakerDropdown = null; - let microphoneDropdown = null; - let webcamDropdown = null; + public render(): ReactNode { + let requestButton: ReactNode | undefined; + let speakerDropdown: ReactNode | undefined; + let microphoneDropdown: ReactNode | undefined; + let webcamDropdown: ReactNode | undefined; if (!this.state.mediaDevices) { requestButton = (
    diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx index 4fc35da5a7..1adcd41960 100644 --- a/src/components/views/spaces/QuickSettingsButton.tsx +++ b/src/components/views/spaces/QuickSettingsButton.tsx @@ -46,8 +46,8 @@ const QuickSettingsButton: React.FC<{ const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = useSettingValue>("Spaces.enabledMetaSpaces"); - let contextMenu: JSX.Element; - if (menuDisplayed) { + let contextMenu: JSX.Element | undefined; + if (menuDisplayed && handle.current) { contextMenu = ( = ({ matrixClient: cli, space, o const [busy, setBusy] = useState(false); const [error, setError] = useState(""); - const userId = cli.getUserId(); + const userId = cli.getUserId()!; - const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar + const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId); const avatarChanged = newAvatar !== null; @@ -48,8 +48,8 @@ const SpaceSettingsGeneralTab: React.FC = ({ matrixClient: cli, space, o const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId); const nameChanged = name !== space.name; - const currentTopic = getTopic(space)?.text; - const [topic, setTopic] = useState(currentTopic); + const currentTopic = getTopic(space)?.text ?? ""; + const [topic, setTopic] = useState(currentTopic); const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId); const topicChanged = topic !== currentTopic; @@ -104,7 +104,7 @@ const SpaceSettingsGeneralTab: React.FC = ({ matrixClient: cli, space, o
    = ({ matrixClient: cli, space false, ); - const userId = cli.getUserId(); + const userId = cli.getUserId()!; const joinRule = useRoomState(space, (state) => state.getJoinRule()); const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho( @@ -120,7 +120,7 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space ); } - let addressesSection; + let addressesSection: JSX.Element | undefined; if (space.getJoinRule() === JoinRule.Public) { addressesSection = ( <> @@ -129,7 +129,7 @@ const SpaceSettingsVisibilityTab: React.FC = ({ matrixClient: cli, space roomId={space.roomId} canSetCanonicalAlias={canSetCanonical} canSetAliases={true} - canonicalAliasEvent={canonicalAliasEv} + canonicalAliasEvent={canonicalAliasEv ?? undefined} hidePublishSetting={!serverSupportsExploringSpaces} /> diff --git a/src/components/views/terms/InlineTermsAgreement.tsx b/src/components/views/terms/InlineTermsAgreement.tsx index 26880ce1ce..17357ee0b4 100644 --- a/src/components/views/terms/InlineTermsAgreement.tsx +++ b/src/components/views/terms/InlineTermsAgreement.tsx @@ -83,7 +83,7 @@ export default class InlineTermsAgreement extends React.Component("FTUE.userOnboardingButton"); if (!visible || minimized || !showUserOnboardingPage(useCase)) { - return null; + return <>; } return ; diff --git a/src/components/views/user-onboarding/UserOnboardingFeedback.tsx b/src/components/views/user-onboarding/UserOnboardingFeedback.tsx index d5e6fd9181..ee827e2603 100644 --- a/src/components/views/user-onboarding/UserOnboardingFeedback.tsx +++ b/src/components/views/user-onboarding/UserOnboardingFeedback.tsx @@ -26,7 +26,7 @@ import { shouldShowFeedback } from "../../../utils/Feedback"; export function UserOnboardingFeedback(): JSX.Element { if (!shouldShowFeedback()) { - return null; + return <>; } return ( diff --git a/src/components/views/user-onboarding/UserOnboardingTask.tsx b/src/components/views/user-onboarding/UserOnboardingTask.tsx index 7b7b8e5346..1c1e3b2544 100644 --- a/src/components/views/user-onboarding/UserOnboardingTask.tsx +++ b/src/components/views/user-onboarding/UserOnboardingTask.tsx @@ -57,7 +57,7 @@ export function UserOnboardingTask({ task, completed = false }: Props): JSX.Elem kind="primary_outline" href={task.action.href} target="_blank" - onClick={task.action.onClick} + onClick={task.action.onClick ?? null} > {task.action.label} diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index a8437129a7..e1dac478b3 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -150,7 +150,7 @@ export const Lobby: FC = ({ room, joinCallButtonDisabledTooltip, con }, [videoMuted, setVideoMuted]); const [videoStream, audioInputs, videoInputs] = useAsyncMemo( - async (): Promise<[MediaStream, MediaDeviceInfo[], MediaDeviceInfo[]]> => { + async (): Promise<[MediaStream | null, MediaDeviceInfo[], MediaDeviceInfo[]]> => { let devices = await MediaDeviceHandler.getDevices(); // We get the preview stream before requesting devices: this is because @@ -335,7 +335,7 @@ const StartCallView: FC = ({ room, resizing, call, setStarti = ({ room, resizing, call }) => { { } }; - private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall): void { + private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall | null): void { if (oldCall === newCall) return; if (oldCall) { @@ -430,7 +430,7 @@ export default class LegacyCallView extends React.Component { const { pipMode, call, onResize } = this.props; const { isLocalOnHold, isRemoteOnHold, sidebarShown, primaryFeed, secondaryFeed, sidebarFeeds } = this.state; - const callRoom = MatrixClientPeg.get().getRoom(call.roomId); + const callRoom = MatrixClientPeg.get().getRoom(call.roomId) ?? undefined; const avatarSize = pipMode ? 76 : 160; const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(call.callId); const isOnHold = isLocalOnHold || isRemoteOnHold; diff --git a/src/editor/dom.ts b/src/editor/dom.ts index a48cace4ef..19bccdbef7 100644 --- a/src/editor/dom.ts +++ b/src/editor/dom.ts @@ -190,6 +190,8 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node): { // get text value of text node, ignoring ZWS if it's a caret node function getTextNodeValue(node: Node): string { const nodeText = node.nodeValue; + if (!nodeText) return ""; + // filter out ZWS for caret nodes if (isCaretNode(node.parentElement)) { // typed in the caret node, so there is now something more in it than the ZWS @@ -200,9 +202,9 @@ function getTextNodeValue(node: Node): string { // only contains ZWS, which is ignored, so return empty string return ""; } - } else { - return nodeText; } + + return nodeText; } export function getRangeForSelection(editor: HTMLDivElement, model: EditorModel, selection: Selection): Range { diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 74203b586e..d5a5c39988 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -424,7 +424,7 @@ class RoomPillPart extends PillPart { let initialLetter = ""; let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop"); if (!avatarUrl) { - initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId); + initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? ""; avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId); } this.setAvatarVars(node, avatarUrl, initialLetter); @@ -478,7 +478,7 @@ class UserPillPart extends PillPart { const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop"); let initialLetter = ""; if (avatarUrl === defaultAvatarUrl) { - initialLetter = Avatar.getInitialLetter(name); + initialLetter = Avatar.getInitialLetter(name) ?? ""; } this.setAvatarVars(node, avatarUrl, initialLetter); } diff --git a/src/hooks/useAsyncMemo.ts b/src/hooks/useAsyncMemo.ts index c1d4a55cbb..69783c88e3 100644 --- a/src/hooks/useAsyncMemo.ts +++ b/src/hooks/useAsyncMemo.ts @@ -18,7 +18,9 @@ import { useState, useEffect, DependencyList } from "react"; type Fn = () => Promise; -export const useAsyncMemo = (fn: Fn, deps: DependencyList, initialValue?: T): T | undefined => { +export function useAsyncMemo(fn: Fn, deps: DependencyList, initialValue: T): T; +export function useAsyncMemo(fn: Fn, deps: DependencyList, initialValue?: T): T | undefined; +export function useAsyncMemo(fn: Fn, deps: DependencyList, initialValue?: T): T | undefined { const [value, setValue] = useState(initialValue); useEffect(() => { let discard = false; @@ -32,4 +34,4 @@ export const useAsyncMemo = (fn: Fn, deps: DependencyList, initialValue?: }; }, deps); // eslint-disable-line react-hooks/exhaustive-deps return value; -}; +} diff --git a/src/hooks/useUserOnboardingTasks.ts b/src/hooks/useUserOnboardingTasks.ts index e7afdacb56..beff1e111c 100644 --- a/src/hooks/useUserOnboardingTasks.ts +++ b/src/hooks/useUserOnboardingTasks.ts @@ -37,7 +37,7 @@ interface UserOnboardingTask { relevant?: UseCase[]; action?: { label: string; - onClick?: (ev?: ButtonEvent) => void; + onClick?: (ev: ButtonEvent) => void; href?: string; hideOnComplete?: boolean; }; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 2493a26966..623737175d 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -87,7 +87,7 @@ export function _td(s: string): string { * for this reason, force fallbackLocale === locale in the first call to translate * and fallback 'manually' so we can mark fallback strings appropriately * */ -const translateWithFallback = (text: string, options?: IVariables): { translated?: string; isFallback?: boolean } => { +const translateWithFallback = (text: string, options?: IVariables): { translated: string; isFallback?: boolean } => { const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() }); if (!translated || translated.startsWith("missing translation:")) { const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE }); @@ -117,7 +117,7 @@ const translateWithFallback = (text: string, options?: IVariables): { translated // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Takes the same arguments as counterpart.translate() -function safeCounterpartTranslate(text: string, variables?: IVariables): { translated?: string; isFallback?: boolean } { +function safeCounterpartTranslate(text: string, variables?: IVariables): { translated: string; isFallback?: boolean } { // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components // However, still pass the variables to counterpart so that it can choose the correct plural if count is given // It is enough to pass the count variable, but in the future counterpart might make use of other information too @@ -251,7 +251,7 @@ export function sanitizeForTranslation(text: string): string { * @return a React component if any non-strings were used in substitutions, otherwise a string */ export function substitute(text: string, variables?: IVariables): string; -export function substitute(text: string, variables: IVariables, tags: Tags): string; +export function substitute(text: string, variables: IVariables | undefined, tags: Tags | undefined): string; export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { let result: React.ReactNode | string = text; @@ -619,7 +619,7 @@ let cachedCustomTranslationsExpire = 0; // zero to trigger expiration right away // This awkward class exists so the test runner can get at the function. It is // not intended for practical or realistic usage. export class CustomTranslationOptions { - public static lookupFn: (url: string) => ICustomTranslations; + public static lookupFn?: (url: string) => ICustomTranslations; private constructor() { // static access for tests only diff --git a/src/stores/VoiceRecordingStore.ts b/src/stores/VoiceRecordingStore.ts index ed2b480255..e755596cc4 100644 --- a/src/stores/VoiceRecordingStore.ts +++ b/src/stores/VoiceRecordingStore.ts @@ -74,7 +74,7 @@ export class VoiceRecordingStore extends AsyncStoreWithClient { * @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to start recording in. * @returns {VoiceRecording} The recording. */ - public startRecording(voiceRecordingId: string): VoiceMessageRecording { + public startRecording(voiceRecordingId?: string): VoiceMessageRecording { if (!this.matrixClient) throw new Error("Cannot start a recording without a MatrixClient"); if (!voiceRecordingId) throw new Error("Recording must be associated with a room"); if (this.state[voiceRecordingId]) throw new Error("A recording is already in progress"); diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index be842049b2..32f6608075 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -218,15 +218,15 @@ export class StopGapWidgetDriver extends WidgetDriver { public async sendEvent( eventType: string, content: IContent, - stateKey: string = null, - targetRoomId: string = null, + stateKey?: string, + targetRoomId?: string, ): Promise { const client = MatrixClientPeg.get(); const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId(); if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); - let r: { event_id: string } = null; // eslint-disable-line camelcase + let r: { event_id: string } | null = null; // eslint-disable-line camelcase if (stateKey !== null) { // state event r = await client.sendStateEvent(roomId, eventType, content, stateKey); @@ -242,7 +242,7 @@ export class StopGapWidgetDriver extends WidgetDriver { if (containsEmoji(content, effect.emojis)) { // For initial threads launch, chat effects are disabled // see #19731 - const isNotThread = content["m.relates_to"].rel_type !== THREAD_RELATION_TYPE.name; + const isNotThread = content["m.relates_to"]?.rel_type !== THREAD_RELATION_TYPE.name; if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) { dis.dispatch({ action: `effects.${effect.command}` }); } @@ -300,7 +300,7 @@ export class StopGapWidgetDriver extends WidgetDriver { } } - private pickRooms(roomIds: (string | Symbols.AnyRoom)[] = null): Room[] { + private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] { const client = MatrixClientPeg.get(); if (!client) throw new Error("Not attached to a client"); @@ -309,14 +309,14 @@ export class StopGapWidgetDriver extends WidgetDriver { ? client.getVisibleRooms() : roomIds.map((r) => client.getRoom(r)) : [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId())]; - return targetRooms.filter((r) => !!r); + return targetRooms.filter((r) => !!r) as Room[]; } public async readRoomEvents( eventType: string, msgtype: string | undefined, limitPerRoom: number, - roomIds: (string | Symbols.AnyRoom)[] = null, + roomIds?: (string | Symbols.AnyRoom)[], ): Promise { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary @@ -343,7 +343,7 @@ export class StopGapWidgetDriver extends WidgetDriver { eventType: string, stateKey: string | undefined, limitPerRoom: number, - roomIds: (string | Symbols.AnyRoom)[] = null, + roomIds?: (string | Symbols.AnyRoom)[], ): Promise { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary @@ -467,8 +467,8 @@ export class StopGapWidgetDriver extends WidgetDriver { return { chunk: events.map((e) => e.getEffectiveEvent() as IRoomEvent), - nextBatch, - prevBatch, + nextBatch: nextBatch ?? undefined, + prevBatch: prevBatch ?? undefined, }; } } diff --git a/src/utils/beacon/bounds.ts b/src/utils/beacon/bounds.ts index c7942953e4..ad1b515fb4 100644 --- a/src/utils/beacon/bounds.ts +++ b/src/utils/beacon/bounds.ts @@ -48,10 +48,12 @@ export const getBeaconBounds = (beacons: Beacon[]): Bounds | undefined => { const sortedByLat = [...coords].sort((left, right) => right.latitude - left.latitude); const sortedByLong = [...coords].sort((left, right) => right.longitude - left.longitude); + if (sortedByLat.length < 1 || sortedByLong.length < 1) return; + return { - north: sortedByLat[0].latitude, - south: sortedByLat[sortedByLat.length - 1].latitude, - east: sortedByLong[0].longitude, - west: sortedByLong[sortedByLong.length - 1].longitude, + north: sortedByLat[0]!.latitude, + south: sortedByLat[sortedByLat.length - 1]!.latitude, + east: sortedByLong[0]!.longitude, + west: sortedByLong[sortedByLong.length - 1]!.longitude, }; }; diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx index d39c2171b2..d0dffd399e 100644 --- a/src/widgets/CapabilityText.tsx +++ b/src/widgets/CapabilityText.tsx @@ -135,7 +135,7 @@ export class CapabilityText { // First see if we have a super simple line of text to provide back if (CapabilityText.simpleCaps[capability]) { const textForKind = CapabilityText.simpleCaps[capability]; - if (textForKind[kind]) return { primary: _t(textForKind[kind]) }; + if (textForKind[kind]) return { primary: _t(textForKind[kind]!) }; if (textForKind[GENERIC_WIDGET_KIND]) return { primary: _t(textForKind[GENERIC_WIDGET_KIND]) }; // ... we'll fall through to the generic capability processing at the end of this @@ -195,8 +195,8 @@ export class CapabilityText { : CapabilityText.nonStateSendRecvCaps; if (evSendRecv[eventCap.eventType]) { const textForKind = evSendRecv[eventCap.eventType]; - const textForDirection = textForKind[kind] || textForKind[GENERIC_WIDGET_KIND]; - if (textForDirection && textForDirection[eventCap.direction]) { + const textForDirection = textForKind?.[kind] || textForKind?.[GENERIC_WIDGET_KIND]; + if (textForDirection?.[eventCap.direction]) { return { primary: _t(textForDirection[eventCap.direction]), // no byline because we would have already represented the event properly diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index 5ab3e98a8c..c98f3a4b79 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -87,7 +87,7 @@ export class Jitsi { * @param {string} url The URL to parse. * @returns {JitsiWidgetData} The widget data if eligible, otherwise null. */ - public parsePreferredConferenceUrl(url: string): JitsiWidgetData { + public parsePreferredConferenceUrl(url: string): JitsiWidgetData | null { const parsed = new URL(url); if (parsed.hostname !== this.preferredDomain) return null; // invalid return { diff --git a/test/PosthogAnalytics-test.ts b/test/PosthogAnalytics-test.ts index 07f3f51dfb..5188bd9a1f 100644 --- a/test/PosthogAnalytics-test.ts +++ b/test/PosthogAnalytics-test.ts @@ -57,7 +57,7 @@ describe("PosthogAnalytics", () => { digest: async (_: AlgorithmIdentifier, encodedMessage: BufferSource) => { const message = new TextDecoder().decode(encodedMessage); const hexHash = shaHashes[message]; - const bytes = []; + const bytes: number[] = []; for (let c = 0; c < hexHash.length; c += 2) { bytes.push(parseInt(hexHash.slice(c, c + 2), 16)); } @@ -68,6 +68,7 @@ describe("PosthogAnalytics", () => { }); afterEach(() => { + // @ts-ignore window.crypto = null; SdkConfig.unset(); // we touch the config, so clean up }); @@ -116,7 +117,7 @@ describe("PosthogAnalytics", () => { foo: "bar", }); expect(mocked(fakePosthog).capture.mock.calls[0][0]).toBe("JestTestEvents"); - expect(mocked(fakePosthog).capture.mock.calls[0][1]["foo"]).toEqual("bar"); + expect(mocked(fakePosthog).capture.mock.calls[0][1]!["foo"]).toEqual("bar"); }); it("Should not track events if anonymous", async () => { @@ -209,7 +210,7 @@ describe("PosthogAnalytics", () => { analytics.trackEvent({ eventName: "JestTestEvents", }); - expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ + expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({ WebLayout: "IRC", }); }); @@ -226,7 +227,7 @@ describe("PosthogAnalytics", () => { analytics.trackEvent({ eventName: "JestTestEvents", }); - expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ + expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({ WebLayout: "Bubble", }); }); @@ -243,7 +244,7 @@ describe("PosthogAnalytics", () => { analytics.trackEvent({ eventName: "JestTestEvents", }); - expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ + expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({ WebLayout: "Group", }); }); @@ -261,7 +262,7 @@ describe("PosthogAnalytics", () => { analytics.trackEvent({ eventName: "JestTestEvents", }); - expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ + expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({ WebLayout: "Compact", }); }); diff --git a/test/ScalarAuthClient-test.ts b/test/ScalarAuthClient-test.ts index 00b1d0644b..9dad8e2459 100644 --- a/test/ScalarAuthClient-test.ts +++ b/test/ScalarAuthClient-test.ts @@ -53,7 +53,7 @@ describe("ScalarAuthClient", function () { client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject); sac.exchangeForScalarToken = jest.fn((arg) => { - if (arg === tokenObject) return Promise.resolve("wokentoken"); + return Promise.resolve(arg === tokenObject ? "wokentoken" : "othertoken"); }); await sac.connect(); diff --git a/test/SlashCommands-test.tsx b/test/SlashCommands-test.tsx index 4ed059e9f3..d3d76e350d 100644 --- a/test/SlashCommands-test.tsx +++ b/test/SlashCommands-test.tsx @@ -223,7 +223,7 @@ describe("SlashCommands", () => { const command = getCommand("/part #foo:bar"); expect(command.cmd).toBeDefined(); expect(command.args).toBeDefined(); - await command.cmd!.run("room-id", null, command.args); + await command.cmd!.run("room-id", null, command.args!); expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything()); }); }); @@ -232,7 +232,7 @@ describe("SlashCommands", () => { const command = findCommand(commandName)!; it("should return usage if no args", () => { - expect(command.run(roomId, null).error).toBe(command.getUsage()); + expect(command.run(roomId, null, undefined).error).toBe(command.getUsage()); }); it("should make things rainbowy", () => { diff --git a/test/SlidingSyncManager-test.ts b/test/SlidingSyncManager-test.ts index 1829fc2958..1918366383 100644 --- a/test/SlidingSyncManager-test.ts +++ b/test/SlidingSyncManager-test.ts @@ -58,7 +58,7 @@ describe("SlidingSyncManager", () => { event_id: "$abc123", sender: client.getUserId()!, content: { - creator: client.getUserId(), + creator: client.getUserId()!, }, }), ]); diff --git a/test/audio/VoiceMessageRecording-test.ts b/test/audio/VoiceMessageRecording-test.ts index e109c61ab9..94fca82bce 100644 --- a/test/audio/VoiceMessageRecording-test.ts +++ b/test/audio/VoiceMessageRecording-test.ts @@ -125,7 +125,7 @@ describe("VoiceMessageRecording", () => { const encryptedFile = {} as unknown as IEncryptedFile; beforeEach(() => { - voiceRecording.onDataAvailable(testBuf); + voiceRecording.onDataAvailable!(testBuf); }); it("contentLength should return the buffer length", () => { @@ -143,9 +143,9 @@ describe("VoiceMessageRecording", () => { }); describe("upload", () => { - let uploadFileClient: MatrixClient; - let uploadFileRoomId: string; - let uploadBlob: Blob; + let uploadFileClient: MatrixClient | null; + let uploadFileRoomId: string | null; + let uploadBlob: Blob | null; beforeEach(() => { uploadFileClient = null; @@ -182,8 +182,8 @@ describe("VoiceMessageRecording", () => { expect(mocked(uploadFile)).toHaveBeenCalled(); expect(uploadFileClient).toBe(client); expect(uploadFileRoomId).toBe(roomId); - expect(uploadBlob.type).toBe(contentType); - const blobArray = await uploadBlob.arrayBuffer(); + expect(uploadBlob?.type).toBe(contentType); + const blobArray = await uploadBlob!.arrayBuffer(); expect(new Uint8Array(blobArray)).toEqual(testBuf); }); diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index e9860f5bba..704dc5f66b 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -79,7 +79,7 @@ describe("RoomView", () => { stores.client = cli; stores.rightPanelStore.useUnitTestClient(cli); - jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(null); + jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined); }); afterEach(async () => { @@ -175,7 +175,7 @@ describe("RoomView", () => { instance = await getRoomViewInstance(); oldRoom = new Room("!old:example.com", cli, cli.getSafeUserId()); rooms.set(oldRoom.roomId, oldRoom); - jest.spyOn(room, "findPredecessor").mockReturnValue({ roomId: oldRoom.roomId, eventId: null }); + jest.spyOn(room, "findPredecessor").mockReturnValue({ roomId: oldRoom.roomId }); }); it("and it has 0 unreads, getHiddenHighlightCount should return 0", async () => { diff --git a/test/components/structures/auth/Login-test.tsx b/test/components/structures/auth/Login-test.tsx index 8814cd04af..bf9e8d567c 100644 --- a/test/components/structures/auth/Login-test.tsx +++ b/test/components/structures/auth/Login-test.tsx @@ -181,7 +181,7 @@ describe("Login", function () { const { container, rerender } = render(getRawComponent()); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); - fireEvent.click(container.querySelector(".mx_SSOButton")); + fireEvent.click(container.querySelector(".mx_SSOButton")!); expect(platform.startSingleSignOn.mock.calls[0][0].baseUrl).toBe("https://matrix.org"); fetchMock.get("https://server2/_matrix/client/versions", { @@ -190,7 +190,7 @@ describe("Login", function () { }); rerender(getRawComponent("https://server2")); - fireEvent.click(container.querySelector(".mx_SSOButton")); + fireEvent.click(container.querySelector(".mx_SSOButton")!); expect(platform.startSingleSignOn.mock.calls[1][0].baseUrl).toBe("https://server2"); }); diff --git a/test/components/structures/auth/Registration-test.tsx b/test/components/structures/auth/Registration-test.tsx index 85401566ba..16b64bc393 100644 --- a/test/components/structures/auth/Registration-test.tsx +++ b/test/components/structures/auth/Registration-test.tsx @@ -118,7 +118,7 @@ describe("Registration", function () { const { container, rerender } = render(getRawComponent()); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); - fireEvent.click(container.querySelector(".mx_SSOButton")); + fireEvent.click(container.querySelector(".mx_SSOButton")!); expect(registerRequest.mock.instances[0].baseUrl).toBe("https://matrix.org"); fetchMock.get("https://server2/_matrix/client/versions", { @@ -128,7 +128,7 @@ describe("Registration", function () { rerender(getRawComponent("https://server2")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); - fireEvent.click(container.querySelector(".mx_SSOButton")); + fireEvent.click(container.querySelector(".mx_SSOButton")!); expect(registerRequest.mock.instances[1].baseUrl).toBe("https://server2"); }); }); diff --git a/test/components/views/beacon/BeaconListItem-test.tsx b/test/components/views/beacon/BeaconListItem-test.tsx index b6fe38ea74..580c247b69 100644 --- a/test/components/views/beacon/BeaconListItem-test.tsx +++ b/test/components/views/beacon/BeaconListItem-test.tsx @@ -84,7 +84,7 @@ describe("", () => { const member = new RoomMember(roomId, aliceId); member.name = `Alice`; - const room = mockClient.getRoom(roomId); + const room = mockClient.getRoom(roomId)!; jest.spyOn(room, "getMember").mockReturnValue(member); return beacons; @@ -185,7 +185,7 @@ describe("", () => { const { container } = getComponent({ beacon, onClick }); // click the beacon name - fireEvent.click(container.querySelector(".mx_BeaconStatus_description")); + fireEvent.click(container.querySelector(".mx_BeaconStatus_description")!); expect(onClick).toHaveBeenCalled(); }); }); diff --git a/test/components/views/beacon/ShareLatestLocation-test.tsx b/test/components/views/beacon/ShareLatestLocation-test.tsx index 11538f5375..654b3dc73a 100644 --- a/test/components/views/beacon/ShareLatestLocation-test.tsx +++ b/test/components/views/beacon/ShareLatestLocation-test.tsx @@ -47,7 +47,7 @@ describe("", () => { const { container, asFragment } = getComponent(); expect(asFragment()).toMatchSnapshot(); - fireEvent.click(container.querySelector(".mx_CopyableText_copyButton")); + fireEvent.click(container.querySelector(".mx_CopyableText_copyButton")!); await flushPromises(); expect(copyPlaintext).toHaveBeenCalledWith("51,42"); diff --git a/test/components/views/context_menus/MessageContextMenu-test.tsx b/test/components/views/context_menus/MessageContextMenu-test.tsx index 91ee949879..7e0a532917 100644 --- a/test/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/components/views/context_menus/MessageContextMenu-test.tsx @@ -575,7 +575,7 @@ function createMenu( return mount( - + , ); } diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx index 3453d4e24a..4ce8337080 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.tsx @@ -32,7 +32,6 @@ describe("AccessSecretStorageDialog", () => { const defaultProps: ComponentProps = { onFinished: jest.fn(), checkPrivateKey: jest.fn(), - keyInfo: undefined, }; const renderComponent = (props = {}): void => { diff --git a/test/components/views/elements/EventListSummary-test.tsx b/test/components/views/elements/EventListSummary-test.tsx index 145d62a7e2..4ecb963ee0 100644 --- a/test/components/views/elements/EventListSummary-test.tsx +++ b/test/components/views/elements/EventListSummary-test.tsx @@ -66,11 +66,11 @@ describe("EventListSummary", function () { } const generateMembershipEvent = ( eventId: string, - { senderId, userId, membership, prevMembership }: MembershipEventParams, + { senderId, userId, membership, prevMembership }: MembershipEventParams & { userId: string }, ): MatrixEvent => { const member = new RoomMember(roomId, userId); // Use localpart as display name; - member.name = userId.match(/@([^:]*):/)[1]; + member.name = userId.match(/@([^:]*):/)![1]; jest.spyOn(member, "getAvatarUrl").mockReturnValue("avatar.jpeg"); jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png"); const e = mkMembership({ @@ -89,8 +89,8 @@ describe("EventListSummary", function () { }; // Generate mock MatrixEvents from the array of parameters - const generateEvents = (parameters: MembershipEventParams[]) => { - const res = []; + const generateEvents = (parameters: Array) => { + const res: MatrixEvent[] = []; for (let i = 0; i < parameters.length; i++) { res.push(generateMembershipEvent(`event${i}`, parameters[i])); } @@ -108,7 +108,9 @@ describe("EventListSummary", function () { events.forEach((e) => { e.userId = userId; }); - eventsForUsers = eventsForUsers.concat(generateEvents(events)); + eventsForUsers = eventsForUsers.concat( + generateEvents(events as Array), + ); } return eventsForUsers; }; diff --git a/test/components/views/elements/LearnMore-test.tsx b/test/components/views/elements/LearnMore-test.tsx index 98241b1ae1..b527713ec3 100644 --- a/test/components/views/elements/LearnMore-test.tsx +++ b/test/components/views/elements/LearnMore-test.tsx @@ -29,7 +29,10 @@ describe("", () => { }; const getComponent = (props = {}) => ; - const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined); + const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({ + finished: new Promise(() => {}), + close: jest.fn(), + }); beforeEach(() => { jest.clearAllMocks(); diff --git a/test/components/views/elements/TooltipTarget-test.tsx b/test/components/views/elements/TooltipTarget-test.tsx index 09438fc2e5..82b620c262 100644 --- a/test/components/views/elements/TooltipTarget-test.tsx +++ b/test/components/views/elements/TooltipTarget-test.tsx @@ -36,7 +36,7 @@ describe("", () => { // clean up renderer tooltips const wrapper = document.querySelector(".mx_Tooltip_wrapper"); while (wrapper?.firstChild) { - wrapper.removeChild(wrapper.lastChild); + wrapper.removeChild(wrapper.lastChild!); } }); diff --git a/test/components/views/messages/MKeyVerificationConclusion-test.tsx b/test/components/views/messages/MKeyVerificationConclusion-test.tsx index 03966d1673..c19166ef57 100644 --- a/test/components/views/messages/MKeyVerificationConclusion-test.tsx +++ b/test/components/views/messages/MKeyVerificationConclusion-test.tsx @@ -50,10 +50,10 @@ describe("MKeyVerificationConclusion", () => { }) => { class MockVerificationRequest extends EventEmitter { constructor( - public readonly pending: boolean, - public readonly cancelled: boolean, - public readonly done: boolean, - public readonly otherUserId: string, + public readonly pending?: boolean, + public readonly cancelled?: boolean, + public readonly done?: boolean, + public readonly otherUserId?: string, ) { super(); } diff --git a/test/components/views/messages/MLocationBody-test.tsx b/test/components/views/messages/MLocationBody-test.tsx index 3f748fbaf7..a3f707a734 100644 --- a/test/components/views/messages/MLocationBody-test.tsx +++ b/test/components/views/messages/MLocationBody-test.tsx @@ -140,7 +140,9 @@ describe("MLocationBody", () => { }); it("opens map dialog on click", () => { - const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined); + const modalSpy = jest + .spyOn(Modal, "createDialog") + .mockReturnValue({ finished: new Promise(() => {}), close: jest.fn() }); const component = getComponent(); act(() => { diff --git a/test/components/views/messages/MessageEvent-test.tsx b/test/components/views/messages/MessageEvent-test.tsx index 6ec3d490e3..e8640415de 100644 --- a/test/components/views/messages/MessageEvent-test.tsx +++ b/test/components/views/messages/MessageEvent-test.tsx @@ -63,7 +63,7 @@ describe("MessageEvent", () => { event = mkEvent({ event: true, type: VoiceBroadcastInfoEventType, - user: client.getUserId(), + user: client.getUserId()!, room: room.roomId, content: { state: VoiceBroadcastInfoState.Started, diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index cf4dd61ec8..f9c2650b55 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -108,7 +108,7 @@ describe("", () => { const room = pins.props().room; const pinListener = mocked(room.currentState).on.mock.calls.find( ([eventName, listener]) => eventName === RoomStateEvent.Events, - )[1]; + )![1]; await act(async () => { // Emit the update diff --git a/test/components/views/rooms/MemberList-test.tsx b/test/components/views/rooms/MemberList-test.tsx index da6f27e582..735425c738 100644 --- a/test/components/views/rooms/MemberList-test.tsx +++ b/test/components/views/rooms/MemberList-test.tsx @@ -37,7 +37,7 @@ function generateRoomId() { describe("MemberList", () => { function createRoom(opts = {}) { - const room = new Room(generateRoomId(), null, client.getUserId()); + const room = new Room(generateRoomId(), client, client.getUserId()!); if (opts) { Object.assign(room, opts); } diff --git a/test/components/views/rooms/MessageComposer-test.tsx b/test/components/views/rooms/MessageComposer-test.tsx index 3f445b244d..48feec7a8f 100644 --- a/test/components/views/rooms/MessageComposer-test.tsx +++ b/test/components/views/rooms/MessageComposer-test.tsx @@ -370,7 +370,7 @@ describe("MessageComposer", () => { replyToEvent = mkEvent({ event: true, type: EventType.RoomMessage, - user: cli.getUserId(), + user: cli.getUserId()!, content: {}, }); diff --git a/test/components/views/rooms/RoomHeader-test.tsx b/test/components/views/rooms/RoomHeader-test.tsx index 3aed3e6845..935745c761 100644 --- a/test/components/views/rooms/RoomHeader-test.tsx +++ b/test/components/views/rooms/RoomHeader-test.tsx @@ -377,7 +377,7 @@ describe("RoomHeader (React Testing Library)", () => { content, }); room.addLiveEvents([event]); - return { event_id: event.getId() }; + return { event_id: event.getId()! }; }); alice = mkRoomMember(room.roomId, "@alice:example.org"); diff --git a/test/components/views/settings/KeyboardShortcut-test.tsx b/test/components/views/settings/KeyboardShortcut-test.tsx index 2f9c94d752..614c2fd059 100644 --- a/test/components/views/settings/KeyboardShortcut-test.tsx +++ b/test/components/views/settings/KeyboardShortcut-test.tsx @@ -21,7 +21,7 @@ import { Key } from "../../../../src/Keyboard"; import { mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils/platform"; import { KeyboardKey, KeyboardShortcut } from "../../../../src/components/views/settings/KeyboardShortcut"; -const renderKeyboardShortcut = (Component: React.FunctionComponentFactory, props: Record) => { +const renderKeyboardShortcut = (Component: React.FunctionComponent, props: Record) => { return render().container; }; diff --git a/test/components/views/settings/devices/DeviceDetails-test.tsx b/test/components/views/settings/devices/DeviceDetails-test.tsx index a3b3c93e98..49173ffb5f 100644 --- a/test/components/views/settings/devices/DeviceDetails-test.tsx +++ b/test/components/views/settings/devices/DeviceDetails-test.tsx @@ -30,7 +30,6 @@ describe("", () => { }; const defaultProps: ComponentProps = { device: baseDevice, - pusher: null, isSigningOut: false, onSignOutDevice: jest.fn(), saveDeviceName: jest.fn(), diff --git a/test/components/views/settings/devices/SelectableDeviceTile-test.tsx b/test/components/views/settings/devices/SelectableDeviceTile-test.tsx index d9327d8ae4..b2e79fc70f 100644 --- a/test/components/views/settings/devices/SelectableDeviceTile-test.tsx +++ b/test/components/views/settings/devices/SelectableDeviceTile-test.tsx @@ -53,7 +53,7 @@ describe("", () => { const { container } = render(getComponent({ onSelect })); act(() => { - fireEvent.click(container.querySelector(`#device-tile-checkbox-${device.device_id}`)); + fireEvent.click(container.querySelector(`#device-tile-checkbox-${device.device_id}`)!); }); expect(onSelect).toHaveBeenCalled(); diff --git a/test/components/views/settings/devices/deleteDevices-test.tsx b/test/components/views/settings/devices/deleteDevices-test.tsx index 743e0a5d54..80e62f423d 100644 --- a/test/components/views/settings/devices/deleteDevices-test.tsx +++ b/test/components/views/settings/devices/deleteDevices-test.tsx @@ -92,7 +92,7 @@ describe("deleteDevices()", () => { // opened modal expect(modalSpy).toHaveBeenCalled(); - const [, { title, authData, aestheticsForStagePhases }] = modalSpy.mock.calls[0]; + const { title, authData, aestheticsForStagePhases } = modalSpy.mock.calls[0][1]!; // modal opened as expected expect(title).toEqual("Authentication"); diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index 6356f652b3..cf2edb97e2 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -21,7 +21,7 @@ import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { logger } from "matrix-js-sdk/src/logger"; import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning"; import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; -import { sleep } from "matrix-js-sdk/src/utils"; +import { defer, sleep } from "matrix-js-sdk/src/utils"; import { ClientEvent, IMyDevice, @@ -927,12 +927,9 @@ describe("", () => { // get a handle for resolving the delete call // because promise flushing after the confirm modal is resolving this too // and we want to test the loading state here - let resolveDeleteRequest: (v?: IAuthData) => void; + const resolveDeleteRequest = defer(); mockClient.deleteMultipleDevices.mockImplementation(() => { - const promise = new Promise((resolve) => { - resolveDeleteRequest = resolve; - }); - return promise; + return resolveDeleteRequest.promise; }); const { getByTestId } = render(getComponent()); @@ -972,7 +969,7 @@ describe("", () => { undefined, ); - resolveDeleteRequest?.(); + resolveDeleteRequest.resolve({}); }); it("signs out of all other devices from other sessions context menu", async () => { diff --git a/test/editor/deserialize-test.ts b/test/editor/deserialize-test.ts index bba9d6cd9f..bcc7de9a79 100644 --- a/test/editor/deserialize-test.ts +++ b/test/editor/deserialize-test.ts @@ -54,7 +54,7 @@ function textMessageReply(body: string, msgtype = "m.text") { function mergeAdjacentParts(parts: Part[]) { let prevPart: Part | undefined; for (let i = 0; i < parts.length; ++i) { - let part = parts[i]; + let part: Part | undefined = parts[i]; const isEmpty = !part.text.length; const isMerged = !isEmpty && prevPart && prevPart.merge?.(part); if (isEmpty || isMerged) { diff --git a/test/editor/roundtrip-test.ts b/test/editor/roundtrip-test.ts index b9a597b806..437c97e863 100644 --- a/test/editor/roundtrip-test.ts +++ b/test/editor/roundtrip-test.ts @@ -38,7 +38,7 @@ async function md2html(markdown: string): Promise { const pc = createPartCreator(); const oldModel = new EditorModel([], pc, () => {}); await oldModel.update(markdown, "insertText", new DocumentOffset(markdown.length, false)); - return htmlSerializeIfNeeded(oldModel, { forceHTML: true }); + return htmlSerializeIfNeeded(oldModel, { forceHTML: true })!; } function html2md(html: string): string { diff --git a/test/hooks/useProfileInfo-test.tsx b/test/hooks/useProfileInfo-test.tsx index f9202de449..2619b7354d 100644 --- a/test/hooks/useProfileInfo-test.tsx +++ b/test/hooks/useProfileInfo-test.tsx @@ -143,7 +143,7 @@ describe("useProfileInfo", () => { }); it("should be able to handle an empty result", async () => { - cli.getProfileInfo = () => null; + cli.getProfileInfo = () => null as unknown as Promise<{}>; const query = "@user:home.server"; const wrapper = mount( diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index e825e5b1a4..2f01f06636 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -107,7 +107,7 @@ const setUpClientRoomAndStores = (): { content, }); room.addLiveEvents([event]); - return { event_id: event.getId() }; + return { event_id: event.getId()! }; }); setupAsyncStoreWithClient(WidgetStore.instance, client); diff --git a/test/modules/AppModule-test.ts b/test/modules/AppModule-test.ts index 6fdf16d694..178487a91d 100644 --- a/test/modules/AppModule-test.ts +++ b/test/modules/AppModule-test.ts @@ -20,7 +20,7 @@ import { AppModule } from "../../src/modules/AppModule"; describe("AppModule", () => { describe("constructor", () => { it("should call the factory immediately", () => { - let module: MockModule; + let module: MockModule | undefined; const appModule = new AppModule((api) => { if (module) { throw new Error("State machine error: Factory called twice"); diff --git a/test/modules/MockModule.ts b/test/modules/MockModule.ts index b4b905fcbf..ab29b02508 100644 --- a/test/modules/MockModule.ts +++ b/test/modules/MockModule.ts @@ -30,7 +30,7 @@ export class MockModule extends RuntimeModule { } export function registerMockModule(): MockModule { - let module: MockModule; + let module: MockModule | undefined; ModuleRunner.instance.registerModule((api) => { if (module) { throw new Error("State machine error: ModuleRunner created the module twice"); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 6354b5bfa1..1b9ae8da60 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -800,7 +800,7 @@ describe("SpaceStore", () => { testUtils.mockStateEventImplementation([childEvent]), ); - client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, undefined); + client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, null); }; const addMember = (spaceId: string, user: RoomMember) => { @@ -964,7 +964,7 @@ describe("SpaceStore", () => { } }); - client.emit(RoomStateEvent.Members, event, null, null); + client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner); return deferred.resolve(true) as unknown as Promise; }); @@ -1279,7 +1279,7 @@ describe("SpaceStore", () => { user: dm1Partner.userId, room: space1, }); - client.emit(RoomStateEvent.Members, memberEvent, undefined, undefined); + client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner); jest.runOnlyPendingTimers(); expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy(); const dm1Room = mkRoom(dm1); diff --git a/test/stores/VoiceRecordingStore-test.ts b/test/stores/VoiceRecordingStore-test.ts index 22695081ad..66eeb81103 100644 --- a/test/stores/VoiceRecordingStore-test.ts +++ b/test/stores/VoiceRecordingStore-test.ts @@ -21,7 +21,7 @@ import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { flushPromises } from "../test-utils"; import { VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording"; -const stubClient = {} as undefined as MatrixClient; +const stubClient = {} as unknown as MatrixClient; jest.spyOn(MatrixClientPeg, "get").mockReturnValue(stubClient); describe("VoiceRecordingStore", () => { diff --git a/test/stores/room-list/RoomListStore-test.ts b/test/stores/room-list/RoomListStore-test.ts index e0ee766f91..5c6435780e 100644 --- a/test/stores/room-list/RoomListStore-test.ts +++ b/test/stores/room-list/RoomListStore-test.ts @@ -216,7 +216,7 @@ describe("RoomListStore", () => { // But when we update the feature flag featureFlagValue = true; - watchCallback( + watchCallback!( "feature_dynamic_room_predecessors", "", SettingLevel.DEFAULT, diff --git a/test/stores/room-list/algorithms/RecentAlgorithm-test.ts b/test/stores/room-list/algorithms/RecentAlgorithm-test.ts index 0375065e94..d86fe9e775 100644 --- a/test/stores/room-list/algorithms/RecentAlgorithm-test.ts +++ b/test/stores/room-list/algorithms/RecentAlgorithm-test.ts @@ -68,6 +68,7 @@ describe("RecentAlgorithm", () => { it("returns a fake ts for rooms without a timeline", () => { const room = mkRoom(cli, "!new:example.org"); + // @ts-ignore room.timeline = undefined; expect(algorithm.getLastTs(room, "@john:matrix.org")).toBe(Number.MAX_SAFE_INTEGER); }); diff --git a/test/utils/beacon/geolocation-test.ts b/test/utils/beacon/geolocation-test.ts index 688e92ba70..0f9aba483f 100644 --- a/test/utils/beacon/geolocation-test.ts +++ b/test/utils/beacon/geolocation-test.ts @@ -196,7 +196,7 @@ describe("geolocation utilities", () => { // suppress expected errors from test log jest.spyOn(logger, "error").mockImplementation(() => {}); geolocation.watchPosition.mockImplementation((_callback, error) => { - error(getMockGeolocationPositionError(1, "message")); + error!(getMockGeolocationPositionError(1, "message")); return -1; }); const positionHandler = jest.fn(); @@ -224,7 +224,7 @@ describe("geolocation utilities", () => { jest.spyOn(logger, "error").mockImplementation(() => {}); const timeoutError = getMockGeolocationPositionError(3, "message"); - geolocation.getCurrentPosition.mockImplementation((callback, error) => error(timeoutError)); + geolocation.getCurrentPosition.mockImplementation((callback, error) => error!(timeoutError)); await expect(() => getCurrentPosition()).rejects.toThrow(GeolocationError.Timeout); }); diff --git a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts index 3cf71a7a94..312ee7f785 100644 --- a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts @@ -135,7 +135,7 @@ describe("VoiceBroadcastRecording", () => { event_id: infoEvent.getId(), }, } as VoiceBroadcastInfoEventContent, - client.getUserId(), + client.getUserId()!, ); }); }; diff --git a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts index 2d1126603c..ff7a50aef3 100644 --- a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts +++ b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts @@ -49,14 +49,14 @@ describe("VoiceBroadcastRecordingsStore", () => { infoEvent = mkVoiceBroadcastInfoStateEvent( roomId, VoiceBroadcastInfoState.Started, - client.getUserId(), - client.getDeviceId(), + client.getUserId()!, + client.getDeviceId()!, ); otherInfoEvent = mkVoiceBroadcastInfoStateEvent( roomId, VoiceBroadcastInfoState.Started, - client.getUserId(), - client.getDeviceId(), + client.getUserId()!, + client.getDeviceId()!, ); recording = new VoiceBroadcastRecording(infoEvent, client); otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client); diff --git a/test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts b/test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts index b166bbb9bd..7c07554fd2 100644 --- a/test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts +++ b/test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts @@ -54,7 +54,7 @@ describe("VoiceBroadcastResumer", () => { event_id: startedInfoEvent.getId(), }, } as VoiceBroadcastInfoEventContent, - client.getUserId(), + client.getUserId()!, ); }); }; diff --git a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts index 5f2447d858..e417e7e0fd 100644 --- a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts @@ -161,7 +161,7 @@ describe("startNewVoiceBroadcastRecording", () => { device_id: client.getDeviceId(), state: VoiceBroadcastInfoState.Started, }, - client.getUserId(), + client.getUserId()!, ); expect(recording!.infoEvent).toBe(infoEvent); expect(recording!.start).toHaveBeenCalled();