diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 7676305c4f..7280b6bb3d 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -193,7 +193,7 @@ export default abstract class BasePlatform { public displayNotification( title: string, msg: string, - avatarUrl: string, + avatarUrl: string | null, room: Room, ev?: MatrixEvent, ): Notification { diff --git a/src/Modal.tsx b/src/Modal.tsx index 1b21f74b5e..3b21c47d3d 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -33,7 +33,7 @@ export interface IModal { beforeClosePromise?: Promise; closeReason?: string; onBeforeClose?(reason?: string): Promise; - onFinished(...args: T): void; + onFinished?(...args: T): void; close(...args: T): void; hidden?: boolean; } @@ -68,11 +68,11 @@ export class ModalManager extends TypedEventEmitter = null; + private priorityModal: IModal | null = null; // The modal to keep open underneath other modals if possible. Useful // for cases like Settings where the modal should remain open while the // user is prompted for more information/errors. - private staticModal: IModal = null; + private staticModal: IModal | null = null; // A list of the modals we have stacked up, with the most recent at [0] // Neither the static nor priority modal will be in this list. private modals: IModal[] = []; @@ -144,17 +144,14 @@ export class ModalManager extends TypedEventEmitter["close"]; onFinishedProm: IHandle["finished"]; } { - const modal: IModal = { - onFinished: props ? props.onFinished : null, - onBeforeClose: options.onBeforeClose, - beforeClosePromise: null, - closeReason: null, + const modal = { + onFinished: props?.onFinished, + onBeforeClose: options?.onBeforeClose, className, // these will be set below but we need an object reference to pass to getCloseFn before we can do that elem: null, - close: null, - }; + } as IModal; // never call this from onFinished() otherwise it will loop const [closeDialog, onFinishedProm] = this.getCloseFn(modal, props); @@ -173,7 +170,7 @@ export class ModalManager extends TypedEventEmitter( modal: IModal, - props: IProps, + props?: IProps, ): [IHandle["close"], IHandle["finished"]] { const deferred = defer(); return [ @@ -183,13 +180,13 @@ export class ModalManager extends TypedEventEmitter= 0) { this.modals.splice(i, 1); @@ -317,7 +314,7 @@ export class ModalManager extends TypedEventEmitter { diff --git a/src/Notifier.ts b/src/Notifier.ts index 0faa533341..4e34083f3f 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype This is useful when the content body contains fallback text that would explain that the client can't handle a particular type of tile. */ -const msgTypeHandlers: Record string> = { +const msgTypeHandlers: Record string | null> = { [MsgType.KeyVerificationRequest]: (event: MatrixEvent) => { const name = (event.sender || {}).name; return _t("%(name)s is requesting verification", { name }); @@ -156,7 +156,7 @@ class NotifierClass { msg = ""; } - let avatarUrl = null; + let avatarUrl: string | null = null; if (ev.sender && !SettingsStore.getValue("lowBandwidth")) { avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop"); } @@ -166,8 +166,8 @@ class NotifierClass { // if displayNotification returns non-null, the platform supports // clearing notifications later, so keep track of this. if (notif) { - if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = []; - this.notifsByRoom[ev.getRoomId()].push(notif); + if (this.notifsByRoom[ev.getRoomId()!] === undefined) this.notifsByRoom[ev.getRoomId()!] = []; + this.notifsByRoom[ev.getRoomId()!].push(notif); } } @@ -219,7 +219,7 @@ class NotifierClass { sound ? `audio[src='${sound.url}']` : "#messageAudio", ); let audioElement = selector; - if (!selector) { + if (!audioElement) { if (!sound) { logger.error("No audio element or sound to play for notification"); return; @@ -378,11 +378,11 @@ class NotifierClass { return global.localStorage.getItem("notifications_hidden") === "true"; } - return this.toolbarHidden; + return !!this.toolbarHidden; } // XXX: Exported for tests - public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => { + public onSyncStateChange = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => { if (state === SyncState.Syncing) { this.isSyncing = true; } else if (state === SyncState.Stopped || state === SyncState.Error) { @@ -411,7 +411,7 @@ class NotifierClass { // If it's an encrypted event and the type is still 'm.room.encrypted', // it hasn't yet been decrypted, so wait until it is. if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) { - this.pendingEncryptedEventIds.push(ev.getId()); + this.pendingEncryptedEventIds.push(ev.getId()!); // don't let the list fill up indefinitely while (this.pendingEncryptedEventIds.length > MAX_PENDING_ENCRYPTED) { this.pendingEncryptedEventIds.shift(); @@ -427,7 +427,7 @@ class NotifierClass { // in which case it might decrypt soon if the keys arrive if (ev.isDecryptionFailure()) return; - const idx = this.pendingEncryptedEventIds.indexOf(ev.getId()); + const idx = this.pendingEncryptedEventIds.indexOf(ev.getId()!); if (idx === -1) return; this.pendingEncryptedEventIds.splice(idx, 1); @@ -456,7 +456,7 @@ class NotifierClass { public evaluateEvent(ev: MatrixEvent): void { // Mute notifications for broadcast info events if (ev.getType() === VoiceBroadcastInfoEventType) return; - let roomId = ev.getRoomId(); + let roomId = ev.getRoomId()!; if (LegacyCallHandler.instance.getSupportsVirtualRooms()) { // Attempt to translate a virtual room to a native one const nativeRoomId = VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(roomId); @@ -492,7 +492,7 @@ class NotifierClass { this.displayPopupNotification(ev, room); } if (actions.tweaks.sound && this.isAudioEnabled()) { - PlatformPeg.get().loudNotification(ev, room); + PlatformPeg.get()?.loudNotification(ev, room); this.playAudioNotification(ev, room); } } @@ -504,7 +504,7 @@ class NotifierClass { private performCustomEventHandling(ev: MatrixEvent): void { if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) { ToastStore.sharedInstance().addOrReplaceToast({ - key: getIncomingCallToastKey(ev.getStateKey()), + key: getIncomingCallToastKey(ev.getStateKey()!), priority: 100, component: IncomingCallToast, bodyClassName: "mx_IncomingCallToast", diff --git a/src/PosthogAnalytics.ts b/src/PosthogAnalytics.ts index 173fdb0b19..5c5805af93 100644 --- a/src/PosthogAnalytics.ts +++ b/src/PosthogAnalytics.ts @@ -238,11 +238,11 @@ export class PosthogAnalytics { } } - private static async getPlatformProperties(): Promise { + private static async getPlatformProperties(): Promise> { const platform = PlatformPeg.get(); - let appVersion: string; + let appVersion: string | undefined; try { - appVersion = await platform.getAppVersion(); + appVersion = await platform?.getAppVersion(); } catch (e) { // this happens if no version is set i.e. in dev appVersion = "unknown"; @@ -250,7 +250,7 @@ export class PosthogAnalytics { return { appVersion, - appPlatform: platform.getHumanReadableName(), + appPlatform: platform?.getHumanReadableName(), }; } @@ -411,7 +411,7 @@ export class PosthogAnalytics { // All other scenarios should not track a user before they have given // explicit consent that they are ok with their analytics data being collected const options: IPostHogEventOptions = {}; - const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10); + const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10); if (!isNaN(registrationTime)) { options.timestamp = new Date(registrationTime); } diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx index 246e9e5a37..c92ebcc55e 100644 --- a/src/RoomInvite.tsx +++ b/src/RoomInvite.tsx @@ -142,7 +142,7 @@ export function showAnyInviteErrors( }); return false; } else { - const errorList = []; + const errorList: string[] = []; for (const addr of failedUsers) { if (states[addr] === "error") { const reason = inviter.getErrorText(addr); @@ -173,8 +173,11 @@ export function showAnyInviteErrors(
, roomId: string, userId: string): v if (room) { // if they are already invited or joined we can resolve immediately. const member = room.getMember(userId); - if (member && ["join", "invite"].includes(member.membership)) { + if (member && ["join", "invite"].includes(member.membership!)) { sendResponse(event, { success: true, }); @@ -389,7 +389,7 @@ function kickUser(event: MessageEvent, roomId: string, userId: string): voi if (room) { // if they are already not in the room we can resolve immediately. const member = room.getMember(userId); - if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) { + if (!member || getEffectiveMembership(member.membership!) === EffectiveMembership.Leave) { sendResponse(event, { success: true, }); @@ -472,7 +472,7 @@ function setWidget(event: MessageEvent, roomId: string | null): void { } else { // Room widget if (!roomId) { - sendError(event, _t("Missing roomId."), null); + sendError(event, _t("Missing roomId.")); return; } WidgetUtils.setRoomWidget( @@ -675,7 +675,7 @@ function canSendEvent(event: MessageEvent, roomId: string): void { sendError(event, _t("You are not in this room.")); return; } - const me = client.credentials.userId; + const me = client.credentials.userId!; let canSend = false; if (isState) { diff --git a/src/Searching.ts b/src/Searching.ts index df54695326..90798f480e 100644 --- a/src/Searching.ts +++ b/src/Searching.ts @@ -34,7 +34,7 @@ const SEARCH_LIMIT = 10; async function serverSideSearch( term: string, - roomId: string = undefined, + roomId?: string, abortSignal?: AbortSignal, ): Promise<{ response: ISearchResponse; query: ISearchRequestBody }> { const client = MatrixClientPeg.get(); @@ -67,7 +67,7 @@ async function serverSideSearch( async function serverSideSearchProcess( term: string, - roomId: string = undefined, + roomId?: string, abortSignal?: AbortSignal, ): Promise { const client = MatrixClientPeg.get(); @@ -158,7 +158,7 @@ async function combinedSearch(searchTerm: string, abortSignal?: AbortSignal): Pr async function localSearch( searchTerm: string, - roomId: string = undefined, + roomId?: string, processResult = true, ): Promise<{ response: IResultRoomEvents; query: ISearchArgs }> { const eventIndex = EventIndexPeg.get(); @@ -195,7 +195,7 @@ export interface ISeshatSearchResults extends ISearchResults { serverSideNextBatch?: string; } -async function localSearchProcess(searchTerm: string, roomId: string = undefined): Promise { +async function localSearchProcess(searchTerm: string, roomId?: string): Promise { const emptyResult = { results: [], highlights: [], @@ -244,7 +244,7 @@ async function localPagination(searchResult: ISeshatSearchResults): Promise { +function eventIndexSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise { let searchPromise: Promise; if (roomId !== undefined) { @@ -643,11 +639,7 @@ export function searchPagination(searchResult: ISearchResults): Promise { +export default function eventSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise { const eventIndex = EventIndexPeg.get(); if (eventIndex === null) { diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index bc1c4bb4b9..73f4be06ea 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -109,7 +109,7 @@ async function getSecretStorageKey({ if (!keyInfo) { // if the default key is not available, pretend the default key // isn't set - keyId = undefined; + keyId = null; } } if (!keyId) { @@ -156,7 +156,7 @@ async function getSecretStorageKey({ return MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo); }, }, - /* className= */ null, + /* className= */ undefined, /* isPriorityModal= */ false, /* isStaticModal= */ false, /* options= */ { @@ -206,7 +206,7 @@ export async function getDehydrationKey( } }, }, - /* className= */ null, + /* className= */ undefined, /* isPriorityModal= */ false, /* isStaticModal= */ false, /* options= */ { @@ -243,7 +243,7 @@ async function onSecretRequested( requestId: string, name: string, deviceTrust: DeviceTrustLevel, -): Promise { +): Promise { logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); const client = MatrixClientPeg.get(); if (userId !== client.getUserId()) { @@ -259,19 +259,19 @@ async function onSecretRequested( name === "m.cross_signing.user_signing" ) { const callbacks = client.getCrossSigningCacheCallbacks(); - if (!callbacks.getCrossSigningKeyCache) return; + if (!callbacks?.getCrossSigningKeyCache) return; const keyId = name.replace("m.cross_signing.", ""); const key = await callbacks.getCrossSigningKeyCache(keyId); if (!key) { logger.log(`${keyId} requested by ${deviceId}, but not found in cache`); } - return key && encodeBase64(key); + return key ? encodeBase64(key) : undefined; } else if (name === "m.megolm_backup.v1") { - const key = await client.crypto.getSessionBackupPrivateKey(); + const key = await client.crypto?.getSessionBackupPrivateKey(); if (!key) { logger.log(`session backup key requested by ${deviceId}, but not found in cache`); } - return key && encodeBase64(key); + return key ? encodeBase64(key) : undefined; } logger.warn("onSecretRequested didn't recognise the secret named ", name); } @@ -284,7 +284,7 @@ export const crossSigningCallbacks: ICryptoCallbacks = { }; export async function promptForBackupPassphrase(): Promise { - let key: Uint8Array; + let key!: Uint8Array; const { finished } = Modal.createDialog( RestoreKeyBackupDialog, @@ -292,7 +292,7 @@ export async function promptForBackupPassphrase(): Promise { showSummary: false, keyCallback: (k: Uint8Array) => (key = k), }, - null, + undefined, /* priority = */ false, /* static = */ true, ); @@ -338,7 +338,7 @@ export async function accessSecretStorage(func = async (): Promise => {}, { forceReset, }, - null, + undefined, /* priority = */ false, /* static = */ true, /* options = */ { diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 43350e44d0..e1f028bedd 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -81,7 +81,7 @@ const singleMxcUpload = async (): Promise => { const fileSelector = document.createElement("input"); fileSelector.setAttribute("type", "file"); fileSelector.onchange = (ev: HTMLInputEvent) => { - const file = ev.target.files[0]; + const file = ev.target.files?.[0]; Modal.createDialog(UploadConfirmDialog, { file, @@ -111,7 +111,7 @@ export const CommandCategories = { export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise }>; -type RunFn = (this: Command, roomId: string, args: string) => RunResult; +type RunFn = (this: Command, roomId: string, args?: string) => RunResult; interface ICommandOpts { command: string; @@ -159,7 +159,7 @@ export class Command { return this.getCommand() + " " + this.args; } - public run(roomId: string, threadId: string, args: string): RunResult { + public run(roomId: string, threadId: string | null, args?: string): RunResult { // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` if (!this.runFn) { return reject(newTranslatableError("Command error: Unable to handle slash command.")); @@ -395,12 +395,12 @@ export const Commands = [ runFn: function (roomId, args) { if (args) { const cli = MatrixClientPeg.get(); - const ev = cli.getRoom(roomId).currentState.getStateEvents("m.room.member", cli.getUserId()); + const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getUserId()!); const content = { ...(ev ? ev.getContent() : { membership: "join" }), displayname: args, }; - return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId())); + return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId()!)); } return reject(this.getUsage()); }, @@ -413,7 +413,7 @@ export const Commands = [ description: _td("Changes the avatar of the current room"), isEnabled: () => !isCurrentLocalRoom(), runFn: function (roomId, args) { - let promise = Promise.resolve(args); + let promise = Promise.resolve(args ?? null); if (!args) { promise = singleMxcUpload(); } @@ -436,9 +436,9 @@ export const Commands = [ runFn: function (roomId, args) { const cli = MatrixClientPeg.get(); const room = cli.getRoom(roomId); - const userId = cli.getUserId(); + const userId = cli.getUserId()!; - let promise = Promise.resolve(args); + let promise = Promise.resolve(args ?? null); if (!args) { promise = singleMxcUpload(); } @@ -446,7 +446,7 @@ export const Commands = [ return success( promise.then((url) => { if (!url) return; - const ev = room.currentState.getStateEvents("m.room.member", userId); + const ev = room?.currentState.getStateEvents("m.room.member", userId); const content = { ...(ev ? ev.getContent() : { membership: "join" }), avatar_url: url, @@ -463,7 +463,7 @@ export const Commands = [ args: "[]", description: _td("Changes your avatar in all rooms"), runFn: function (roomId, args) { - let promise = Promise.resolve(args); + let promise = Promise.resolve(args ?? null); if (!args) { promise = singleMxcUpload(); } @@ -496,7 +496,7 @@ export const Commands = [ ); } - const content: MRoomTopicEventContent = room.currentState.getStateEvents("m.room.topic", "")?.getContent(); + const content = room.currentState.getStateEvents("m.room.topic", "")?.getContent(); const topic = !!content ? ContentHelpers.parseTopicContent(content) : { text: _t("This room has no topic.") }; @@ -874,7 +874,8 @@ export const Commands = [ const cli = MatrixClientPeg.get(); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()); return ( - room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room) + !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) && + !isLocalRoom(room) ); }, runFn: function (roomId, args) { @@ -916,7 +917,8 @@ export const Commands = [ const cli = MatrixClientPeg.get(); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()); return ( - room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room) + !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) && + !isLocalRoom(room) ); }, runFn: function (roomId, args) { @@ -932,7 +934,7 @@ export const Commands = [ } const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent.getContent().users[args]) { + if (!powerLevelEvent?.getContent().users[args]) { return reject(newTranslatableError("Could not find user in room")); } return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); @@ -1113,9 +1115,9 @@ export const Commands = [ MatrixClientPeg.get().forceDiscardSession(roomId); return success( - room.getEncryptionTargetMembers().then((members) => { + room?.getEncryptionTargetMembers().then((members) => { // noinspection JSIgnoredPromiseFromCall - MatrixClientPeg.get().crypto.ensureOlmSessionsForUsers( + MatrixClientPeg.get().crypto?.ensureOlmSessionsForUsers( members.map((m) => m.userId), true, ); @@ -1167,7 +1169,7 @@ export const Commands = [ return reject(this.getUsage()); } - const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); + const member = MatrixClientPeg.get().getRoom(roomId)?.getMember(userId); dis.dispatch({ action: Action.ViewUser, // XXX: We should be using a real member object and not assuming what the receiver wants. @@ -1412,7 +1414,7 @@ interface ICmd { export function getCommand(input: string): ICmd { const { cmd, args } = parseCommandString(input); - if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) { + if (CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) { return { cmd: CommandMap.get(cmd), args, diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index a515576748..1798a5016f 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -43,12 +43,12 @@ import { getSenderName } from "./utils/event/getSenderName"; function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string { const client = MatrixClientPeg.get(); const roomId = event.getRoomId(); - const member = client.getRoom(roomId)?.getMember(userId); + const member = client.getRoom(roomId)?.getMember(userId!); return member?.name || member?.rawDisplayName || userId || _t("Someone"); } function textForCallEvent(event: MatrixEvent): () => string { - const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!).name; + const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!)?.name; const isSupported = MatrixClientPeg.get().supportsVoip(); return isSupported @@ -60,7 +60,7 @@ function textForCallEvent(event: MatrixEvent): () => string { // any text to display at all. For this reason they return deferred values // to avoid the expense of looking up translations when they're not needed. -function textForCallInviteEvent(event: MatrixEvent): () => string | null { +function textForCallInviteEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); // FIXME: Find a better way to determine this from the event? const isVoice = !event.getContent().offer?.sdp?.includes("m=video"); @@ -78,9 +78,11 @@ function textForCallInviteEvent(event: MatrixEvent): () => string | null { } else if (!isVoice && !isSupported) { return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { senderName }); } + + return null; } -function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): () => string | null { +function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): (() => string) | null { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender?.name || getRoomMemberDisplayname(ev); const targetName = ev.target?.name || getRoomMemberDisplayname(ev, ev.getStateKey()); @@ -187,9 +189,11 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents return null; } } + + return null; } -function textForTopicEvent(ev: MatrixEvent): () => string | null { +function textForTopicEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { @@ -198,12 +202,12 @@ function textForTopicEvent(ev: MatrixEvent): () => string | null { }); } -function textForRoomAvatarEvent(ev: MatrixEvent): () => string | null { +function textForRoomAvatarEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev?.sender?.name || ev.getSender(); return () => _t("%(senderDisplayName)s changed the room avatar.", { senderDisplayName }); } -function textForRoomNameEvent(ev: MatrixEvent): () => string | null { +function textForRoomNameEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { @@ -224,7 +228,7 @@ function textForRoomNameEvent(ev: MatrixEvent): () => string | null { }); } -function textForTombstoneEvent(ev: MatrixEvent): () => string | null { +function textForTombstoneEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t("%(senderDisplayName)s upgraded this room.", { senderDisplayName }); } @@ -281,7 +285,7 @@ function textForJoinRulesEvent(ev: MatrixEvent, allowJSX: boolean): () => Render } } -function textForGuestAccessEvent(ev: MatrixEvent): () => string | null { +function textForGuestAccessEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().guest_access) { case GuestAccess.CanJoin: @@ -298,7 +302,7 @@ function textForGuestAccessEvent(ev: MatrixEvent): () => string | null { } } -function textForServerACLEvent(ev: MatrixEvent): () => string | null { +function textForServerACLEvent(ev: MatrixEvent): (() => string) | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); const current = ev.getContent(); @@ -308,7 +312,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null { allow_ip_literals: prevContent.allow_ip_literals !== false, }; - let getText: () => string = null; + let getText: () => string; if (prev.deny.length === 0 && prev.allow.length === 0) { getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName }); } else { @@ -328,7 +332,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null { return getText; } -function textForMessageEvent(ev: MatrixEvent): () => string | null { +function textForMessageEvent(ev: MatrixEvent): (() => string) | null { if (isLocationEvent(ev)) { return textForLocationEvent(ev); } @@ -354,7 +358,7 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null { }; } -function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null { +function textForCanonicalAliasEvent(ev: MatrixEvent): (() => string) | null { const senderName = getSenderName(ev); const oldAlias = ev.getPrevContent().alias; const oldAltAliases = ev.getPrevContent().alt_aliases || []; @@ -414,7 +418,7 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null { }); } -function textForThreePidInviteEvent(event: MatrixEvent): () => string | null { +function textForThreePidInviteEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); if (!isValid3pidInvite(event)) { @@ -432,7 +436,7 @@ function textForThreePidInviteEvent(event: MatrixEvent): () => string | null { }); } -function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null { +function textForHistoryVisibilityEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); switch (event.getContent().history_visibility) { case HistoryVisibility.Invited: @@ -463,7 +467,7 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null } // Currently will only display a change if a user's power level is changed -function textForPowerEvent(event: MatrixEvent): () => string | null { +function textForPowerEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); if (!event.getPrevContent()?.users || !event.getContent()?.users) { return null; @@ -528,10 +532,10 @@ const onPinnedMessagesClick = (): void => { RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); }; -function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Renderable { +function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Renderable) | null { if (!SettingsStore.getValue("feature_pinning")) return null; const senderName = getSenderName(event); - const roomId = event.getRoomId(); + const roomId = event.getRoomId()!; const pinned = event.getContent<{ pinned: string[] }>().pinned ?? []; const previouslyPinned: string[] = event.getPrevContent().pinned ?? []; @@ -625,7 +629,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render return () => _t("%(senderName)s changed the pinned messages for the room.", { senderName }); } -function textForWidgetEvent(event: MatrixEvent): () => string | null { +function textForWidgetEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); const { name: prevName, type: prevType, url: prevUrl } = event.getPrevContent(); const { name, type, url } = event.getContent() || {}; @@ -661,12 +665,12 @@ function textForWidgetEvent(event: MatrixEvent): () => string | null { } } -function textForWidgetLayoutEvent(event: MatrixEvent): () => string | null { +function textForWidgetLayoutEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); return () => _t("%(senderName)s has updated the room layout", { senderName }); } -function textForMjolnirEvent(event: MatrixEvent): () => string | null { +function textForMjolnirEvent(event: MatrixEvent): (() => string) | null { const senderName = getSenderName(event); const { entity: prevEntity } = event.getPrevContent(); const { entity, recommendation, reason } = event.getContent(); @@ -795,7 +799,7 @@ function textForMjolnirEvent(event: MatrixEvent): () => string | null { ); } -export function textForLocationEvent(event: MatrixEvent): () => string | null { +export function textForLocationEvent(event: MatrixEvent): () => string { return () => _t("%(senderName)s has shared their location", { senderName: getSenderName(event), @@ -817,7 +821,7 @@ function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string { return message; } -function textForPollStartEvent(event: MatrixEvent): () => string | null { +function textForPollStartEvent(event: MatrixEvent): (() => string) | null { return () => { let message = ""; @@ -836,7 +840,7 @@ function textForPollStartEvent(event: MatrixEvent): () => string | null { }; } -function textForPollEndEvent(event: MatrixEvent): () => string | null { +function textForPollEndEvent(event: MatrixEvent): (() => string) | null { return () => _t("%(senderName)s has ended a poll", { senderName: getSenderName(event), @@ -846,7 +850,7 @@ function textForPollEndEvent(event: MatrixEvent): () => string | null { type Renderable = string | React.ReactNode | null; interface IHandlers { - [type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => () => Renderable; + [type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => (() => Renderable) | null; } const handlers: IHandlers = { diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index bc90ecb255..68dda98e27 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -73,7 +73,7 @@ export function attachRelation(content: IContent, relation?: IEventRelation): vo // exported for tests export function createMessageContent( model: EditorModel, - replyToEvent: MatrixEvent, + replyToEvent: MatrixEvent | undefined, relation: IEventRelation | undefined, permalinkCreator: RoomPermalinkCreator, includeReplyLegacyFallback = true, @@ -148,8 +148,8 @@ export class SendMessageComposer extends React.Component void>; private readonly editorRef = createRef(); - private model: EditorModel = null; - private currentlyComposedEditorState: SerializedPart[] = null; + private model: EditorModel; + private currentlyComposedEditorState: SerializedPart[] | null = null; private dispatcherRef: string; private sendHistoryManager: SendHistoryManager; @@ -299,7 +299,7 @@ export class SendMessageComposer extends React.Component { @@ -575,7 +579,7 @@ export class SendMessageComposer extends React.Component { Modal.createDialog( LogoutDialog, /* props= */ {}, - /* className= */ null, + /* className= */ undefined, /* isPriority= */ false, /* isStatic= */ true, ); diff --git a/src/dispatcher/payloads/SettingUpdatedPayload.ts b/src/dispatcher/payloads/SettingUpdatedPayload.ts index 3e3f42e8a1..9f14d955de 100644 --- a/src/dispatcher/payloads/SettingUpdatedPayload.ts +++ b/src/dispatcher/payloads/SettingUpdatedPayload.ts @@ -23,7 +23,7 @@ export interface SettingUpdatedPayload extends ActionPayload { action: Action.SettingUpdated; settingName: string; - roomId: string; + roomId: string | null; level: SettingLevel; newValueAtLevel: SettingLevel; newValue: SettingValueType; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 2bb7e6ea45..2493a26966 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -288,7 +288,7 @@ export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { // We initially store our output as an array of strings and objects (e.g. React components). // This will then be converted to a string or a at the end - const output = [text]; + const output: SubstitutionValue[] = [text]; // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components. let shouldWrapInSpan = false; @@ -319,7 +319,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri // The textual part before the first match const head = inputText.slice(0, match.index); - const parts = []; + const parts: SubstitutionValue[] = []; // keep track of prevMatch let prevMatch; while (match) { @@ -327,7 +327,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri prevMatch = match; const capturedGroups = match.slice(2); - let replaced; + let replaced: SubstitutionValue; // If substitution is a function, call it if (mapping[regexpString] instanceof Function) { replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups); @@ -434,7 +434,7 @@ export function setLanguage(preferredLangs: string | string[]): Promise { return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); }) - .then(async (langData): Promise => { + .then(async (langData): Promise => { counterpart.registerTranslations(langToUse, langData); await registerCustomTranslations(); counterpart.setLocale(langToUse); @@ -664,7 +664,7 @@ export async function registerCustomTranslations(): Promise { if (!lookupUrl) return; // easy - nothing to do try { - let json: ICustomTranslations; + let json: Optional; if (Date.now() >= cachedCustomTranslationsExpire) { json = CustomTranslationOptions.lookupFn ? CustomTranslationOptions.lookupFn(lookupUrl) diff --git a/src/models/Call.ts b/src/models/Call.ts index 3fefeb3f09..6f96e9d887 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -871,7 +871,7 @@ export class ElementCall extends Call { private onScreenshareRequest = async (ev: CustomEvent): Promise => { ev.preventDefault(); - if (PlatformPeg.get().supportsDesktopCapturer()) { + if (PlatformPeg.get()?.supportsDesktopCapturer()) { await this.messaging!.transport.reply(ev.detail, { pending: true }); const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); diff --git a/src/notifications/NotificationUtils.ts b/src/notifications/NotificationUtils.ts index 42b49024b7..5eadcf81dc 100644 --- a/src/notifications/NotificationUtils.ts +++ b/src/notifications/NotificationUtils.ts @@ -55,10 +55,10 @@ export class NotificationUtils { // "highlight: true/false, // } // If the actions couldn't be decoded then returns null. - public static decodeActions(actions: PushRuleAction[]): IEncodedActions { + public static decodeActions(actions: PushRuleAction[]): IEncodedActions | null { let notify = false; - let sound = null; - let highlight = false; + let sound: string | undefined; + let highlight: boolean | undefined = false; for (let i = 0; i < actions.length; ++i) { const action = actions[i]; @@ -87,7 +87,7 @@ export class NotificationUtils { } const result: IEncodedActions = { notify, highlight }; - if (sound !== null) { + if (sound !== undefined) { result.sound = sound; } return result; diff --git a/src/notifications/PushRuleVectorState.ts b/src/notifications/PushRuleVectorState.ts index 683d80439a..47b4e93774 100644 --- a/src/notifications/PushRuleVectorState.ts +++ b/src/notifications/PushRuleVectorState.ts @@ -62,7 +62,7 @@ export class PushRuleVectorState { * category or in PushRuleVectorState.LOUD, regardless of its enabled * state. Returns null if it does not match these categories. */ - public static contentRuleVectorStateKind(rule: IPushRule): VectorState { + public static contentRuleVectorStateKind(rule: IPushRule): VectorState | null { const decoded = NotificationUtils.decodeActions(rule.actions); if (!decoded) { @@ -77,7 +77,7 @@ export class PushRuleVectorState { if (decoded.highlight) { tweaks++; } - let stateKind = null; + let stateKind: VectorState | null = null; switch (tweaks) { case 0: stateKind = VectorState.On; diff --git a/src/notifications/StandardActions.ts b/src/notifications/StandardActions.ts index f3a9be71b7..3ff2300fa9 100644 --- a/src/notifications/StandardActions.ts +++ b/src/notifications/StandardActions.ts @@ -28,5 +28,5 @@ export class StandardActions { public static ACTION_HIGHLIGHT = encodeActions({ notify: true, highlight: true }); public static ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default", highlight: true }); public static ACTION_DONT_NOTIFY = encodeActions({ notify: false }); - public static ACTION_DISABLED: PushRuleAction[] | null = null; + public static ACTION_DISABLED: PushRuleAction[] | undefined = undefined; } diff --git a/src/notifications/VectorPushRulesDefinitions.ts b/src/notifications/VectorPushRulesDefinitions.ts index d1c7804708..81a9c84a4e 100644 --- a/src/notifications/VectorPushRulesDefinitions.ts +++ b/src/notifications/VectorPushRulesDefinitions.ts @@ -41,7 +41,7 @@ class VectorPushRuleDefinition { } // Translate the rule actions and its enabled value into vector state - public ruleToVectorState(rule: IAnnotatedPushRule): VectorState { + public ruleToVectorState(rule: IAnnotatedPushRule): VectorState | undefined { let enabled = false; if (rule) { enabled = rule.enabled; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index ff84ebf31f..ec0d6b4594 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -81,8 +81,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise
= {}, ): Promise { - let version = "UNKNOWN"; + let version: string | undefined; try { - version = await PlatformPeg.get().getAppVersion(); + version = await PlatformPeg.get()?.getAppVersion(); } catch (err) {} // PlatformPeg already logs this. const body = new FormData(); @@ -304,7 +304,7 @@ export async function submitFeedback( body.append("can_contact", canContact ? "yes" : "no"); body.append("app", "element-web"); - body.append("version", version); + body.append("version", version || "UNKNOWN"); body.append("platform", PlatformPeg.get().getHumanReadableName()); body.append("user_id", MatrixClientPeg.get()?.getUserId()); diff --git a/src/resizer/sizer.ts b/src/resizer/sizer.ts index 7e6202f5a2..ce4bc247d0 100644 --- a/src/resizer/sizer.ts +++ b/src/resizer/sizer.ts @@ -86,9 +86,9 @@ export default class Sizer { public clearItemSize(item: HTMLElement): void { if (this.vertical) { - item.style.height = null; + item.style.height = null!; } else { - item.style.width = null; + item.style.width = null!; } } diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 39885df293..ef03e96683 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -91,7 +91,7 @@ function getLevelOrder(setting: ISetting): SettingLevel[] { export type CallbackFn = ( settingName: string, - roomId: string, + roomId: string | null, atLevel: SettingLevel, newValAtLevel: any, newVal: any, @@ -133,7 +133,7 @@ export default class SettingsStore { // when the setting changes. We track which rooms we're monitoring though to ensure we // don't duplicate updates on the bus. private static watchers = new Map(); - private static monitors = new Map>(); // { settingName => { roomId => callbackRef } } + private static monitors = new Map>(); // { settingName => { roomId => callbackRef } } // Counter used for generation of watcher IDs private static watcherCount = 1; @@ -205,7 +205,7 @@ export default class SettingsStore { return; } - defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)); + defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)!); SettingsStore.watchers.delete(watcherReference); } @@ -223,7 +223,7 @@ export default class SettingsStore { if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); const registerWatcher = (): void => { - this.monitors.get(settingName).set( + this.monitors.get(settingName)!.set( roomId, SettingsStore.watchSetting( settingName, @@ -242,7 +242,7 @@ export default class SettingsStore { ); }; - const rooms = Array.from(this.monitors.get(settingName).keys()); + const rooms = Array.from(this.monitors.get(settingName)!.keys()); const hasRoom = rooms.find((r) => r === roomId || r === null); if (!hasRoom) { registerWatcher(); @@ -250,9 +250,9 @@ export default class SettingsStore { if (roomId === null) { // Unregister all existing watchers and register the new one rooms.forEach((roomId) => { - SettingsStore.unwatchSetting(this.monitors.get(settingName).get(roomId)); + SettingsStore.unwatchSetting(this.monitors.get(settingName)!.get(roomId)); }); - this.monitors.get(settingName).clear(); + this.monitors.get(settingName)!.clear(); registerWatcher(); } // else a watcher is already registered for the room, so don't bother registering it again } @@ -265,7 +265,7 @@ export default class SettingsStore { * The level to get the display name for; Defaults to 'default'. * @return {String} The display name for the setting, or null if not found. */ - public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string { + public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string | null { if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null; let displayName = SETTINGS[settingName].displayName; @@ -296,7 +296,7 @@ export default class SettingsStore { */ public static isFeature(settingName: string): boolean { if (!SETTINGS[settingName]) return false; - return SETTINGS[settingName].isFeature; + return !!SETTINGS[settingName].isFeature; } /** @@ -319,7 +319,7 @@ export default class SettingsStore { } } - public static getLabGroup(settingName: string): LabGroup { + public static getLabGroup(settingName: string): LabGroup | undefined { if (SettingsStore.isFeature(settingName)) { return (SETTINGS[settingName]).labsGroup; } @@ -333,7 +333,7 @@ export default class SettingsStore { */ public static isEnabled(settingName: string): boolean { if (!SETTINGS[settingName]) return false; - return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; + return !SETTINGS[settingName].controller?.settingDisabled ?? true; } /** @@ -559,7 +559,7 @@ export default class SettingsStore { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); } - return level === SettingLevel.DEFAULT || setting.supportedLevels.includes(level); + return level === SettingLevel.DEFAULT || !!setting.supportedLevels?.includes(level); } /** @@ -568,7 +568,7 @@ export default class SettingsStore { * @param {string} settingName The setting name. * @return {SettingLevel} */ - public static firstSupportedLevel(settingName: string): SettingLevel { + public static firstSupportedLevel(settingName: string): SettingLevel | null { // Verify that the setting is actually a setting const setting = SETTINGS[settingName]; if (!setting) { @@ -723,10 +723,10 @@ export default class SettingsStore { logger.log(`--- END DEBUG`); } - private static getHandler(settingName: string, level: SettingLevel): SettingsHandler { + private static getHandler(settingName: string, level: SettingLevel): SettingsHandler | null { const handlers = SettingsStore.getHandlers(settingName); if (!handlers[level]) return null; - return handlers[level]; + return handlers[level]!; } private static getHandlers(settingName: string): HandlerMap { diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index 34ad47a2e8..0e3cae8435 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -16,9 +16,9 @@ limitations under the License. import { SettingLevel } from "./SettingLevel"; -export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; +export type CallbackFn = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM: string = null; +const IRRELEVANT_ROOM: string | null = null; /** * Generalized management class for dealing with watchers on a per-handler (per-level) @@ -26,13 +26,13 @@ const IRRELEVANT_ROOM: string = null; * class, which are then proxied outwards to any applicable watchers. */ export class WatchManager { - private watchers = new Map>(); // settingName -> roomId -> CallbackFn[] + private watchers = new Map>(); // settingName -> roomId -> CallbackFn[] // Proxy for handlers to delegate changes to this manager public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn): void { if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map()); - if (!this.watchers.get(settingName).has(roomId)) this.watchers.get(settingName).set(roomId, []); - this.watchers.get(settingName).get(roomId).push(cb); + if (!this.watchers.get(settingName)!.has(roomId)) this.watchers.get(settingName)!.set(roomId, []); + this.watchers.get(settingName)!.get(roomId)!.push(cb); } // Proxy for handlers to delegate changes to this manager @@ -59,18 +59,18 @@ export class WatchManager { if (!this.watchers.has(settingName)) return; - const roomWatchers = this.watchers.get(settingName); - const callbacks = []; + const roomWatchers = this.watchers.get(settingName)!; + const callbacks: CallbackFn[] = []; if (inRoomId !== null && roomWatchers.has(inRoomId)) { - callbacks.push(...roomWatchers.get(inRoomId)); + callbacks.push(...roomWatchers.get(inRoomId)!); } if (!inRoomId) { // Fire updates to all the individual room watchers too, as they probably care about the change higher up. callbacks.push(...Array.from(roomWatchers.values()).flat(1)); } else if (roomWatchers.has(IRRELEVANT_ROOM)) { - callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)); + callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)!); } for (const callback of callbacks) { diff --git a/src/settings/controllers/ReloadOnChangeController.ts b/src/settings/controllers/ReloadOnChangeController.ts index f1a5df14a1..4066d2f761 100644 --- a/src/settings/controllers/ReloadOnChangeController.ts +++ b/src/settings/controllers/ReloadOnChangeController.ts @@ -20,6 +20,6 @@ import { SettingLevel } from "../SettingLevel"; export default class ReloadOnChangeController extends SettingController { public onChange(level: SettingLevel, roomId: string, newValue: any): void { - PlatformPeg.get().reload(); + PlatformPeg.get()?.reload(); } } diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts index f2bf91e1b5..725dccc623 100644 --- a/src/settings/controllers/SettingController.ts +++ b/src/settings/controllers/SettingController.ts @@ -53,7 +53,7 @@ export default abstract class SettingController { * @param {*} newValue The new value for the setting, may be null. * @return {boolean} Whether the settings change should be accepted. */ - public async beforeChange(level: SettingLevel, roomId: string, newValue: any): Promise { + public async beforeChange(level: SettingLevel, roomId: string | null, newValue: any): Promise { return true; } @@ -63,7 +63,7 @@ export default abstract class SettingController { * @param {String} roomId The room ID, may be null. * @param {*} newValue The new value for the setting, may be null. */ - public onChange(level: SettingLevel, roomId: string, newValue: any): void { + public onChange(level: SettingLevel, roomId: string | null, newValue: any): void { // do nothing by default } diff --git a/src/settings/handlers/AbstractLocalStorageSettingsHandler.ts b/src/settings/handlers/AbstractLocalStorageSettingsHandler.ts index e08ae733e0..bcc72055f4 100644 --- a/src/settings/handlers/AbstractLocalStorageSettingsHandler.ts +++ b/src/settings/handlers/AbstractLocalStorageSettingsHandler.ts @@ -22,7 +22,7 @@ import SettingsHandler from "./SettingsHandler"; */ export default abstract class AbstractLocalStorageSettingsHandler extends SettingsHandler { // Shared cache between all subclass instances - private static itemCache = new Map(); + private static itemCache = new Map(); private static objectCache = new Map(); private static storageListenerBound = false; @@ -51,14 +51,14 @@ export default abstract class AbstractLocalStorageSettingsHandler extends Settin } } - protected getItem(key: string): string { + protected getItem(key: string): string | null { if (!AbstractLocalStorageSettingsHandler.itemCache.has(key)) { const value = localStorage.getItem(key); AbstractLocalStorageSettingsHandler.itemCache.set(key, value); return value; } - return AbstractLocalStorageSettingsHandler.itemCache.get(key); + return AbstractLocalStorageSettingsHandler.itemCache.get(key)!; } protected getBoolean(key: string): boolean | null { @@ -72,7 +72,7 @@ export default abstract class AbstractLocalStorageSettingsHandler extends Settin protected getObject(key: string): T | null { if (!AbstractLocalStorageSettingsHandler.objectCache.has(key)) { try { - const value = JSON.parse(localStorage.getItem(key)); + const value = JSON.parse(localStorage.getItem(key)!); AbstractLocalStorageSettingsHandler.objectCache.set(key, value); return value; } catch (err) { diff --git a/src/settings/handlers/PlatformSettingsHandler.ts b/src/settings/handlers/PlatformSettingsHandler.ts index 2ebc5091d9..e55c5b5c97 100644 --- a/src/settings/handlers/PlatformSettingsHandler.ts +++ b/src/settings/handlers/PlatformSettingsHandler.ts @@ -40,7 +40,7 @@ export default class PlatformSettingsHandler extends SettingsHandler { this.store = {}; // Load setting values as they are async and `getValue` must be synchronous Object.entries(SETTINGS).forEach(([key, setting]) => { - if (setting.supportedLevels.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) { + if (setting.supportedLevels?.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) { payload.platform.getSettingValue(key).then((value: any) => { this.store[key] = value; }); @@ -50,19 +50,19 @@ export default class PlatformSettingsHandler extends SettingsHandler { }; public canSetValue(settingName: string, roomId: string): boolean { - return PlatformPeg.get().supportsSetting(settingName); + return PlatformPeg.get()?.supportsSetting(settingName) ?? false; } public getValue(settingName: string, roomId: string): any { return this.store[settingName]; } - public setValue(settingName: string, roomId: string, newValue: any): Promise { + public async setValue(settingName: string, roomId: string, newValue: any): Promise { this.store[settingName] = newValue; // keep cache up to date for synchronous access - return PlatformPeg.get().setSettingValue(settingName, newValue); + await PlatformPeg.get()?.setSettingValue(settingName, newValue); } public isSupported(): boolean { - return PlatformPeg.get().supportsSetting(); + return PlatformPeg.get()?.supportsSetting() ?? false; } } diff --git a/src/settings/handlers/RoomSettingsHandler.ts b/src/settings/handlers/RoomSettingsHandler.ts index 7818ad4504..a7c6408ce9 100644 --- a/src/settings/handlers/RoomSettingsHandler.ts +++ b/src/settings/handlers/RoomSettingsHandler.ts @@ -44,7 +44,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl } private onEvent = (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent): void => { - const roomId = event.getRoomId(); + const roomId = event.getRoomId()!; const room = this.client.getRoom(roomId); // Note: in tests and during the encryption setup on initial load we might not have @@ -124,7 +124,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl let eventType = DEFAULT_SETTINGS_EVENT_TYPE; if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls"; - return room?.currentState.maySendStateEvent(eventType, this.client.getUserId()) ?? false; + return room?.currentState.maySendStateEvent(eventType, this.client.getUserId()!) ?? false; } public isSupported(): boolean { diff --git a/src/settings/handlers/SettingsHandler.ts b/src/settings/handlers/SettingsHandler.ts index c0070bc1ef..d9cfdaca1d 100644 --- a/src/settings/handlers/SettingsHandler.ts +++ b/src/settings/handlers/SettingsHandler.ts @@ -32,7 +32,7 @@ export default abstract class SettingsHandler { * @param {String} roomId The room ID to read from, may be null. * @returns {*} The setting value, or null if not found. */ - public abstract getValue(settingName: string, roomId: string): any; + public abstract getValue(settingName: string, roomId: string | null): any; /** * Sets the value for a particular setting at this level for a particular room. @@ -45,7 +45,7 @@ export default abstract class SettingsHandler { * @param {*} newValue The new value for the setting, may be null. * @returns {Promise} Resolves when the setting has been saved. */ - public abstract setValue(settingName: string, roomId: string, newValue: any): Promise; + public abstract setValue(settingName: string, roomId: string | null, newValue: any): Promise; /** * Determines if the current user is able to set the value of the given setting @@ -54,7 +54,7 @@ export default abstract class SettingsHandler { * @param {String} roomId The room ID to check in, may be null * @returns {boolean} True if the setting can be set by the user, false otherwise. */ - public abstract canSetValue(settingName: string, roomId: string): boolean; + public abstract canSetValue(settingName: string, roomId: string | null): boolean; /** * Determines if this level is supported on this device. diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts index e967c9bff5..8f27b7a3e9 100644 --- a/src/settings/watchers/FontWatcher.ts +++ b/src/settings/watchers/FontWatcher.ts @@ -30,7 +30,7 @@ export class FontWatcher implements IWatcher { // Externally we tell the user the font is size 15. Internally we use 10. public static readonly SIZE_DIFF = 5; - private dispatcherRef: string; + private dispatcherRef: string | null; public constructor() { this.dispatcherRef = null; @@ -42,6 +42,7 @@ export class FontWatcher implements IWatcher { } public stop(): void { + if (!this.dispatcherRef) return; dis.unregister(this.dispatcherRef); } @@ -77,7 +78,7 @@ export class FontWatcher implements IWatcher { if (fontSize !== size) { SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, fontSize); } - document.querySelector(":root").style.fontSize = toPx(fontSize); + document.querySelector(":root")!.style.fontSize = toPx(fontSize); }; private setSystemFont = ({ diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts index a9f745f50c..6ef9a24a11 100644 --- a/src/settings/watchers/ThemeWatcher.ts +++ b/src/settings/watchers/ThemeWatcher.ts @@ -26,9 +26,9 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { SettingLevel } from "../SettingLevel"; export default class ThemeWatcher { - private themeWatchRef: string; - private systemThemeWatchRef: string; - private dispatcherRef: string; + private themeWatchRef: string | null; + private systemThemeWatchRef: string | null; + private dispatcherRef: string | null; private preferDark: MediaQueryList; private preferLight: MediaQueryList; @@ -67,9 +67,9 @@ export default class ThemeWatcher { this.preferLight.removeEventListener("change", this.onChange); this.preferHighContrast.removeEventListener("change", this.onChange); } - SettingsStore.unwatchSetting(this.systemThemeWatchRef); - SettingsStore.unwatchSetting(this.themeWatchRef); - dis.unregister(this.dispatcherRef); + if (this.systemThemeWatchRef) SettingsStore.unwatchSetting(this.systemThemeWatchRef); + if (this.themeWatchRef) SettingsStore.unwatchSetting(this.themeWatchRef); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); } private onChange = (): void => { diff --git a/src/stores/AsyncStore.ts b/src/stores/AsyncStore.ts index 8873b0845c..dd38351591 100644 --- a/src/stores/AsyncStore.ts +++ b/src/stores/AsyncStore.ts @@ -92,7 +92,7 @@ export abstract class AsyncStore extends EventEmitter { * @param {T|*} newState The new state of the store. * @param {boolean} quiet If true, the function will not raise an UPDATE_EVENT. */ - protected async reset(newState: T | Object = null, quiet = false): Promise { + protected async reset(newState: T | Object | null = null, quiet = false): Promise { await this.lock.acquireAsync(); try { this.storeState = Object.freeze((newState || {})); diff --git a/src/stores/HostSignupStore.ts b/src/stores/HostSignupStore.ts index 768eb7fa2f..0b8e316f90 100644 --- a/src/stores/HostSignupStore.ts +++ b/src/stores/HostSignupStore.ts @@ -19,7 +19,7 @@ import { AsyncStore } from "./AsyncStore"; import { ActionPayload } from "../dispatcher/payloads"; interface IState { - hostSignupActive?: boolean; + hostSignupActive: boolean; } export class HostSignupStore extends AsyncStore { diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts index 26fe328ff3..26a1da54a4 100644 --- a/src/stores/LifecycleStore.ts +++ b/src/stores/LifecycleStore.ts @@ -22,7 +22,7 @@ import { ActionPayload } from "../dispatcher/payloads"; import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload"; interface IState { - deferredAction: ActionPayload; + deferredAction: ActionPayload | null; } const INITIAL_STATE: IState = { @@ -83,8 +83,8 @@ class LifecycleStore extends Store { } } -let singletonLifecycleStore = null; +let singletonLifecycleStore: LifecycleStore | null = null; if (!singletonLifecycleStore) { singletonLifecycleStore = new LifecycleStore(); } -export default singletonLifecycleStore; +export default singletonLifecycleStore!; diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts index d07e2806f7..dc2524b5b5 100644 --- a/src/stores/OwnBeaconStore.ts +++ b/src/stores/OwnBeaconStore.ts @@ -110,15 +110,15 @@ export class OwnBeaconStore extends AsyncStoreWithClient { * ordered by creation time descending */ private liveBeaconIds: BeaconIdentifier[] = []; - private locationInterval: number; - private geolocationError: GeolocationError | undefined; - private clearPositionWatch: ClearWatchCallback | undefined; + private locationInterval?: number; + private geolocationError?: GeolocationError; + private clearPositionWatch?: ClearWatchCallback; /** * Track when the last position was published * So we can manually get position on slow interval * when the target is stationary */ - private lastPublishedPositionTimestamp: number | undefined; + private lastPublishedPositionTimestamp?: number; public constructor() { super(defaultDispatcher); @@ -231,7 +231,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { */ private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => { - if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { + if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) { return; } this.addBeacon(beacon); @@ -242,7 +242,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { * This will be called when a beacon is replaced */ private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => { - if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { + if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) { return; } @@ -309,7 +309,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { } private initialiseBeaconState = (): void => { - const userId = this.matrixClient.getUserId(); + const userId = this.matrixClient.getUserId()!; const visibleRooms = this.matrixClient.getVisibleRooms(); visibleRooms.forEach((room) => { @@ -329,7 +329,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.beaconsByRoomId.set(beacon.roomId, new Set()); } - this.beaconsByRoomId.get(beacon.roomId).add(beacon.identifier); + this.beaconsByRoomId.get(beacon.roomId)!.add(beacon.identifier); beacon.monitorLiveness(); }; @@ -343,7 +343,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient { if (!this.beacons.has(beaconId)) { return; } - this.beacons.get(beaconId).destroy(); + this.beacons.get(beaconId)!.destroy(); this.beacons.delete(beaconId); this.checkLiveness(); diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts index 1ad90b9ee4..07671192f1 100644 --- a/src/stores/OwnProfileStore.ts +++ b/src/stores/OwnProfileStore.ts @@ -43,7 +43,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { return instance; })(); - private monitoredUser: User; + private monitoredUser: User | null; private constructor() { // seed from localstorage because otherwise we won't get these values until a whole network @@ -62,7 +62,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { /** * Gets the display name for the user, or null if not present. */ - public get displayName(): string { + public get displayName(): string | null { if (!this.matrixClient) return this.state.displayName || null; if (this.matrixClient.isGuest()) { @@ -81,7 +81,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { /** * Gets the MXC URI of the user's avatar, or null if not present. */ - public get avatarMxc(): string { + public get avatarMxc(): string | null { return this.state.avatarUrl || null; } @@ -92,7 +92,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { * will be returned as an HTTP URL. * @returns The HTTP URL of the user's avatar */ - public getHttpAvatarUrl(size = 0): string { + public getHttpAvatarUrl(size = 0): string | null { if (!this.avatarMxc) return null; const media = mediaFromMxc(this.avatarMxc); if (!size || size <= 0) { @@ -112,7 +112,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { } protected async onReady(): Promise { - const myUserId = this.matrixClient.getUserId(); + const myUserId = this.matrixClient.getUserId()!; this.monitoredUser = this.matrixClient.getUser(myUserId); if (this.monitoredUser) { this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate); @@ -134,7 +134,7 @@ export class OwnProfileStore extends AsyncStoreWithClient { async (): Promise => { // We specifically do not use the User object we stored for profile info as it // could easily be wrong (such as per-room instead of global profile). - const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()); + const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()!); if (profileInfo.displayname) { window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname); } else { diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index f2b6756071..baedc7de17 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -44,10 +44,12 @@ export enum Phase { export class SetupEncryptionStore extends EventEmitter { private started: boolean; public phase: Phase; - public verificationRequest: VerificationRequest; - public backupInfo: IKeyBackupInfo; - public keyId: string; - public keyInfo: ISecretStorageKeyInfo; + public verificationRequest: VerificationRequest | null = null; + public backupInfo: IKeyBackupInfo | null = null; + // ID of the key that the secrets we want are encrypted with + public keyId: string | null = null; + // Descriptor of the key that the secrets we want are encrypted with + public keyInfo: ISecretStorageKeyInfo | null = null; public hasDevicesToVerifyAgainst: boolean; public static sharedInstance(): SetupEncryptionStore { @@ -61,19 +63,12 @@ export class SetupEncryptionStore extends EventEmitter { } this.started = true; this.phase = Phase.Loading; - this.verificationRequest = null; - this.backupInfo = null; - - // ID of the key that the secrets we want are encrypted with - this.keyId = null; - // Descriptor of the key that the secrets we want are encrypted with - this.keyInfo = null; const cli = MatrixClientPeg.get(); cli.on(CryptoEvent.VerificationRequest, this.onVerificationRequest); cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); - const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId()); + const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId()!); if (requestsInProgress.length) { // If there are multiple, we take the most recent. Equally if the user sends another request from // another device after this screen has been shown, we'll switch to the new one, so this @@ -111,7 +106,7 @@ export class SetupEncryptionStore extends EventEmitter { // do we have any other verified devices which are E2EE which we can verify against? const dehydratedDevice = await cli.getDehydratedDevice(); - const ownUserId = cli.getUserId(); + const ownUserId = cli.getUserId()!; const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId); this.hasDevicesToVerifyAgainst = cli .getStoredDevicesForUser(ownUserId) @@ -119,7 +114,7 @@ export class SetupEncryptionStore extends EventEmitter { (device) => device.getIdentityKey() && (!dehydratedDevice || device.deviceId != dehydratedDevice.device_id) && - crossSigningInfo.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(), + crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(), ); this.phase = Phase.Intro; @@ -183,11 +178,11 @@ export class SetupEncryptionStore extends EventEmitter { }; public onVerificationRequestChange = (): void => { - if (this.verificationRequest.cancelled) { + if (this.verificationRequest?.cancelled) { this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange); this.verificationRequest = null; this.emit("update"); - } else if (this.verificationRequest.phase === VERIF_PHASE_DONE) { + } else if (this.verificationRequest?.phase === VERIF_PHASE_DONE) { this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange); this.verificationRequest = null; // At this point, the verification has finished, we just need to wait for @@ -259,7 +254,7 @@ export class SetupEncryptionStore extends EventEmitter { this.phase = Phase.Finished; this.emit("update"); // async - ask other clients for keys, if necessary - MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests(); + MatrixClientPeg.get().crypto?.cancelAndResendAllOutgoingKeyRequests(); } private async setActiveVerificationRequest(request: VerificationRequest): Promise { diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 8076ec7bda..849917bb8b 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -104,9 +104,9 @@ export default class WidgetStore extends AsyncStoreWithClient { private generateApps(room: Room): IApp[] { return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { return WidgetUtils.makeAppConfig( - ev.getStateKey(), + ev.getStateKey()!, ev.getContent(), - ev.getSender(), + ev.getSender()!, ev.getRoomId(), ev.getId(), ); @@ -172,7 +172,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private onRoomStateEvents = (ev: MatrixEvent): void => { if (ev.getType() !== "im.vector.modular.widgets") return; // TODO: Support m.widget too - const roomId = ev.getRoomId(); + const roomId = ev.getRoomId()!; this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); this.emit(UPDATE_EVENT, roomId); diff --git a/src/stores/local-echo/EchoContext.ts b/src/stores/local-echo/EchoContext.ts index 8583461185..c368bfb8a2 100644 --- a/src/stores/local-echo/EchoContext.ts +++ b/src/stores/local-echo/EchoContext.ts @@ -37,7 +37,7 @@ export abstract class EchoContext extends Whenable impl return this._state; } - public get firstFailedTime(): Date { + public get firstFailedTime(): Date | null { const failedTxn = this.transactions.find((t) => t.didPreviouslyFail || t.status === TransactionStatus.Error); if (failedTxn) return failedTxn.startTime; return null; diff --git a/src/stores/local-echo/GenericEchoChamber.ts b/src/stores/local-echo/GenericEchoChamber.ts index 00b9fe2843..e1f418fbd9 100644 --- a/src/stores/local-echo/GenericEchoChamber.ts +++ b/src/stores/local-echo/GenericEchoChamber.ts @@ -28,19 +28,19 @@ export const PROPERTY_UPDATED = "property_updated"; export abstract class GenericEchoChamber extends EventEmitter { private cache = new Map(); - protected matrixClient: MatrixClient; + protected matrixClient: MatrixClient | null; protected constructor(public readonly context: C, private lookupFn: (key: K) => V) { super(); } - public setClient(client: MatrixClient): void { + public setClient(client: MatrixClient | null): void { const oldClient = this.matrixClient; this.matrixClient = client; this.onClientChanged(oldClient, client); } - protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void; + protected abstract onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void; /** * Gets a value. If the key is in flight, the cached value will be returned. If @@ -50,7 +50,7 @@ export abstract class GenericEchoChamber extends Ev * @returns The value for the key. */ public getValue(key: K): V { - return this.cache.has(key) ? this.cache.get(key).val : this.lookupFn(key); + return this.cache.has(key) ? this.cache.get(key)!.val : this.lookupFn(key); } private cacheVal(key: K, val: V, txn: EchoTransaction): void { @@ -60,7 +60,7 @@ export abstract class GenericEchoChamber extends Ev private decacheKey(key: K): void { if (this.cache.has(key)) { - this.context.disownTransaction(this.cache.get(key).txn); + this.context.disownTransaction(this.cache.get(key)!.txn); this.cache.delete(key); this.emit(PROPERTY_UPDATED, key); } @@ -68,7 +68,7 @@ export abstract class GenericEchoChamber extends Ev protected markEchoReceived(key: K): void { if (this.cache.has(key)) { - const txn = this.cache.get(key).txn; + const txn = this.cache.get(key)!.txn; this.context.disownTransaction(txn); txn.cancel(); } @@ -78,7 +78,7 @@ export abstract class GenericEchoChamber extends Ev public setValue(auditName: string, key: K, targetVal: V, runFn: RunFn, revertFn: RunFn): void { // Cancel any pending transactions for the same key if (this.cache.has(key)) { - this.cache.get(key).txn.cancel(); + this.cache.get(key)!.txn.cancel(); } const ctxn = this.context.beginTransaction(auditName, runFn); diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts index 88a3b84c83..62ebad48b3 100644 --- a/src/stores/local-echo/RoomEchoChamber.ts +++ b/src/stores/local-echo/RoomEchoChamber.ts @@ -34,7 +34,7 @@ export class RoomEchoChamber extends GenericEchoChamber this.properties.get(k)); } - protected onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void { + protected onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void { this.properties.clear(); oldClient?.removeListener(ClientEvent.AccountData, this.onAccountData); if (newClient) { @@ -57,7 +57,7 @@ export class RoomEchoChamber extends GenericEchoChamber { */ public getListState(tagId: TagID): ListNotificationState { if (this.listMap.has(tagId)) { - return this.listMap.get(tagId); + return this.listMap.get(tagId)!; } // TODO: Update if/when invites move out of the room list. @@ -86,14 +86,14 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { if (!this.roomMap.has(room)) { this.roomMap.set(room, new RoomNotificationState(room)); } - return this.roomMap.get(room); + return this.roomMap.get(room)!; } public static get instance(): RoomNotificationStateStore { return RoomNotificationStateStore.internalInstance; } - private onSync = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => { + private onSync = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => { // Only count visible rooms to not torment the user with notification counts in rooms they can't see. // This will include highlights from the previous version of the room internally const globalState = new SummarizedNotificationState(); diff --git a/src/stores/right-panel/RightPanelStore.ts b/src/stores/right-panel/RightPanelStore.ts index ba0eae7885..c0e9352930 100644 --- a/src/stores/right-panel/RightPanelStore.ts +++ b/src/stores/right-panel/RightPanelStore.ts @@ -58,20 +58,20 @@ export default class RightPanelStore extends ReadyWatchingStore { * Resets the store. Intended for test usage only. */ public reset(): void { - this.global = null; + this.global = undefined; this.byRoom = {}; this.viewedRoomId = null; } protected async onReady(): Promise { this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); - this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); + this.matrixClient?.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.loadCacheFromSettings(); this.emitAndUpdateSettings(); } protected async onNotReady(): Promise { - this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); + this.matrixClient?.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); } protected onDispatcherAction(payload: ActionPayload): void { @@ -376,7 +376,7 @@ export default class RightPanelStore extends ReadyWatchingStore { // the room member list. if (SettingsStore.getValue("feature_right_panel_default_open") && !this.byRoom[this.viewedRoomId]?.isOpen) { const history = [{ phase: RightPanelPhases.RoomMemberList }]; - const room = this.viewedRoomId && this.mxClient?.getRoom(this.viewedRoomId); + const room = this.viewedRoomId ? this.mxClient?.getRoom(this.viewedRoomId) : undefined; if (!room?.isSpaceRoom()) { history.unshift({ phase: RightPanelPhases.RoomSummary }); } diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 75469ebb0a..66ea6621a5 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -123,7 +123,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { * @param inTagId The tag ID in which the room resides * @returns The preview, or null if none present. */ - public async getPreviewForRoom(room: Room, inTagId: TagID): Promise { + public async getPreviewForRoom(room: Room, inTagId: TagID): Promise { if (!room) return null; // invalid room, just return nothing if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId); @@ -132,14 +132,14 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (!previews) return null; if (!previews.has(inTagId)) { - return previews.get(TAG_ANY); + return previews.get(TAG_ANY)!; } - return previews.get(inTagId); + return previews.get(inTagId) ?? null; } public generatePreviewForEvent(event: MatrixEvent): string { const previewDef = PREVIEWS[event.getType()]; - return previewDef?.previewer.getTextFor(event, null, true) ?? ""; + return previewDef?.previewer.getTextFor(event, undefined, true) ?? ""; } private async generatePreview(room: Room, tagId?: TagID): Promise { @@ -171,7 +171,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (!previewDef) continue; if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue; - const anyPreview = previewDef.previewer.getTextFor(event, null); + const anyPreview = previewDef.previewer.getTextFor(event); if (!anyPreview) continue; // not previewable for some reason changed = changed || anyPreview !== map.get(TAG_ANY); @@ -179,7 +179,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { const tagsToGenerate = Array.from(map.keys()).filter((t) => t !== TAG_ANY); // we did the any tag above for (const genTagId of tagsToGenerate) { - const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId; + const realTagId = genTagId === TAG_ANY ? undefined : genTagId; const preview = previewDef.previewer.getTextFor(event, realTagId); if (preview === anyPreview) { changed = changed || anyPreview !== map.get(genTagId); diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 6ed50406b1..a97057cf35 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -116,7 +116,7 @@ export class Algorithm extends EventEmitter { * Awaitable version of the sticky room setter. * @param val The new room to sticky. */ - public setStickyRoom(val: Room): void { + public setStickyRoom(val: Room | null): void { try { this.updateStickyRoom(val); } catch (e) { diff --git a/src/stores/room-list/previews/PollStartEventPreview.ts b/src/stores/room-list/previews/PollStartEventPreview.ts index 404a808989..8b49636f29 100644 --- a/src/stores/room-list/previews/PollStartEventPreview.ts +++ b/src/stores/room-list/previews/PollStartEventPreview.ts @@ -29,7 +29,7 @@ export class PollStartEventPreview implements IPreview { public static contextType = MatrixClientContext; public context!: React.ContextType; - public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { let eventContent = event.getContent(); if (event.isRelation("m.replace")) { @@ -51,7 +51,7 @@ export class PollStartEventPreview implements IPreview { let question = poll.question.text.trim(); question = sanitizeForTranslation(question); - if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { return question; } else { return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: question }); diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts index 5346a75b0d..ccf25a8f54 100644 --- a/src/stores/room-list/previews/StickerEventPreview.ts +++ b/src/stores/room-list/previews/StickerEventPreview.ts @@ -22,11 +22,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { _t } from "../../../languageHandler"; export class StickerEventPreview implements IPreview { - public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { const stickerName = event.getContent()["body"]; if (!stickerName) return null; - if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { return stickerName; } else { return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName }); diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index b1a1a39fb6..5188af7354 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -78,7 +78,7 @@ const getSpaceContextKey = (space: SpaceKey): string => `mx_space_context_${spac const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms] - return arr.reduce( + return arr.reduce<[Room[], Room[]]>( (result, room: Room) => { result[room.isSpaceRoom() ? 0 : 1].push(room); return result; @@ -165,7 +165,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.rootSpaces; } - public get activeSpace(): SpaceKey { + public get activeSpace(): SpaceKey | undefined { return this._activeSpace; } @@ -228,7 +228,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { public setActiveSpace(space: SpaceKey, contextSwitch = true): void { if (!space || !this.matrixClient || space === this.activeSpace) return; - let cliSpace: Room; + let cliSpace: Room | null = null; if (!isMetaSpace(space)) { cliSpace = this.matrixClient.getRoom(space); if (!cliSpace?.isSpaceRoom()) return; @@ -246,6 +246,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // else if the last viewed room in this space is joined then view that // else view space home or home depending on what is being clicked on if ( + roomId && cliSpace?.getMyMembership() !== "invite" && this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" && this.isRoomInSpace(space, roomId) @@ -348,10 +349,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { .filter((ev) => ev.getContent()?.via); return ( sortBy(childEvents, (ev) => { - return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()); + return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()!); }) .map((ev) => { - const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey(), true); + const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey()!, true); return history[history.length - 1]; }) .filter((room) => { @@ -373,7 +374,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const userId = this.matrixClient?.getUserId(); const room = this.matrixClient?.getRoom(roomId); return ( - room?.currentState + (room?.currentState .getStateEvents(EventType.SpaceParent) .map((ev) => { const content = ev.getContent(); @@ -396,7 +397,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return parent; }) - .filter(Boolean) || [] + .filter(Boolean) as Room[]) || [] ); } @@ -467,7 +468,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { space: SpaceKey, includeDescendantSpaces = true, useCache = true, - ): Set => { + ): Set | undefined => { if (space === MetaSpace.Home && this.allRoomsInHome) { return undefined; } @@ -490,7 +491,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private markTreeChildren = (rootSpace: Room, unseen: Set): void => { const stack = [rootSpace]; while (stack.length) { - const space = stack.pop(); + const space = stack.pop()!; unseen.delete(space); this.getChildSpaces(space.roomId).forEach((space) => { if (unseen.has(space)) { @@ -646,7 +647,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const visibleRooms = this.matrixClient.getVisibleRooms(); - let dmBadgeSpace: MetaSpace; + let dmBadgeSpace: MetaSpace | undefined; // only show badges on dms on the most relevant space if such exists if (enabledMetaSpaces.has(MetaSpace.People)) { dmBadgeSpace = MetaSpace.People; @@ -702,8 +703,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient { ); // put all invites in the Home Space }; - private static isInSpace(member: RoomMember): boolean { - return member.membership === "join" || member.membership === "invite"; + private static isInSpace(member?: RoomMember | null): boolean { + return member?.membership === "join" || member?.membership === "invite"; } // Method for resolving the impact of a single user's membership change in the given Space and its hierarchy @@ -755,11 +756,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.rootSpaces.forEach((s) => { // traverse each space tree in DFS to build up the supersets as you go up, // reusing results from like subtrees. - const traverseSpace = (spaceId: string, parentPath: Set): [Set, Set] => { + const traverseSpace = ( + spaceId: string, + parentPath: Set, + ): [Set, Set] | undefined => { if (parentPath.has(spaceId)) return; // prevent cycles // reuse existing results if multiple similar branches exist if (this.roomIdsBySpace.has(spaceId) && this.userIdsBySpace.has(spaceId)) { - return [this.roomIdsBySpace.get(spaceId), this.userIdsBySpace.get(spaceId)]; + return [this.roomIdsBySpace.get(spaceId)!, this.userIdsBySpace.get(spaceId)!]; } const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId)); @@ -865,7 +869,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (this.suggestedRooms.find((r) => r.room_id === roomId)) return; // try to find the canonical parent first - let parent: SpaceKey = this.getCanonicalParent(roomId)?.roomId; + let parent: SpaceKey | undefined = this.getCanonicalParent(roomId)?.roomId; // otherwise, try to find a root space which contains this room if (!parent) { diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts index 77d65c1ab3..0cc4bbab8e 100644 --- a/src/stores/spaces/index.ts +++ b/src/stores/spaces/index.ts @@ -54,7 +54,7 @@ export interface ISuggestedRoom extends IHierarchyRoom { viaServers: string[]; } -export function isMetaSpace(spaceKey: SpaceKey): boolean { +export function isMetaSpace(spaceKey?: SpaceKey): boolean { return ( spaceKey === MetaSpace.Home || spaceKey === MetaSpace.Favourites || diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 1604e49778..f87da3bc18 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -33,6 +33,7 @@ import { WidgetApiFromWidgetAction, WidgetKind, } from "matrix-widget-api"; +import { Optional } from "matrix-events-sdk"; import { EventEmitter } from "events"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; @@ -156,7 +157,7 @@ export class ElementWidget extends Widget { export class StopGapWidget extends EventEmitter { private client: MatrixClient; - private messaging: ClientWidgetApi; + private messaging: ClientWidgetApi | null; private mockWidget: ElementWidget; private scalarToken: string; private roomId?: string; @@ -172,7 +173,7 @@ export class StopGapWidget extends EventEmitter { // Backwards compatibility: not all old widgets have a creatorUserId if (!app.creatorUserId) { app = objectShallowClone(app); // clone to prevent accidental mutation - app.creatorUserId = this.client.getUserId(); + app.creatorUserId = this.client.getUserId()!; } this.mockWidget = new ElementWidget(app); @@ -181,7 +182,7 @@ export class StopGapWidget extends EventEmitter { this.virtual = app.eventId === undefined; } - private get eventListenerRoomId(): string { + private get eventListenerRoomId(): Optional { // When widgets are listening to events, we need to make sure they're only // receiving events for the right room. In particular, room widgets get locked // to the room they were added in while account widgets listen to the currently @@ -192,7 +193,7 @@ export class StopGapWidget extends EventEmitter { return SdkContextClass.instance.roomViewStore.getRoomId(); } - public get widgetApi(): ClientWidgetApi { + public get widgetApi(): ClientWidgetApi | null { return this.messaging; } @@ -214,7 +215,7 @@ export class StopGapWidget extends EventEmitter { const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {}; const defaults: ITemplateParams = { widgetRoomId: this.roomId, - currentUserId: this.client.getUserId(), + currentUserId: this.client.getUserId()!, userDisplayName: OwnProfileStore.instance.displayName, userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), clientId: ELEMENT_CLIENT_ID, @@ -256,9 +257,9 @@ export class StopGapWidget extends EventEmitter { ev.preventDefault(); if (ModalWidgetStore.instance.canOpenModalWidget()) { ModalWidgetStore.instance.openModalWidget(ev.detail.data, this.mockWidget, this.roomId); - this.messaging.transport.reply(ev.detail, {}); // ack + this.messaging?.transport.reply(ev.detail, {}); // ack } else { - this.messaging.transport.reply(ev.detail, { + this.messaging?.transport.reply(ev.detail, { error: { message: "Unable to open modal at this time", }, @@ -301,14 +302,14 @@ export class StopGapWidget extends EventEmitter { // Check up front if this is even a valid request const targetRoomId = (ev.detail.data || {}).room_id; if (!targetRoomId) { - return this.messaging.transport.reply(ev.detail, { + return this.messaging?.transport.reply(ev.detail, { error: { message: "Room ID not supplied." }, }); } // Check the widget's permission - if (!this.messaging.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) { - return this.messaging.transport.reply(ev.detail, { + if (!this.messaging?.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) { + return this.messaging?.transport.reply(ev.detail, { error: { message: "This widget does not have permission for this action (denied)." }, }); } @@ -332,7 +333,7 @@ export class StopGapWidget extends EventEmitter { const events = room.getLiveTimeline()?.getEvents() || []; const roomEvent = events[events.length - 1]; if (!roomEvent) continue; // force later code to think the room is fresh - this.readUpToMap[room.roomId] = roomEvent.getId(); + this.readUpToMap[room.roomId] = roomEvent.getId()!; } // Attach listeners for feeding events - the underlying widget classes handle permissions for us @@ -343,7 +344,7 @@ export class StopGapWidget extends EventEmitter { this.messaging.on( `action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`, (ev: CustomEvent) => { - if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { + if (this.messaging?.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { ActiveWidgetStore.instance.setWidgetPersistence( this.mockWidget.id, this.roomId, @@ -360,7 +361,7 @@ export class StopGapWidget extends EventEmitter { this.messaging.on( `action:${WidgetApiFromWidgetAction.SendSticker}`, (ev: CustomEvent) => { - if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) { + if (this.messaging?.hasCapability(MatrixCapabilities.StickerSending)) { // Acknowledge first ev.preventDefault(); this.messaging.transport.reply(ev.detail, {}); @@ -381,7 +382,7 @@ export class StopGapWidget extends EventEmitter { (ev: CustomEvent) => { // Acknowledge first ev.preventDefault(); - this.messaging.transport.reply(ev.detail, {}); + this.messaging?.transport.reply(ev.detail, {}); // First close the stickerpicker defaultDispatcher.dispatch({ action: "stickerpicker_close" }); @@ -415,7 +416,7 @@ export class StopGapWidget extends EventEmitter { }), }); } - this.messaging.transport.reply(ev.detail, {}); + this.messaging?.transport.reply(ev.detail, {}); }); } } @@ -478,7 +479,7 @@ export class StopGapWidget extends EventEmitter { private onToDeviceEvent = async (ev: MatrixEvent): Promise => { await this.client.decryptEventIfNeeded(ev); if (ev.isDecryptionFailure()) return; - await this.messaging.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted()); + await this.messaging?.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted()); }; private feedEvent(ev: MatrixEvent): void { @@ -490,7 +491,7 @@ export class StopGapWidget extends EventEmitter { // // This approach of "read up to" prevents widgets receiving decryption spam from startup or // receiving out-of-order events from backfill and such. - const upToEventId = this.readUpToMap[ev.getRoomId()]; + const upToEventId = this.readUpToMap[ev.getRoomId()!]; if (upToEventId) { // Small optimization for exact match (prevent search) if (upToEventId === ev.getId()) { @@ -501,7 +502,7 @@ export class StopGapWidget extends EventEmitter { // Timelines are most recent last, so reverse the order and limit ourselves to 100 events // to avoid overusing the CPU. - const timeline = this.client.getRoom(ev.getRoomId()).getLiveTimeline(); + const timeline = this.client.getRoom(ev.getRoomId()!).getLiveTimeline(); const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100); for (const timelineEvent of events) { diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index 992b6d7665..1d3b2aa1d9 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -131,7 +131,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { protected async onReady(): Promise { this.updateAllRooms(); - this.matrixClient.on(RoomStateEvent.Events, this.updateRoomFromState); + this.matrixClient?.on(RoomStateEvent.Events, this.updateRoomFromState); this.pinnedRef = SettingsStore.watchSetting("Widgets.pinned", null, this.updateFromSettings); this.layoutRef = SettingsStore.watchSetting("Widgets.layout", null, this.updateFromSettings); WidgetStore.instance.on(UPDATE_EVENT, this.updateFromWidgetStore); @@ -155,7 +155,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { private updateFromWidgetStore = (roomId?: string): void => { if (roomId) { - const room = this.matrixClient.getRoom(roomId); + const room = this.matrixClient?.getRoom(roomId); if (room) this.recalculateRoom(room); } else { this.updateAllRooms(); @@ -164,13 +164,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore { private updateRoomFromState = (ev: MatrixEvent): void => { if (ev.getType() !== WIDGET_LAYOUT_EVENT_TYPE) return; - const room = this.matrixClient.getRoom(ev.getRoomId()); + const room = this.matrixClient?.getRoom(ev.getRoomId()); if (room) this.recalculateRoom(room); }; private updateFromSettings = (settingName: string, roomId: string /* and other stuff */): void => { if (roomId) { - const room = this.matrixClient.getRoom(roomId); + const room = this.matrixClient?.getRoom(roomId); if (room) this.recalculateRoom(room); } else { this.updateAllRooms(); @@ -189,7 +189,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, ""); const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId); - let userLayout = SettingsStore.getValue("Widgets.layout", room.roomId); + let userLayout = SettingsStore.getValue("Widgets.layout", room.roomId); if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) { // For some other layout that we don't really care about. The user can reset this @@ -197,7 +197,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { userLayout = null; } - const roomLayout: ILayoutStateEvent = layoutEv ? layoutEv.getContent() : null; + const roomLayout = layoutEv?.getContent() ?? null; // We filter for the center container first. // (An error is raised, if there are multiple widgets marked for the center container) // For the right and top container multiple widgets are allowed. @@ -218,9 +218,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore { // The widget won't need to be put in any other container. continue; } - let targetContainer = defaultContainer; + let targetContainer: Container = defaultContainer; if (!!manualContainer || !!stateContainer) { - targetContainer = manualContainer ? manualContainer : stateContainer; + targetContainer = manualContainer ?? stateContainer!; } else if (isLegacyPinned && !stateContainer) { // Special legacy case targetContainer = Container.Top; @@ -259,7 +259,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { // Determine width distribution and height of the top container now (the only relevant one) const widths: number[] = []; - let maxHeight = null; // null == default + let maxHeight: number | null = null; // null == default let doAutobalance = true; for (let i = 0; i < topWidgets.length; i++) { const widget = topWidgets[i]; @@ -487,7 +487,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { public canCopyLayoutToRoom(room: Room): boolean { if (!this.matrixClient) return false; // not ready yet - return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()); + return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()!); } public copyLayoutToRoom(room: Room): void { @@ -508,7 +508,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { }; } } - this.matrixClient.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, ""); + this.matrixClient?.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, ""); } private getAllWidgets(room: Room): [IApp, Container][] { @@ -516,7 +516,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { if (!containers) return []; const ret: [IApp, Container][] = []; - for (const container of Object.keys(containers)) { + for (const container in containers) { const widgets = containers[container as Container].ordered; for (const widget of widgets) { ret.push([widget, container as Container]); diff --git a/src/stores/widgets/WidgetPermissionStore.ts b/src/stores/widgets/WidgetPermissionStore.ts index b045849456..244a95f06c 100644 --- a/src/stores/widgets/WidgetPermissionStore.ts +++ b/src/stores/widgets/WidgetPermissionStore.ts @@ -32,7 +32,7 @@ export class WidgetPermissionStore { // TODO (all functions here): Merge widgetKind with the widget definition private packSettingKey(widget: Widget, kind: WidgetKind, roomId?: string): string { - let location = roomId; + let location: string | null | undefined = roomId; if (kind !== WidgetKind.Room) { location = this.context.client?.getUserId(); } diff --git a/src/theme.ts b/src/theme.ts index 3353d9c8ae..8089102eb5 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -133,7 +133,7 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string { .map((face) => { const src = face.src ?.map((srcElement) => { - let format: string; + let format = ""; if (srcElement.format) { format = `format("${srcElement.format}")`; } diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index aec53dae10..e3e1b84dda 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -66,7 +66,7 @@ interface Props { export function IncomingCallToast({ callEvent }: Props): JSX.Element { const roomId = callEvent.getRoomId()!; - const room = MatrixClientPeg.get().getRoom(roomId); + const room = MatrixClientPeg.get().getRoom(roomId) ?? undefined; const call = useCall(roomId); const dismissToast = useCallback((): void => { @@ -107,7 +107,7 @@ export function IncomingCallToast({ callEvent }: Props): JSX.Element { defaultDispatcher.dispatch({ action: Action.ViewRoom, - room_id: room.roomId, + room_id: room?.roomId, view_call: true, metricsTrigger: undefined, }); diff --git a/src/utils/ErrorUtils.tsx b/src/utils/ErrorUtils.tsx index 8e4c26a827..1cc049c03a 100644 --- a/src/utils/ErrorUtils.tsx +++ b/src/utils/ErrorUtils.tsx @@ -35,7 +35,7 @@ import { _t, _td, Tags, TranslatedString } from "../languageHandler"; */ export function messageForResourceLimitError( limitType: string, - adminContact: string, + adminContact: string | undefined, strings: Record, extraTranslations?: Tags, ): TranslatedString { @@ -57,7 +57,7 @@ export function messageForResourceLimitError( if (errString.includes("")) { return _t(errString, {}, Object.assign({ a: linkSub }, extraTranslations)); } else { - return _t(errString, {}, extraTranslations); + return _t(errString, {}, extraTranslations!); } } diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts index aa12f790b0..456e7d9d05 100644 --- a/src/utils/FileUtils.ts +++ b/src/utils/FileUtils.ts @@ -37,7 +37,7 @@ export function presentableTextForFile( shortened = false, ): string { let text = fallbackText; - if (content.body?.length > 0) { + if (content.body?.length) { // The content body should be the name of the file including a // file extension. text = content.body; diff --git a/src/utils/FontManager.ts b/src/utils/FontManager.ts index 4b26bc9db2..089f800aad 100644 --- a/src/utils/FontManager.ts +++ b/src/utils/FontManager.ts @@ -69,7 +69,7 @@ async function isColrFontSupported(): Promise { try { const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); + const context = canvas.getContext("2d")!; const img = new Image(); // eslint-disable-next-line const fontCOLR = diff --git a/src/utils/beacon/useLiveBeacons.ts b/src/utils/beacon/useLiveBeacons.ts index fd6b2164d9..556e6b3dea 100644 --- a/src/utils/beacon/useLiveBeacons.ts +++ b/src/utils/beacon/useLiveBeacons.ts @@ -26,8 +26,8 @@ import { useEventEmitterState } from "../../hooks/useEventEmitter"; export const useLiveBeacons = (roomId: Room["roomId"], matrixClient: MatrixClient): Beacon[] => { const room = matrixClient.getRoom(roomId); - const liveBeacons = useEventEmitterState(room.currentState, RoomStateEvent.BeaconLiveness, () => - room.currentState?.liveBeaconIds.map((beaconIdentifier) => room.currentState.beacons.get(beaconIdentifier)), + const liveBeacons = useEventEmitterState(room?.currentState, RoomStateEvent.BeaconLiveness, () => + room?.currentState?.liveBeaconIds.map((beaconIdentifier) => room.currentState.beacons.get(beaconIdentifier)), ); return liveBeacons; diff --git a/src/utils/direct-messages.ts b/src/utils/direct-messages.ts index 93de779fc5..b1fd4fa844 100644 --- a/src/utils/direct-messages.ts +++ b/src/utils/direct-messages.ts @@ -102,7 +102,7 @@ export abstract class Member { * Gets the MXC URL of this Member's avatar. For users this should be their profile's * avatar MXC URL or null if none set. For 3PIDs this should always be null. */ - public abstract getMxcAvatarUrl(): string; + public abstract getMxcAvatarUrl(): string | null; } export class DirectoryMember extends Member { @@ -127,8 +127,8 @@ export class DirectoryMember extends Member { return this._userId; } - public getMxcAvatarUrl(): string { - return this.avatarUrl; + public getMxcAvatarUrl(): string | null { + return this.avatarUrl ?? null; } } @@ -156,7 +156,7 @@ export class ThreepidMember extends Member { return this.id; } - public getMxcAvatarUrl(): string { + public getMxcAvatarUrl(): string | null { return null; } } diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index 4bdc723118..c01f5ac3ee 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -276,14 +276,14 @@ export default abstract class Exporter { protected isReply(event: MatrixEvent): boolean { const isEncrypted = event.isEncrypted(); // If encrypted, in_reply_to lies in event.event.content - const content = isEncrypted ? event.event.content : event.getContent(); + const content = isEncrypted ? event.event.content! : event.getContent(); const relatesTo = content["m.relates_to"]; return !!(relatesTo && relatesTo["m.in_reply_to"]); } protected isAttachment(mxEv: MatrixEvent): boolean { const attachmentTypes = ["m.sticker", "m.image", "m.file", "m.video", "m.audio"]; - return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype); + return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype!); } public abstract export(): Promise; diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index d12da2bd65..77c1bc0155 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -217,10 +217,10 @@ export default class HTMLExporter extends Exporter { `; } - protected getAvatarURL(event: MatrixEvent): string | undefined { + protected getAvatarURL(event: MatrixEvent): string | null { const member = event.sender; const avatarUrl = member?.getMxcAvatarUrl(); - return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : undefined; + return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : null; } protected async saveAvatarIfNeeded(event: MatrixEvent): Promise { @@ -386,7 +386,7 @@ export default class HTMLExporter extends Exporter { protected async createHTML(events: MatrixEvent[], start: number): Promise { let content = ""; - let prevEvent = null; + let prevEvent: MatrixEvent | null = null; for (let i = start; i < Math.min(start + 1000, events.length); i++) { const event = events[i]; this.updateProgress( diff --git a/src/utils/exportUtils/JSONExport.ts b/src/utils/exportUtils/JSONExport.ts index d884467358..e1b41b6f82 100644 --- a/src/utils/exportUtils/JSONExport.ts +++ b/src/utils/exportUtils/JSONExport.ts @@ -47,7 +47,7 @@ export default class JSONExporter extends Exporter { const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator; const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || ""; - const exporter = this.client.getUserId(); + const exporter = this.client.getUserId()!; const exporterName = this.room?.getMember(exporter)?.rawDisplayName || exporter; const jsonObject = { room_name: this.room.name, diff --git a/src/utils/exportUtils/exportCSS.ts b/src/utils/exportUtils/exportCSS.ts index 2a6a098a14..267443504a 100644 --- a/src/utils/exportUtils/exportCSS.ts +++ b/src/utils/exportUtils/exportCSS.ts @@ -45,7 +45,7 @@ async function getRulesFromCssFile(path: string): Promise { // the style will only be parsed once it is added to a document doc.body.appendChild(styleElement); - return styleElement.sheet; + return styleElement.sheet!; } // naively culls unused css rules based on which classes are present in the html, diff --git a/src/utils/image-media.ts b/src/utils/image-media.ts index 003f6d8a37..b69d552355 100644 --- a/src/utils/image-media.ts +++ b/src/utils/image-media.ts @@ -19,6 +19,8 @@ import { IEncryptedFile } from "../customisations/models/IMediaEventContent"; type ThumbnailableElement = HTMLImageElement | HTMLVideoElement; +export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448 + interface IThumbnail { info: { thumbnail_info?: { @@ -29,15 +31,13 @@ interface IThumbnail { }; w: number; h: number; - [BLURHASH_FIELD]: string; + [BLURHASH_FIELD]?: string; thumbnail_url?: string; thumbnail_file?: IEncryptedFile; }; thumbnail: Blob; } -export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448 - const MAX_WIDTH = 800; const MAX_HEIGHT = 600; @@ -88,7 +88,7 @@ export async function createThumbnail( canvas = document.createElement("canvas"); canvas.width = targetWidth; canvas.height = targetHeight; - context = canvas.getContext("2d"); + context = canvas.getContext("2d")!; } context.drawImage(element, 0, 0, targetWidth, targetHeight); @@ -97,7 +97,9 @@ export async function createThumbnail( if (window.OffscreenCanvas && canvas instanceof OffscreenCanvas) { thumbnailPromise = canvas.convertToBlob({ type: mimeType }); } else { - thumbnailPromise = new Promise((resolve) => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType)); + thumbnailPromise = new Promise((resolve) => + (canvas as HTMLCanvasElement).toBlob(resolve as BlobCallback, mimeType), + ); } const imageData = context.getImageData(0, 0, targetWidth, targetHeight); diff --git a/test/Notifier-test.ts b/test/Notifier-test.ts index 3bd120c274..e156658ddc 100644 --- a/test/Notifier-test.ts +++ b/test/Notifier-test.ts @@ -345,7 +345,7 @@ describe("Notifier", () => { tweaks: {}, }); Notifier.start(); - Notifier.onSyncStateChange(SyncState.Syncing); + Notifier.onSyncStateChange(SyncState.Syncing, null); }); afterEach(() => { diff --git a/test/SlashCommands-test.tsx b/test/SlashCommands-test.tsx index c91196a53b..4ed059e9f3 100644 --- a/test/SlashCommands-test.tsx +++ b/test/SlashCommands-test.tsx @@ -33,21 +33,23 @@ describe("SlashCommands", () => { let localRoom: LocalRoom; let command: Command; - const findCommand = (cmd: string): Command => { + const findCommand = (cmd: string): Command | undefined => { return Commands.find((command: Command) => command.command === cmd); }; const setCurrentRoom = (): void => { mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId); - mocked(client.getRoom).mockImplementation((rId: string): Room => { + mocked(client.getRoom).mockImplementation((rId: string): Room | null => { if (rId === roomId) return room; + return null; }); }; const setCurrentLocalRoon = (): void => { mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId); - mocked(client.getRoom).mockImplementation((rId: string): Room => { + mocked(client.getRoom).mockImplementation((rId: string): Room | null => { if (rId === localRoomId) return localRoom; + return null; }); }; @@ -57,8 +59,8 @@ describe("SlashCommands", () => { client = createTestClient(); jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); - room = new Room(roomId, client, client.getUserId()); - localRoom = new LocalRoom(localRoomId, client, client.getUserId()); + room = new Room(roomId, client, client.getUserId()!); + localRoom = new LocalRoom(localRoomId, client, client.getUserId()!); jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId"); }); @@ -68,7 +70,7 @@ describe("SlashCommands", () => { const command = getCommand("/topic pizza"); 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.setRoomTopic).toHaveBeenCalledWith("room-id", "pizza", undefined); }); }); @@ -96,7 +98,7 @@ describe("SlashCommands", () => { ["converttoroom"], ])("/%s", (commandName: string) => { beforeEach(() => { - command = findCommand(commandName); + command = findCommand(commandName)!; }); describe("isEnabled", () => { @@ -114,7 +116,7 @@ describe("SlashCommands", () => { describe("/tovirtual", () => { beforeEach(() => { - command = findCommand("tovirtual"); + command = findCommand("tovirtual")!; }); describe("isEnabled", () => { @@ -154,7 +156,7 @@ describe("SlashCommands", () => { describe("/remakeolm", () => { beforeEach(() => { - command = findCommand("remakeolm"); + command = findCommand("remakeolm")!; }); describe("isEnabled", () => { @@ -198,39 +200,39 @@ describe("SlashCommands", () => { describe("/part", () => { it("should part room matching alias if found", async () => { - const room1 = new Room("room-id", client, client.getUserId()); + const room1 = new Room("room-id", client, client.getUserId()!); room1.getCanonicalAlias = jest.fn().mockReturnValue("#foo:bar"); - const room2 = new Room("other-room", client, client.getUserId()); + const room2 = new Room("other-room", client, client.getUserId()!); room2.getCanonicalAlias = jest.fn().mockReturnValue("#baz:bar"); mocked(client.getRooms).mockReturnValue([room1, room2]); 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()); }); it("should part room matching alt alias if found", async () => { - const room1 = new Room("room-id", client, client.getUserId()); + const room1 = new Room("room-id", client, client.getUserId()!); room1.getAltAliases = jest.fn().mockReturnValue(["#foo:bar"]); - const room2 = new Room("other-room", client, client.getUserId()); + const room2 = new Room("other-room", client, client.getUserId()!); room2.getAltAliases = jest.fn().mockReturnValue(["#baz:bar"]); mocked(client.getRooms).mockReturnValue([room1, room2]); 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()); }); }); describe.each(["rainbow", "rainbowme"])("/%s", (commandName: string) => { - const command = findCommand(commandName); + const command = findCommand(commandName)!; it("should return usage if no args", () => { - expect(command.run(roomId, null, null).error).toBe(command.getUsage()); + expect(command.run(roomId, null).error).toBe(command.getUsage()); }); it("should make things rainbowy", () => { diff --git a/test/SlidingSyncManager-test.ts b/test/SlidingSyncManager-test.ts index 6b5a854e84..1829fc2958 100644 --- a/test/SlidingSyncManager-test.ts +++ b/test/SlidingSyncManager-test.ts @@ -50,19 +50,19 @@ describe("SlidingSyncManager", () => { }); it("adds a custom subscription for a lazy-loadable room", async () => { const roomId = "!lazy:id"; - const room = new Room(roomId, client, client.getUserId()); + const room = new Room(roomId, client, client.getUserId()!); room.getLiveTimeline().initialiseState([ new MatrixEvent({ type: "m.room.create", state_key: "", event_id: "$abc123", - sender: client.getUserId(), + sender: client.getUserId()!, content: { creator: client.getUserId(), }, }), ]); - mocked(client.getRoom).mockImplementation((r: string): Room => { + mocked(client.getRoom).mockImplementation((r: string): Room | null => { if (roomId === r) { return room; } diff --git a/test/autocomplete/EmojiProvider-test.ts b/test/autocomplete/EmojiProvider-test.ts index f66b39be1f..ac5e40d041 100644 --- a/test/autocomplete/EmojiProvider-test.ts +++ b/test/autocomplete/EmojiProvider-test.ts @@ -73,16 +73,16 @@ describe("EmojiProvider", function () { add("😘"); //kissing_heart add("😘"); add("😚"); //kissing_closed_eyes - const emojiProvider = new EmojiProvider(null); + const emojiProvider = new EmojiProvider(null!); let completionsList = await emojiProvider.getCompletions(":kis", { beginning: true, end: 3, start: 3 }); - expect(completionsList[0].component.props.title).toEqual(":kissing_heart:"); - expect(completionsList[1].component.props.title).toEqual(":kissing_closed_eyes:"); + expect(completionsList[0].component!.props.title).toEqual(":kissing_heart:"); + expect(completionsList[1].component!.props.title).toEqual(":kissing_closed_eyes:"); completionsList = await emojiProvider.getCompletions(":kissing_c", { beginning: true, end: 3, start: 3 }); - expect(completionsList[0].component.props.title).toEqual(":kissing_closed_eyes:"); + expect(completionsList[0].component!.props.title).toEqual(":kissing_closed_eyes:"); completionsList = await emojiProvider.getCompletions(":so", { beginning: true, end: 2, start: 2 }); - expect(completionsList[0].component.props.title).toEqual(":sob:"); + expect(completionsList[0].component!.props.title).toEqual(":sob:"); }); }); diff --git a/test/components/structures/MessagePanel-test.tsx b/test/components/structures/MessagePanel-test.tsx index 533849a1ec..ade1f7a28d 100644 --- a/test/components/structures/MessagePanel-test.tsx +++ b/test/components/structures/MessagePanel-test.tsx @@ -46,7 +46,7 @@ jest.mock("../../../src/utils/beacon", () => ({ const roomId = "!roomId:server_name"; describe("MessagePanel", function () { - let clock: FakeTimers.InstalledClock | null = null; + let clock: FakeTimers.InstalledClock; const realSetTimeout = window.setTimeout; const events = mkEvents(); const userId = "@me:here"; @@ -117,14 +117,11 @@ describe("MessagePanel", function () { }); afterEach(function () { - if (clock) { - clock.uninstall(); - clock = null; - } + clock?.uninstall(); }); function mkEvents() { - const events = []; + const events: MatrixEvent[] = []; const ts0 = Date.now(); for (let i = 0; i < 10; i++) { events.push( @@ -141,7 +138,7 @@ describe("MessagePanel", function () { // Just to avoid breaking Dateseparator tests that might run at 00hrs function mkOneDayEvents() { - const events = []; + const events: MatrixEvent[] = []; const ts0 = Date.parse("09 May 2004 00:12:00 GMT"); for (let i = 0; i < 10; i++) { events.push( @@ -158,7 +155,7 @@ describe("MessagePanel", function () { // make a collection of events with some member events that should be collapsed with an EventListSummary function mkMelsEvents() { - const events = []; + const events: MatrixEvent[] = []; const ts0 = Date.now(); let i = 0; @@ -200,7 +197,7 @@ describe("MessagePanel", function () { // A list of membership events only with nothing else function mkMelsEventsOnly() { - const events = []; + const events: MatrixEvent[] = []; const ts0 = Date.now(); let i = 0; @@ -323,7 +320,7 @@ describe("MessagePanel", function () { } function isReadMarkerVisible(rmContainer?: Element) { - return rmContainer?.children.length > 0; + return !!rmContainer?.children.length; } it("should show the events", function () { @@ -466,8 +463,8 @@ describe("MessagePanel", function () { it("should collapse creation events", function () { const events = mkCreationEvents(); - const createEvent = events.find((event) => event.getType() === "m.room.create"); - const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption"); + const createEvent = events.find((event) => event.getType() === "m.room.create")!; + const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption")!; client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); TestUtilsMatrix.upsertRoomStateEvents(room, events); @@ -493,8 +490,8 @@ describe("MessagePanel", function () { it("should not collapse beacons as part of creation events", function () { const events = mkCreationEvents(); - const creationEvent = events.find((event) => event.getType() === "m.room.create"); - const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender(), creationEvent.getRoomId(), { + const creationEvent = events.find((event) => event.getType() === "m.room.create")!; + const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender()!, creationEvent.getRoomId()!, { isLive: true, }); const combinedEvents = [...events, beaconInfoEvent]; @@ -550,7 +547,7 @@ describe("MessagePanel", function () { let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10); const updatedEvents = [ ...events, @@ -570,7 +567,7 @@ describe("MessagePanel", function () { els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(11); }); it("prepends events into summaries during backward pagination without changing key", () => { @@ -580,7 +577,7 @@ describe("MessagePanel", function () { let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10); const updatedEvents = [ TestUtilsMatrix.mkMembership({ @@ -600,7 +597,7 @@ describe("MessagePanel", function () { els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(11); }); it("assigns different keys to summaries that get split up", () => { @@ -610,7 +607,7 @@ describe("MessagePanel", function () { let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10); const updatedEvents = [ ...events.slice(0, 5), @@ -628,10 +625,10 @@ describe("MessagePanel", function () { els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(2); expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(5); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(5); expect(els[1].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[5].getId()}`); - expect(els[1].getAttribute("data-scroll-tokens").split(",").length).toEqual(5); + expect(els[1].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(5); }); // We test this because setting lookups can be *slow*, and we don't want @@ -681,7 +678,7 @@ describe("MessagePanel", function () { const els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); - expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(3); + expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(3); }); }); diff --git a/test/components/structures/ThreadPanel-test.tsx b/test/components/structures/ThreadPanel-test.tsx index 1225490bd7..dd404c27bb 100644 --- a/test/components/structures/ThreadPanel-test.tsx +++ b/test/components/structures/ThreadPanel-test.tsx @@ -68,7 +68,7 @@ describe("ThreadPanel", () => { const found = container.querySelector(".mx_ThreadPanel_dropdown"); expect(found).toBeTruthy(); expect(screen.queryByRole("menu")).toBeFalsy(); - fireEvent.click(found); + fireEvent.click(found!); expect(screen.queryByRole("menu")).toBeTruthy(); }); @@ -80,11 +80,13 @@ describe("ThreadPanel", () => { setFilterOption={() => undefined} />, ); - fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown")); + fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown")!); const found = screen.queryAllByRole("menuitemradio"); expect(found).toHaveLength(2); const foundButton = screen.queryByRole("menuitemradio", { checked: true }); - expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t("Shows all threads from current room")}`); + expect(foundButton?.textContent).toEqual( + `${_t("All threads")}${_t("Shows all threads from current room")}`, + ); expect(foundButton).toMatchSnapshot(); }); }); diff --git a/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx b/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx index d59a96f259..93fbc7c2b6 100644 --- a/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx +++ b/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx @@ -111,7 +111,7 @@ describe("", () => { const { container } = getComponent(); const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); - fireEvent.click(container.querySelector("[role=button]")); + fireEvent.click(container.querySelector("[role=button]")!); expect(dispatchSpy).toHaveBeenCalledWith({ action: Action.ViewRoom, @@ -144,7 +144,7 @@ describe("", () => { const { container } = getComponent(); const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); - fireEvent.click(container.querySelector("[role=button]")); + fireEvent.click(container.querySelector("[role=button]")!); expect(dispatchSpy).toHaveBeenCalledWith({ action: Action.ViewRoom, @@ -163,7 +163,7 @@ describe("", () => { ]); const { container, rerender } = getComponent(); // error mode - expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual( + expect(container.querySelector(".mx_LeftPanelLiveShareWarning")?.textContent).toEqual( "An error occurred whilst sharing your live location", ); @@ -175,7 +175,7 @@ describe("", () => { rerender(); // default mode - expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual( + expect(container.querySelector(".mx_LeftPanelLiveShareWarning")?.textContent).toEqual( "You are sharing your live location", ); }); @@ -252,7 +252,7 @@ describe("", () => { const { container } = getComponent(); const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); - fireEvent.click(container.querySelector("[role=button]")); + fireEvent.click(container.querySelector("[role=button]")!); expect(dispatchSpy).toHaveBeenCalledWith({ action: Action.ViewRoom, diff --git a/test/components/views/dialogs/ForwardDialog-test.tsx b/test/components/views/dialogs/ForwardDialog-test.tsx index 2036d83753..7d0354f352 100644 --- a/test/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/components/views/dialogs/ForwardDialog-test.tsx @@ -74,13 +74,13 @@ describe("ForwardDialog", () => { const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => { mockClient.getVisibleRooms.mockReturnValue(rooms); - mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId)); + mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null); const wrapper: RenderResult = render( , ); @@ -135,8 +135,8 @@ describe("ForwardDialog", () => { }), ); - let firstButton: Element; - let secondButton: Element; + let firstButton!: Element; + let secondButton!: Element; const update = () => { [firstButton, secondButton] = container.querySelectorAll(".mx_ForwardList_sendButton"); }; @@ -253,7 +253,6 @@ describe("ForwardDialog", () => { [M_ASSET.name]: { type: LocationAssetType.Pin }, [M_LOCATION.name]: { uri: geoUri, - description: undefined as string, }, }; expect(mockClient.sendEvent).toHaveBeenCalledWith( @@ -269,7 +268,7 @@ describe("ForwardDialog", () => { expect(container.querySelector(".mx_MLocationBody")).toBeTruthy(); sendToFirstRoom(container); - const timestamp = M_TIMESTAMP.findIn(modernLocationEvent.getContent()); + const timestamp = M_TIMESTAMP.findIn(modernLocationEvent.getContent())!; // text and description from original event are removed // text gets new default message from event values const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`; @@ -280,7 +279,6 @@ describe("ForwardDialog", () => { [M_ASSET.name]: { type: LocationAssetType.Pin }, [M_LOCATION.name]: { uri: geoUri, - description: undefined as string, }, }; expect(mockClient.sendEvent).toHaveBeenCalledWith( @@ -301,7 +299,6 @@ describe("ForwardDialog", () => { [M_ASSET.name]: { type: LocationAssetType.Pin }, [M_LOCATION.name]: { uri: geoUri, - description: undefined as string, }, geo_uri: geoUri, [M_TIMESTAMP.name]: timestamp, diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index d46561422e..3248ef80fe 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -92,7 +92,7 @@ function mockClient({ (it) => !searchTerm || it.user_id.toLowerCase().includes(searchTerm) || - it.display_name.toLowerCase().includes(searchTerm), + it.display_name?.toLowerCase().includes(searchTerm), ); return Promise.resolve({ results: results.slice(0, limit ?? +Infinity), @@ -138,7 +138,7 @@ describe("Spotlight Dialog", () => { mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] }); testRoom = mkRoom(mockedClient, "!test23:example.com"); mocked(testRoom.getMyMembership).mockReturnValue("join"); - testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId()); + testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId()!); testLocalRoom.updateMyMembership("join"); mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]); @@ -149,7 +149,7 @@ describe("Spotlight Dialog", () => { describe("should apply filters supplied via props", () => { it("without filter", async () => { - const wrapper = mount( null} />); + const wrapper = mount( null} />); await act(async () => { await sleep(200); }); diff --git a/test/components/views/elements/StyledRadioGroup-test.tsx b/test/components/views/elements/StyledRadioGroup-test.tsx index 8a78609c39..34c53e21c0 100644 --- a/test/components/views/elements/StyledRadioGroup-test.tsx +++ b/test/components/views/elements/StyledRadioGroup-test.tsx @@ -61,7 +61,7 @@ describe("", () => { value: optionC.value, }); - expect(getCheckedInput(component).value).toEqual(optionC.value); + expect(getCheckedInput(component)?.value).toEqual(optionC.value); }); it("selects correct buttons when definitions have checked prop", () => { @@ -99,7 +99,7 @@ describe("", () => { onChange, }); - fireEvent.click(getInputByValue(component, optionB.value)); + fireEvent.click(getInputByValue(component, optionB.value)!); expect(onChange).toHaveBeenCalledWith(optionB.value); }); diff --git a/test/components/views/elements/TooltipTarget-test.tsx b/test/components/views/elements/TooltipTarget-test.tsx index 098e62c4f0..09438fc2e5 100644 --- a/test/components/views/elements/TooltipTarget-test.tsx +++ b/test/components/views/elements/TooltipTarget-test.tsx @@ -62,7 +62,7 @@ describe("", () => { const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o)); it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => { - const wrapper = getComponent({ alignment: Alignment[alignment] }); + const wrapper = getComponent({ alignment: Alignment[alignment] })!; act(() => { Simulate.mouseOver(wrapper); }); @@ -70,7 +70,7 @@ describe("", () => { }); it("hides tooltip on mouseleave", () => { - const wrapper = getComponent(); + const wrapper = getComponent()!; act(() => { Simulate.mouseOver(wrapper); }); @@ -82,7 +82,7 @@ describe("", () => { }); it("displays tooltip on focus", () => { - const wrapper = getComponent(); + const wrapper = getComponent()!; act(() => { Simulate.focus(wrapper); }); @@ -90,7 +90,7 @@ describe("", () => { }); it("hides tooltip on blur", async () => { - const wrapper = getComponent(); + const wrapper = getComponent()!; act(() => { Simulate.focus(wrapper); }); diff --git a/test/components/views/location/shareLocation-test.ts b/test/components/views/location/shareLocation-test.ts index 910d471362..6213e1a531 100644 --- a/test/components/views/location/shareLocation-test.ts +++ b/test/components/views/location/shareLocation-test.ts @@ -53,7 +53,7 @@ describe("shareLocation", () => { }, ); - shareLocationFn = shareLocation(client, roomId, shareType, null, () => {}); + shareLocationFn = shareLocation(client, roomId, shareType, undefined, () => {}); }); it("should forward the call to doMaybeLocalRoomAction", () => { diff --git a/test/components/views/messages/EncryptionEvent-test.tsx b/test/components/views/messages/EncryptionEvent-test.tsx index 11268e077a..ebf084cdbc 100644 --- a/test/components/views/messages/EncryptionEvent-test.tsx +++ b/test/components/views/messages/EncryptionEvent-test.tsx @@ -52,7 +52,7 @@ describe("EncryptionEvent", () => { event = mkMessage({ event: true, room: roomId, - user: client.getUserId(), + user: client.getUserId()!, }); jest.spyOn(DMRoomMap, "shared").mockReturnValue({ getUserIdForRoomId: jest.fn(), @@ -61,9 +61,9 @@ describe("EncryptionEvent", () => { describe("for an encrypted room", () => { beforeEach(() => { - event.event.content.algorithm = algorithm; + event.event.content!.algorithm = algorithm; mocked(client.isRoomEncrypted).mockReturnValue(true); - const room = new Room(roomId, client, client.getUserId()); + const room = new Room(roomId, client, client.getUserId()!); mocked(client.getRoom).mockReturnValue(room); }); @@ -91,7 +91,7 @@ describe("EncryptionEvent", () => { describe("with unknown algorithm", () => { beforeEach(() => { - event.event.content.algorithm = "unknown"; + event.event.content!.algorithm = "unknown"; }); it("should show the expected texts", () => { @@ -115,9 +115,9 @@ describe("EncryptionEvent", () => { describe("for an encrypted local room", () => { beforeEach(() => { - event.event.content.algorithm = algorithm; + event.event.content!.algorithm = algorithm; mocked(client.isRoomEncrypted).mockReturnValue(true); - const localRoom = new LocalRoom(roomId, client, client.getUserId()); + const localRoom = new LocalRoom(roomId, client, client.getUserId()!); mocked(client.getRoom).mockReturnValue(localRoom); renderEncryptionEvent(client, event); }); diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index dbb0e7f379..a0a1958a27 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -236,9 +236,7 @@ describe("", () => { it("opens message context menu on click", () => { const { getByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.click(queryByLabelText("Options")); - }); + fireEvent.click(queryByLabelText("Options")!); expect(getByTestId("mx_MessageContextMenu")).toBeTruthy(); }); }); @@ -269,9 +267,7 @@ describe("", () => { it("dispatches reply event on click", () => { const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.click(queryByLabelText("Reply")); - }); + fireEvent.click(queryByLabelText("Reply")!); expect(dispatcher.dispatch).toHaveBeenCalledWith({ action: "reply_to_event", @@ -306,9 +302,7 @@ describe("", () => { it("opens reaction picker on click", () => { const { queryByLabelText, getByTestId } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.click(queryByLabelText("React")); - }); + fireEvent.click(queryByLabelText("React")!); expect(getByTestId("mx_EmojiPicker")).toBeTruthy(); }); }); @@ -421,9 +415,7 @@ describe("", () => { jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.click(getByLabelText("Reply in thread")); - }); + fireEvent.click(getByLabelText("Reply in thread")); expect(dispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ViewUserSettings, @@ -453,9 +445,7 @@ describe("", () => { it("opens thread on click", () => { const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.click(getByLabelText("Reply in thread")); - }); + fireEvent.click(getByLabelText("Reply in thread")); expect(dispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ShowThread, @@ -482,9 +472,7 @@ describe("", () => { } as unknown as Thread); const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent }); - act(() => { - fireEvent.click(getByLabelText("Reply in thread")); - }); + fireEvent.click(getByLabelText("Reply in thread")); expect(dispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ShowThread, @@ -501,7 +489,7 @@ describe("", () => { describe("favourite button", () => { //for multiple event usecase const favButton = (evt: MatrixEvent) => { - return getComponent({ mxEvent: evt }).getByTestId(evt.getId()); + return getComponent({ mxEvent: evt }).getByTestId(evt.getId()!); }; describe("when favourite_messages feature is enabled", () => { @@ -538,9 +526,7 @@ describe("", () => { expect(localStorageMock.getItem("io_element_favouriteMessages")).toBeNull(); //if only alice's event is fired - act(() => { - fireEvent.click(alicesAction); - }); + fireEvent.click(alicesAction); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar"); @@ -550,9 +536,7 @@ describe("", () => { ); //when bob's event is fired,both should be styled and stored in localStorage - act(() => { - fireEvent.click(bobsAction); - }); + fireEvent.click(bobsAction); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(bobsAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); @@ -567,9 +551,7 @@ describe("", () => { ); //if decided to unfavourite bob's event by clicking again - act(() => { - fireEvent.click(bobsAction); - }); + fireEvent.click(bobsAction); expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(localStorageMock.getItem("io_element_favouriteMessages")).toEqual('["$alices_message"]'); @@ -599,9 +581,7 @@ describe("", () => { event.preventDefault = jest.fn(); const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent(queryByLabelText(buttonLabel), event); - }); + fireEvent(queryByLabelText(buttonLabel)!, event); expect(event.stopPropagation).toHaveBeenCalled(); expect(event.preventDefault).toHaveBeenCalled(); expect(queryByTestId("mx_MessageContextMenu")).toBeFalsy(); @@ -610,9 +590,7 @@ describe("", () => { it("does shows context menu when right-clicking options", () => { const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); - act(() => { - fireEvent.contextMenu(queryByLabelText("Options")); - }); + fireEvent.contextMenu(queryByLabelText("Options")!); expect(queryByTestId("mx_MessageContextMenu")).toBeTruthy(); }); }); diff --git a/test/components/views/rooms/MemberList-test.tsx b/test/components/views/rooms/MemberList-test.tsx index a384705590..da6f27e582 100644 --- a/test/components/views/rooms/MemberList-test.tsx +++ b/test/components/views/rooms/MemberList-test.tsx @@ -44,11 +44,11 @@ describe("MemberList", () => { return room; } - let parentDiv: HTMLDivElement = null; - let client: MatrixClient = null; - let root: Component = null; + let parentDiv: HTMLDivElement; + let client: MatrixClient; + let root: Component; let memberListRoom: Room; - let memberList: MemberList = null; + let memberList: MemberList; let adminUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = []; @@ -140,14 +140,13 @@ describe("MemberList", () => { if (parentDiv) { ReactDOM.unmountComponentAtNode(parentDiv); parentDiv.remove(); - parentDiv = null; } done(); }); function expectOrderedByPresenceAndPowerLevel(memberTiles: MemberTile[], isPresenceEnabled: boolean) { - let prevMember = null; + let prevMember: RoomMember | undefined; for (const tile of memberTiles) { const memberA = prevMember; const memberB = tile.props.member; @@ -160,8 +159,8 @@ describe("MemberList", () => { console.log(memberList.memberString(memberA)); console.log(memberList.memberString(memberB)); - const userA = memberA.user; - const userB = memberB.user; + const userA = memberA.user!; + const userB = memberB.user!; let groupChange = false; @@ -229,16 +228,16 @@ describe("MemberList", () => { const onlineUsers = [adminUsers[0]]; const offlineUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)]; activeUsers.forEach((u) => { - u.user.currentlyActive = true; - u.user.presence = "online"; + u.user!.currentlyActive = true; + u.user!.presence = "online"; }); onlineUsers.forEach((u) => { - u.user.currentlyActive = false; - u.user.presence = "online"; + u.user!.currentlyActive = false; + u.user!.presence = "online"; }); offlineUsers.forEach((u) => { - u.user.currentlyActive = false; - u.user.presence = "offline"; + u.user!.currentlyActive = false; + u.user!.presence = "offline"; }); // Bypass all the event listeners and skip to the good part @@ -268,18 +267,18 @@ describe("MemberList", () => { const inactiveUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)]; activeUsers.forEach((u) => { u.powerLevel = 100; // set everyone to the same PL to avoid running that check - u.user.lastPresenceTs = 1000; - u.user.lastActiveAgo = 0; + u.user!.lastPresenceTs = 1000; + u.user!.lastActiveAgo = 0; }); semiActiveUsers.forEach((u) => { u.powerLevel = 100; - u.user.lastPresenceTs = 1000; - u.user.lastActiveAgo = 50; + u.user!.lastPresenceTs = 1000; + u.user!.lastActiveAgo = 50; }); inactiveUsers.forEach((u) => { u.powerLevel = 100; - u.user.lastPresenceTs = 1000; - u.user.lastActiveAgo = 100; + u.user!.lastPresenceTs = 1000; + u.user!.lastActiveAgo = 100; }); // Bypass all the event listeners and skip to the good part @@ -294,10 +293,10 @@ describe("MemberList", () => { // Intentionally put everyone on the same level to force a name comparison const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers]; allUsers.forEach((u) => { - u.user.currentlyActive = true; - u.user.presence = "online"; - u.user.lastPresenceTs = 1000; - u.user.lastActiveAgo = 0; + u.user!.currentlyActive = true; + u.user!.presence = "online"; + u.user!.lastPresenceTs = 1000; + u.user!.lastActiveAgo = 0; u.powerLevel = 100; }); diff --git a/test/components/views/rooms/NotificationBadge/NotificationBadge-test.tsx b/test/components/views/rooms/NotificationBadge/NotificationBadge-test.tsx index b322fcc892..888bbcb081 100644 --- a/test/components/views/rooms/NotificationBadge/NotificationBadge-test.tsx +++ b/test/components/views/rooms/NotificationBadge/NotificationBadge-test.tsx @@ -37,13 +37,13 @@ describe("NotificationBadge", () => { />, ); - fireEvent.click(container.firstChild); + fireEvent.click(container.firstChild!); expect(cb).toHaveBeenCalledTimes(1); - fireEvent.mouseEnter(container.firstChild); + fireEvent.mouseEnter(container.firstChild!); expect(cb).toHaveBeenCalledTimes(2); - fireEvent.mouseLeave(container.firstChild); + fireEvent.mouseLeave(container.firstChild!); expect(cb).toHaveBeenCalledTimes(3); }); diff --git a/test/components/views/rooms/RoomHeader-test.tsx b/test/components/views/rooms/RoomHeader-test.tsx index 46152ca6da..3aed3e6845 100644 --- a/test/components/views/rooms/RoomHeader-test.tsx +++ b/test/components/views/rooms/RoomHeader-test.tsx @@ -217,7 +217,7 @@ function createRoom(info: IRoomCreationInfo) { const client: MatrixClient = MatrixClientPeg.get(); const roomId = "!1234567890:domain"; - const userId = client.getUserId(); + const userId = client.getUserId()!; if (info.isDm) { client.getAccountData = (eventType) => { expect(eventType).toEqual("m.direct"); @@ -231,7 +231,7 @@ function createRoom(info: IRoomCreationInfo) { pendingEventOrdering: PendingEventOrdering.Detached, }); - const otherJoinEvents = []; + const otherJoinEvents: MatrixEvent[] = []; for (const otherUserId of info.userIds) { otherJoinEvents.push(mkJoinEvent(roomId, otherUserId)); } diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index 7dcc89be1e..cb547cc365 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -88,7 +88,7 @@ describe("", () => { const documentOffset = new DocumentOffset(11, true); model.update("hello world", "insertText", documentOffset); - const content = createMessageContent(model, null, undefined, permalinkCreator); + const content = createMessageContent(model, undefined, undefined, permalinkCreator); expect(content).toEqual({ body: "hello world", @@ -101,7 +101,7 @@ describe("", () => { const documentOffset = new DocumentOffset(13, true); model.update("hello *world*", "insertText", documentOffset); - const content = createMessageContent(model, null, undefined, permalinkCreator); + const content = createMessageContent(model, undefined, undefined, permalinkCreator); expect(content).toEqual({ body: "hello *world*", @@ -116,7 +116,7 @@ describe("", () => { const documentOffset = new DocumentOffset(22, true); model.update("/me blinks __quickly__", "insertText", documentOffset); - const content = createMessageContent(model, null, undefined, permalinkCreator); + const content = createMessageContent(model, undefined, undefined, permalinkCreator); expect(content).toEqual({ body: "blinks __quickly__", @@ -132,7 +132,7 @@ describe("", () => { model.update("/me ✨sparkles✨", "insertText", documentOffset); expect(model.parts.length).toEqual(4); // Emoji count as non-text - const content = createMessageContent(model, null, undefined, permalinkCreator); + const content = createMessageContent(model, undefined, undefined, permalinkCreator); expect(content).toEqual({ body: "✨sparkles✨", @@ -146,7 +146,7 @@ describe("", () => { model.update("//dev/null is my favourite place", "insertText", documentOffset); - const content = createMessageContent(model, null, undefined, permalinkCreator); + const content = createMessageContent(model, undefined, undefined, permalinkCreator); expect(content).toEqual({ body: "/dev/null is my favourite place", @@ -216,7 +216,7 @@ describe("", () => { // ensure the right state was persisted to localStorage unmount(); - expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({ + expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({ parts: [{ type: "plain", text: "Test Text" }], replyEventId: mockEvent.getId(), }); @@ -249,7 +249,7 @@ describe("", () => { // ensure the right state was persisted to localStorage window.dispatchEvent(new Event("beforeunload")); - expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({ + expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({ parts: [{ type: "plain", text: "Hello World" }], }); }); @@ -260,7 +260,7 @@ describe("", () => { const { container } = getComponent({ replyToEvent: mockEvent }); addTextToComposer(container, "This is a message"); - fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" }); + fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); await waitFor(() => { expect(spyDispatcher).toHaveBeenCalledWith({ @@ -271,7 +271,7 @@ describe("", () => { }); expect(container.textContent).toBe(""); - const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`); + const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`)!; expect(JSON.parse(str)).toStrictEqual({ parts: [{ type: "plain", text: "This is a message" }], replyEventId: mockEvent.getId(), @@ -289,7 +289,7 @@ describe("", () => { const { container } = getComponent(); addTextToComposer(container, "test message"); - fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" }); + fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" }); expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, { body: "test message", diff --git a/test/components/views/settings/Notifications-test.tsx b/test/components/views/settings/Notifications-test.tsx index 8ff7654d4c..d956ba0d4b 100644 --- a/test/components/views/settings/Notifications-test.tsx +++ b/test/components/views/settings/Notifications-test.tsx @@ -310,11 +310,8 @@ describe("", () => { it("enables email notification when toggling on", async () => { await getComponentAndWait(); - const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); - - await act(async () => { - fireEvent.click(emailToggle); - }); + const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!; + fireEvent.click(emailToggle); expect(mockClient.setPusher).toHaveBeenCalledWith( expect.objectContaining({ @@ -332,11 +329,8 @@ describe("", () => { mockClient.setPusher.mockRejectedValue({}); await getComponentAndWait(); - const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); - - await act(async () => { - fireEvent.click(emailToggle); - }); + const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!; + fireEvent.click(emailToggle); // force render await flushPromises(); @@ -349,11 +343,8 @@ describe("", () => { mockClient.getPushers.mockResolvedValue({ pushers: [testPusher] }); await getComponentAndWait(); - const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); - - await act(async () => { - fireEvent.click(emailToggle); - }); + const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!; + fireEvent.click(emailToggle); expect(mockClient.setPusher).toHaveBeenCalledWith({ ...testPusher, @@ -364,21 +355,19 @@ describe("", () => { it("toggles and sets settings correctly", async () => { await getComponentAndWait(); - let audioNotifsToggle: HTMLDivElement; + let audioNotifsToggle!: HTMLDivElement; const update = () => { audioNotifsToggle = screen .getByTestId("notif-setting-audioNotificationsEnabled") - .querySelector('div[role="switch"]'); + .querySelector('div[role="switch"]')!; }; update(); expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("true"); expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(true); - act(() => { - fireEvent.click(audioNotifsToggle); - }); + fireEvent.click(audioNotifsToggle); update(); expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("false"); @@ -422,7 +411,7 @@ describe("", () => { const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id); await act(async () => { - const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]'); + const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!; fireEvent.click(offToggle); }); diff --git a/test/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx b/test/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx index 1509add915..88e49e0239 100644 --- a/test/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/room/VoipRoomSettingsTab-test.tsx @@ -55,7 +55,7 @@ describe("RolesRoomSettingsTab", () => { }; const getElementCallSwitch = (tab: RenderResult): HTMLElement => { - return tab.container.querySelector("[data-testid='element-call-switch']"); + return tab.container.querySelector("[data-testid='element-call-switch']")!; }; describe("correct state", () => { @@ -87,7 +87,7 @@ describe("RolesRoomSettingsTab", () => { const tab = renderTab(); - fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); + fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!); await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith( room.roomId, @@ -107,7 +107,7 @@ describe("RolesRoomSettingsTab", () => { const tab = renderTab(); - fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); + fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!); await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith( room.roomId, @@ -128,7 +128,7 @@ describe("RolesRoomSettingsTab", () => { const tab = renderTab(); - fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); + fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!); await waitFor(() => expect(cli.sendStateEvent).toHaveBeenCalledWith( room.roomId, diff --git a/test/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx b/test/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx index 3e46f05581..c09ec8f8ec 100644 --- a/test/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx +++ b/test/components/views/spaces/SpaceSettingsVisibilityTab-test.tsx @@ -94,13 +94,13 @@ describe("", () => { const getByTestId = (container: Element, id: string) => container.querySelector(`[data-test-id=${id}]`); const toggleGuestAccessSection = async (component: Element) => { - const toggleButton = getByTestId(component, "toggle-guest-access-btn"); + const toggleButton = getByTestId(component, "toggle-guest-access-btn")!; await act(async () => { Simulate.click(toggleButton); }); }; - const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"'); - const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"'); + const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"]'); + const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"]'); const getErrorMessage = (component: Element) => getByTestId(component, "space-settings-error")?.textContent; beforeEach(() => { @@ -150,10 +150,10 @@ describe("", () => { await toggleGuestAccessSection(component); const guestAccessInput = getGuestAccessToggle(component); - expect(guestAccessInput.getAttribute("aria-checked")).toEqual("true"); + expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("true"); await act(async () => { - Simulate.click(guestAccessInput); + Simulate.click(guestAccessInput!); }); expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( @@ -165,7 +165,7 @@ describe("", () => { ); // toggled off - expect(guestAccessInput.getAttribute("aria-checked")).toEqual("false"); + expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("false"); }); it("renders error message when update fails", async () => { @@ -174,7 +174,7 @@ describe("", () => { const component = getComponent({ space }); await toggleGuestAccessSection(component); await act(async () => { - Simulate.click(getGuestAccessToggle(component)); + Simulate.click(getGuestAccessToggle(component)!); }); expect(getErrorMessage(component)).toEqual("Failed to update the guest access of this space"); @@ -187,7 +187,7 @@ describe("", () => { await toggleGuestAccessSection(component); - expect(getGuestAccessToggle(component).getAttribute("aria-disabled")).toEqual("true"); + expect(getGuestAccessToggle(component)?.getAttribute("aria-disabled")).toEqual("true"); }); }); @@ -197,7 +197,7 @@ describe("", () => { const component = getComponent({ space }); // toggle off because space settings is != WorldReadable - expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("false"); + expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false"); }); it("updates history visibility on toggle", async () => { @@ -205,10 +205,10 @@ describe("", () => { const component = getComponent({ space }); // toggle off because space settings is != WorldReadable - expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("false"); + expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false"); await act(async () => { - Simulate.click(getHistoryVisibilityToggle(component)); + Simulate.click(getHistoryVisibilityToggle(component)!); }); expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( @@ -218,7 +218,7 @@ describe("", () => { "", ); - expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("true"); + expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("true"); }); it("renders error message when history update fails", async () => { @@ -227,7 +227,7 @@ describe("", () => { const component = getComponent({ space }); await act(async () => { - Simulate.click(getHistoryVisibilityToggle(component)); + Simulate.click(getHistoryVisibilityToggle(component)!); }); expect(getErrorMessage(component)).toEqual("Failed to update the history visibility of this space"); @@ -237,7 +237,7 @@ describe("", () => { const space = makeMockSpace(mockMatrixClient, joinRule, guestRule, historyRule); (space.currentState.maySendStateEvent as jest.Mock).mockReturnValue(false); const component = getComponent({ space }); - expect(getHistoryVisibilityToggle(component).getAttribute("aria-disabled")).toEqual("true"); + expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-disabled")).toEqual("true"); }); }); diff --git a/test/i18n-test/languageHandler-test.tsx b/test/i18n-test/languageHandler-test.tsx index 7c2a092667..8c8b540231 100644 --- a/test/i18n-test/languageHandler-test.tsx +++ b/test/i18n-test/languageHandler-test.tsx @@ -34,7 +34,7 @@ describe("languageHandler", function () { const plurals = "and %(count)s others..."; const variableSub = "You are now ignoring %(userId)s"; - type TestCase = [string, string, Record, Record, TranslatedString]; + type TestCase = [string, string, Record, Record | undefined, TranslatedString]; const testCasesEn: TestCase[] = [ // description of the test case, translationString, variables, tags, expected result ["translates a basic string", basicString, {}, undefined, "Rooms"], @@ -82,7 +82,7 @@ describe("languageHandler", function () { ], ]; - let oldNodeEnv: string; + let oldNodeEnv: string | undefined; beforeAll(() => { oldNodeEnv = process.env.NODE_ENV; process.env.NODE_ENV = "test"; @@ -110,7 +110,7 @@ describe("languageHandler", function () { }); it.each(testCasesEn)("%s", (_d, translationString, variables, tags, result) => { - expect(_t(translationString, variables, tags)).toEqual(result); + expect(_t(translationString, variables, tags!)).toEqual(result); }); it("replacements in the wrong order", function () { @@ -168,25 +168,25 @@ describe("languageHandler", function () { describe("_t", () => { it("translated correctly when plural string exists for count", () => { - expect(_t(lvExistingPlural, { count: 1, filename: "test.txt" }, undefined)).toEqual( + expect(_t(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual( "Качване на test.txt и 1 друг", ); }); it.each(pluralCases)("%s", (_d, translationString, variables, tags, result) => { - expect(_t(translationString, variables, tags)).toEqual(result); + expect(_t(translationString, variables, tags!)).toEqual(result); }); }); describe("_tDom()", () => { it("translated correctly when plural string exists for count", () => { - expect(_tDom(lvExistingPlural, { count: 1, filename: "test.txt" }, undefined)).toEqual( + expect(_tDom(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual( "Качване на test.txt и 1 друг", ); }); it.each(pluralCases)( "%s and translates with fallback locale, attributes fallback locale", (_d, translationString, variables, tags, result) => { - expect(_tDom(translationString, variables, tags)).toEqual({result}); + expect(_tDom(translationString, variables, tags!)).toEqual({result}); }, ); }); @@ -197,7 +197,7 @@ describe("languageHandler", function () { it.each(testCasesEn)( "%s and translates with fallback locale", (_d, translationString, variables, tags, result) => { - expect(_t(translationString, variables, tags)).toEqual(result); + expect(_t(translationString, variables, tags!)).toEqual(result); }, ); }); @@ -206,7 +206,7 @@ describe("languageHandler", function () { it.each(testCasesEn)( "%s and translates with fallback locale, attributes fallback locale", (_d, translationString, variables, tags, result) => { - expect(_tDom(translationString, variables, tags)).toEqual({result}); + expect(_tDom(translationString, variables, tags!)).toEqual({result}); }, ); }); @@ -216,12 +216,12 @@ describe("languageHandler", function () { describe("when languages dont load", () => { it("_t", () => { const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary"; - expect(_t(STRING_NOT_IN_THE_DICTIONARY, {}, undefined)).toEqual(STRING_NOT_IN_THE_DICTIONARY); + expect(_t(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual(STRING_NOT_IN_THE_DICTIONARY); }); it("_tDom", () => { const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary"; - expect(_tDom(STRING_NOT_IN_THE_DICTIONARY, {}, undefined)).toEqual( + expect(_tDom(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual( {STRING_NOT_IN_THE_DICTIONARY}, ); }); diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index 17f4fbfc4a..e825e5b1a4 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -383,7 +383,7 @@ describe("JitsiCall", () => { await waitFor( () => expect( - room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), + room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(), ).toEqual({ devices: [client.getDeviceId()], expires_ts: now1 + call.STUCK_DEVICE_TIMEOUT_MS, @@ -396,7 +396,7 @@ describe("JitsiCall", () => { await waitFor( () => expect( - room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), + room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(), ).toEqual({ devices: [], expires_ts: now2 + call.STUCK_DEVICE_TIMEOUT_MS, @@ -495,7 +495,7 @@ describe("JitsiCall", () => { }); const expectDevices = (devices: IMyDevice[]) => expect( - room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), + room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(), ).toEqual({ expires_ts: expect.any(Number), devices: devices.map((d) => d.device_id), diff --git a/test/settings/watchers/ThemeWatcher-test.tsx b/test/settings/watchers/ThemeWatcher-test.tsx index d38c899587..48981a4038 100644 --- a/test/settings/watchers/ThemeWatcher-test.tsx +++ b/test/settings/watchers/ThemeWatcher-test.tsx @@ -37,12 +37,12 @@ function makeMatchMedia(values: any) { } return function matchMedia(query: string) { - return new FakeMediaQueryList(query); + return new FakeMediaQueryList(query) as unknown as MediaQueryList; }; } function makeGetValue(values: any) { - return function getValue(settingName: string, _roomId: string = null, _excludeDefault = false): T { + return function getValue(settingName: string, _roomId: string | null = null, _excludeDefault = false): T { return values[settingName]; }; } @@ -51,7 +51,7 @@ function makeGetValueAt(values: any) { return function getValueAt( _level: SettingLevel, settingName: string, - _roomId: string = null, + _roomId: string | null = null, _explicit = false, _excludeDefault = false, ): any {