Conform more code to strict null checking (#10153)

* Conform more code to strict null checking

* Conform more code to strict null checking

* Iterate

* Iterate
This commit is contained in:
Michael Telatynski 2023-02-15 13:36:22 +00:00 committed by GitHub
parent a4ff959aa1
commit 145a5a8a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 520 additions and 551 deletions

View File

@ -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 {

View File

@ -33,7 +33,7 @@ export interface IModal<T extends any[]> {
beforeClosePromise?: Promise<boolean>;
closeReason?: string;
onBeforeClose?(reason?: string): Promise<boolean>;
onFinished(...args: T): void;
onFinished?(...args: T): void;
close(...args: T): void;
hidden?: boolean;
}
@ -68,11 +68,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// The modal to prioritise over all others. If this is set, only show
// this modal. Remove all other modals from the stack when this modal
// is closed.
private priorityModal: IModal<any> = null;
private priorityModal: IModal<any> | 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<any> = null;
private staticModal: IModal<any> | 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<any>[] = [];
@ -144,17 +144,14 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
closeDialog: IHandle<T>["close"];
onFinishedProm: IHandle<T>["finished"];
} {
const modal: IModal<T> = {
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<T>;
// never call this from onFinished() otherwise it will loop
const [closeDialog, onFinishedProm] = this.getCloseFn<T>(modal, props);
@ -173,7 +170,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
private getCloseFn<T extends any[]>(
modal: IModal<T>,
props: IProps<T>,
props?: IProps<T>,
): [IHandle<T>["close"], IHandle<T>["finished"]] {
const deferred = defer<T>();
return [
@ -183,13 +180,13 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
} else if (modal.onBeforeClose) {
modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason);
const shouldClose = await modal.beforeClosePromise;
modal.beforeClosePromise = null;
modal.beforeClosePromise = undefined;
if (!shouldClose) {
return;
}
}
deferred.resolve(args);
if (props && props.onFinished) props.onFinished.apply(null, args);
if (props?.onFinished) props.onFinished.apply(null, args);
const i = this.modals.indexOf(modal);
if (i >= 0) {
this.modals.splice(i, 1);
@ -317,7 +314,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// so, pass the reason to close through a member variable
modal.closeReason = "backgroundClick";
modal.close();
modal.closeReason = null;
modal.closeReason = undefined;
};
private getCurrentModal(): IModal<any> {

View File

@ -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, (event: MatrixEvent) => string> = {
const msgTypeHandlers: Record<string, (event: MatrixEvent) => 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",

View File

@ -238,11 +238,11 @@ export class PosthogAnalytics {
}
}
private static async getPlatformProperties(): Promise<PlatformProperties> {
private static async getPlatformProperties(): Promise<Partial<PlatformProperties>> {
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);
}

View File

@ -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(
<div key={addr} className="mx_InviteDialog_tile mx_InviteDialog_tile--inviterError">
<div className="mx_InviteDialog_tile_avatarStack">
<BaseAvatar
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
name={name}
url={
(avatarUrl && mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24)) ??
undefined
}
name={name!}
idName={user?.userId}
width={36}
height={36}

View File

@ -32,8 +32,8 @@ const imApiVersion = "1.1";
// TODO: Generify the name of this class and all components within - it's not just for Scalar.
export default class ScalarAuthClient {
private scalarToken: string;
private termsInteractionCallback: TermsInteractionCallback;
private scalarToken: string | null;
private termsInteractionCallback?: TermsInteractionCallback;
private isDefaultManager: boolean;
public constructor(private apiUrl: string, private uiUrl: string) {
@ -59,7 +59,7 @@ export default class ScalarAuthClient {
}
}
private readTokenFromStore(): string {
private readTokenFromStore(): string | null {
let token = window.localStorage.getItem("mx_scalar_token_at_" + this.apiUrl);
if (!token && this.isDefaultManager) {
token = window.localStorage.getItem("mx_scalar_token");
@ -67,7 +67,7 @@ export default class ScalarAuthClient {
return token;
}
private readToken(): string {
private readToken(): string | null {
if (this.scalarToken) return this.scalarToken;
return this.readTokenFromStore();
}

View File

@ -358,7 +358,7 @@ function inviteUser(event: MessageEvent<any>, 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<any>, 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<any>, 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<any>, 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) {

View File

@ -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<ISearchResults> {
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<ISeshatSearchResults> {
async function localSearchProcess(searchTerm: string, roomId?: string): Promise<ISeshatSearchResults> {
const emptyResult = {
results: [],
highlights: [],
@ -244,7 +244,7 @@ async function localPagination(searchResult: ISeshatSearchResults): Promise<ISes
const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0));
restoreEncryptionInfo(newSlice);
searchResult.pendingRequest = null;
searchResult.pendingRequest = undefined;
return result;
}
@ -388,8 +388,8 @@ function combineEventSources(
*/
function combineEvents(
previousSearchResult: ISeshatSearchResults,
localEvents: IResultRoomEvents = undefined,
serverEvents: IResultRoomEvents = undefined,
localEvents?: IResultRoomEvents,
serverEvents?: IResultRoomEvents,
): IResultRoomEvents {
const response = {} as IResultRoomEvents;
@ -451,8 +451,8 @@ function combineEvents(
*/
function combineResponses(
previousSearchResult: ISeshatSearchResults,
localEvents: IResultRoomEvents = undefined,
serverEvents: IResultRoomEvents = undefined,
localEvents: IResultRoomEvents,
serverEvents: IResultRoomEvents,
): IResultRoomEvents {
// Combine our events first.
const response = combineEvents(previousSearchResult, localEvents, serverEvents);
@ -496,10 +496,10 @@ function combineResponses(
}
interface IEncryptedSeshatEvent {
curve25519Key: string;
ed25519Key: string;
algorithm: string;
forwardingCurve25519KeyChain: string[];
curve25519Key?: string;
ed25519Key?: string;
algorithm?: string;
forwardingCurve25519KeyChain?: string[];
}
function restoreEncryptionInfo(searchResultSlice: SearchResult[] = []): void {
@ -514,7 +514,7 @@ function restoreEncryptionInfo(searchResultSlice: SearchResult[] = []): void {
EventType.RoomMessageEncrypted,
{ algorithm: ev.algorithm },
ev.curve25519Key,
ev.ed25519Key,
ev.ed25519Key!,
);
// @ts-ignore
mxEv.forwardingCurve25519KeyChain = ev.forwardingCurve25519KeyChain;
@ -581,11 +581,7 @@ async function combinedPagination(searchResult: ISeshatSearchResults): Promise<I
return result;
}
function eventIndexSearch(
term: string,
roomId: string = undefined,
abortSignal?: AbortSignal,
): Promise<ISearchResults> {
function eventIndexSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise<ISearchResults> {
let searchPromise: Promise<ISearchResults>;
if (roomId !== undefined) {
@ -643,11 +639,7 @@ export function searchPagination(searchResult: ISearchResults): Promise<ISearchR
else return eventIndexSearchPagination(searchResult);
}
export default function eventSearch(
term: string,
roomId: string = undefined,
abortSignal?: AbortSignal,
): Promise<ISearchResults> {
export default function eventSearch(term: string, roomId?: string, abortSignal?: AbortSignal): Promise<ISearchResults> {
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) {

View File

@ -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<string> {
): Promise<string | undefined> {
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<Uint8Array> {
let key: Uint8Array;
let key!: Uint8Array;
const { finished } = Modal.createDialog(
RestoreKeyBackupDialog,
@ -292,7 +292,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
showSummary: false,
keyCallback: (k: Uint8Array) => (key = k),
},
null,
undefined,
/* priority = */ false,
/* static = */ true,
);
@ -338,7 +338,7 @@ export async function accessSecretStorage(func = async (): Promise<void> => {},
{
forceReset,
},
null,
undefined,
/* priority = */ false,
/* static = */ true,
/* options = */ {

View File

@ -81,7 +81,7 @@ const singleMxcUpload = async (): Promise<string | null> => {
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<IContent | undefined> }>;
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: "[<mxc_url>]",
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<MRoomTopicEventContent>();
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<ViewUserPayload>({
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,

View File

@ -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 = {

View File

@ -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<ISendMessageComposerPro
private readonly prepareToEncrypt?: DebouncedFunc<() => void>;
private readonly editorRef = createRef<BasicMessageComposer>();
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<ISendMessageComposerPro
const lastMessage = events[i];
const userId = MatrixClientPeg.get().getUserId();
const messageReactions = this.props.room.relations.getChildEventsForEvent(
lastMessage.getId(),
lastMessage.getId()!,
RelationType.Annotation,
EventType.Reaction,
);
@ -314,7 +314,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
shouldReact = !myReactionKeys.includes(reaction);
}
if (shouldReact) {
MatrixClientPeg.get().sendEvent(lastMessage.getRoomId(), EventType.Reaction, {
MatrixClientPeg.get().sendEvent(lastMessage.getRoomId()!, EventType.Reaction, {
"m.relates_to": {
rel_type: RelationType.Annotation,
event_id: lastMessage.getId(),
@ -359,7 +359,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
const replyToEvent = this.props.replyToEvent;
let shouldSend = true;
let content: IContent;
let content: IContent | null = null;
if (!containsEmote(model) && isSlashCommand(this.model)) {
const [cmd, args, commandText] = getSlashCommand(this.model);
@ -481,7 +481,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
localStorage.removeItem(this.editorStateKey);
}
private restoreStoredEditorState(partCreator: PartCreator): Part[] {
private restoreStoredEditorState(partCreator: PartCreator): Part[] | null {
const replyingToThread = this.props.relation?.key === THREAD_RELATION_TYPE.name;
if (replyingToThread) {
return null;
@ -504,6 +504,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
logger.error(e);
}
}
return null;
}
// should save state when editor has contents or reply is open
@ -563,6 +565,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
);
return true; // to skip internal onPaste handler
}
return false;
};
private onChange = (): void => {
@ -575,7 +579,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
public render(): React.ReactNode {
const threadId =
this.props.relation?.rel_type === THREAD_RELATION_TYPE.name ? this.props.relation.event_id : null;
this.props.relation?.rel_type === THREAD_RELATION_TYPE.name ? this.props.relation.event_id : undefined;
return (
<div className="mx_SendMessageComposer" onClick={this.focusComposer} onKeyDown={this.onKeyDown}>
<BasicMessageComposer

View File

@ -90,7 +90,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
Modal.createDialog(
LogoutDialog,
/* props= */ {},
/* className= */ null,
/* className= */ undefined,
/* isPriority= */ false,
/* isStatic= */ true,
);

View File

@ -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;

View File

@ -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 <span> 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<void> {
return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName);
})
.then(async (langData): Promise<ICounterpartTranslation> => {
.then(async (langData): Promise<ICounterpartTranslation | undefined> => {
counterpart.registerTranslations(langToUse, langData);
await registerCustomTranslations();
counterpart.setLocale(langToUse);
@ -664,7 +664,7 @@ export async function registerCustomTranslations(): Promise<void> {
if (!lookupUrl) return; // easy - nothing to do
try {
let json: ICustomTranslations;
let json: Optional<ICustomTranslations>;
if (Date.now() >= cachedCustomTranslationsExpire) {
json = CustomTranslationOptions.lookupFn
? CustomTranslationOptions.lookupFn(lookupUrl)

View File

@ -871,7 +871,7 @@ export class ElementCall extends Call {
private onScreenshareRequest = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
ev.preventDefault();
if (PlatformPeg.get().supportsDesktopCapturer()) {
if (PlatformPeg.get()?.supportsDesktopCapturer()) {
await this.messaging!.transport.reply(ev.detail, { pending: true });
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -81,8 +81,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
}
if (client) {
body.append("user_id", client.credentials.userId);
body.append("device_id", client.deviceId);
body.append("user_id", client.credentials.userId!);
body.append("device_id", client.deviceId!);
// TODO: make this work with rust crypto
if (client.isCryptoEnabled() && client.crypto) {
@ -181,7 +181,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
}
}
body.append("mx_local_settings", localStorage.getItem("mx_local_settings"));
body.append("mx_local_settings", localStorage.getItem("mx_local_settings")!);
if (opts.sendLogs) {
progressCallback(_t("Collecting logs"));
@ -293,9 +293,9 @@ export async function submitFeedback(
canContact = false,
extraData: Record<string, string> = {},
): Promise<void> {
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());

View File

@ -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!;
}
}

View File

@ -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<string, WatchCallbackFn>();
private static monitors = new Map<string, Map<string, string>>(); // { settingName => { roomId => callbackRef } }
private static monitors = new Map<string, Map<string | null, string>>(); // { 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 (<IFeature>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 {

View File

@ -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<string, Map<string | symbol, CallbackFn[]>>(); // settingName -> roomId -> CallbackFn[]
private watchers = new Map<string, Map<string | null, CallbackFn[]>>(); // 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) {

View File

@ -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();
}
}

View File

@ -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<boolean> {
public async beforeChange(level: SettingLevel, roomId: string | null, newValue: any): Promise<boolean> {
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
}

View File

@ -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<string, string>();
private static itemCache = new Map<string, string | null>();
private static objectCache = new Map<string, object>();
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<T extends object>(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) {

View File

@ -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<void> {
public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
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;
}
}

View File

@ -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 {

View File

@ -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<void>;
public abstract setValue(settingName: string, roomId: string | null, newValue: any): Promise<void>;
/**
* 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.

View File

@ -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<HTMLElement>(":root").style.fontSize = toPx(fontSize);
document.querySelector<HTMLElement>(":root")!.style.fontSize = toPx(fontSize);
};
private setSystemFont = ({

View File

@ -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 => {

View File

@ -92,7 +92,7 @@ export abstract class AsyncStore<T extends Object> 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<void> {
protected async reset(newState: T | Object | null = null, quiet = false): Promise<void> {
await this.lock.acquireAsync();
try {
this.storeState = Object.freeze(<T>(newState || {}));

View File

@ -19,7 +19,7 @@ import { AsyncStore } from "./AsyncStore";
import { ActionPayload } from "../dispatcher/payloads";
interface IState {
hostSignupActive?: boolean;
hostSignupActive: boolean;
}
export class HostSignupStore extends AsyncStore<IState> {

View File

@ -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<ActionPayload> {
}
}
let singletonLifecycleStore = null;
let singletonLifecycleStore: LifecycleStore | null = null;
if (!singletonLifecycleStore) {
singletonLifecycleStore = new LifecycleStore();
}
export default singletonLifecycleStore;
export default singletonLifecycleStore!;

View File

@ -110,15 +110,15 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* 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<OwnBeaconStoreState> {
*/
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<OwnBeaconStoreState> {
* 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<OwnBeaconStoreState> {
}
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<OwnBeaconStoreState> {
this.beaconsByRoomId.set(beacon.roomId, new Set<string>());
}
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<OwnBeaconStoreState> {
if (!this.beacons.has(beaconId)) {
return;
}
this.beacons.get(beaconId).destroy();
this.beacons.get(beaconId)!.destroy();
this.beacons.delete(beaconId);
this.checkLiveness();

View File

@ -43,7 +43,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
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<IState> {
/**
* 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<IState> {
/**
* 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<IState> {
* 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<IState> {
}
protected async onReady(): Promise<void> {
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<IState> {
async (): Promise<void> => {
// 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 {

View File

@ -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<void> {

View File

@ -104,9 +104,9 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
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<IState> {
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);

View File

@ -37,7 +37,7 @@ export abstract class EchoContext extends Whenable<ContextTransactionState> 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;

View File

@ -28,19 +28,19 @@ export const PROPERTY_UPDATED = "property_updated";
export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends EventEmitter {
private cache = new Map<K, { txn: EchoTransaction; val: V }>();
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<C extends EchoContext, K, V> 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<C extends EchoContext, K, V> 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<C extends EchoContext, K, V> 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<C extends EchoContext, K, V> 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);

View File

@ -34,7 +34,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
super(context, (k) => 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<RoomEchoContext, CachedR
};
private updateNotificationVolume(): void {
const state = getRoomNotifsState(this.matrixClient, this.context.room.roomId);
const state = this.matrixClient ? getRoomNotifsState(this.matrixClient, this.context.room.roomId) : null;
if (state) this.properties.set(CachedRoomKey.NotificationVolume, state);
else this.properties.delete(CachedRoomKey.NotificationVolume);
this.markEchoReceived(CachedRoomKey.NotificationVolume);

View File

@ -62,7 +62,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
*/
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<IState> {
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();

View File

@ -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<any> {
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<any> {
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 });
}

View File

@ -123,7 +123,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
* @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<string> {
public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string | null> {
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<IState> {
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<void> {
@ -171,7 +171,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
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<IState> {
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);

View File

@ -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) {

View File

@ -29,7 +29,7 @@ export class PollStartEventPreview implements IPreview {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
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 });

View File

@ -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 });

View File

@ -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<IState> {
return this.rootSpaces;
}
public get activeSpace(): SpaceKey {
public get activeSpace(): SpaceKey | undefined {
return this._activeSpace;
}
@ -228,7 +228,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
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<IState> {
// 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<IState> {
.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<IState> {
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<IState> {
return parent;
})
.filter(Boolean) || []
.filter(Boolean) as Room[]) || []
);
}
@ -467,7 +468,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
space: SpaceKey,
includeDescendantSpaces = true,
useCache = true,
): Set<string> => {
): Set<string> | undefined => {
if (space === MetaSpace.Home && this.allRoomsInHome) {
return undefined;
}
@ -490,7 +491,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private markTreeChildren = (rootSpace: Room, unseen: Set<Room>): 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<IState> {
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<IState> {
); // 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<IState> {
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<string>): [Set<string>, Set<string>] => {
const traverseSpace = (
spaceId: string,
parentPath: Set<string>,
): [Set<string>, Set<string>] | 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<IState> {
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) {

View File

@ -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 ||

View File

@ -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<string> {
// 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, <IWidgetApiErrorResponseData>{
return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
error: { message: "Room ID not supplied." },
});
}
// Check the widget's permission
if (!this.messaging.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) {
return this.messaging.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
if (!this.messaging?.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) {
return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
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<IStickyActionRequest>) => {
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<IStickerActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) {
if (this.messaging?.hasCapability(MatrixCapabilities.StickerSending)) {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
@ -381,7 +382,7 @@ export class StopGapWidget extends EventEmitter {
(ev: CustomEvent<IWidgetApiRequest>) => {
// Acknowledge first
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
// First close the stickerpicker
defaultDispatcher.dispatch({ action: "stickerpicker_close" });
@ -415,7 +416,7 @@ export class StopGapWidget extends EventEmitter {
}),
});
}
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
});
}
}
@ -478,7 +479,7 @@ export class StopGapWidget extends EventEmitter {
private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
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) {

View File

@ -131,7 +131,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
protected async onReady(): Promise<void> {
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<ILayoutSettings>("Widgets.layout", room.roomId);
let userLayout = SettingsStore.getValue<ILayoutSettings | null>("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<ILayoutStateEvent>() ?? 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]);

View File

@ -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();
}

View File

@ -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}")`;
}

View File

@ -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<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: room.roomId,
room_id: room?.roomId,
view_call: true,
metricsTrigger: undefined,
});

View File

@ -35,7 +35,7 @@ import { _t, _td, Tags, TranslatedString } from "../languageHandler";
*/
export function messageForResourceLimitError(
limitType: string,
adminContact: string,
adminContact: string | undefined,
strings: Record<string, string>,
extraTranslations?: Tags,
): TranslatedString {
@ -57,7 +57,7 @@ export function messageForResourceLimitError(
if (errString.includes("<a>")) {
return _t(errString, {}, Object.assign({ a: linkSub }, extraTranslations));
} else {
return _t(errString, {}, extraTranslations);
return _t(errString, {}, extraTranslations!);
}
}

View File

@ -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;

View File

@ -69,7 +69,7 @@ async function isColrFontSupported(): Promise<boolean> {
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 =

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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<void>;

View File

@ -217,10 +217,10 @@ export default class HTMLExporter extends Exporter {
</html>`;
}
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<void> {
@ -386,7 +386,7 @@ export default class HTMLExporter extends Exporter {
protected async createHTML(events: MatrixEvent[], start: number): Promise<string> {
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(

View File

@ -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,

View File

@ -45,7 +45,7 @@ async function getRulesFromCssFile(path: string): Promise<CSSStyleSheet> {
// 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,

View File

@ -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<Blob>((resolve) => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
thumbnailPromise = new Promise<Blob>((resolve) =>
(canvas as HTMLCanvasElement).toBlob(resolve as BlobCallback, mimeType),
);
}
const imageData = context.getImageData(0, 0, targetWidth, targetHeight);

View File

@ -345,7 +345,7 @@ describe("Notifier", () => {
tweaks: {},
});
Notifier.start();
Notifier.onSyncStateChange(SyncState.Syncing);
Notifier.onSyncStateChange(SyncState.Syncing, null);
});
afterEach(() => {

View File

@ -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", () => {

View File

@ -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;
}

View File

@ -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:");
});
});

View File

@ -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);
});
});

View File

@ -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();
});
});

View File

@ -111,7 +111,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
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("<LeftPanelLiveShareWarning />", () => {
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("<LeftPanelLiveShareWarning />", () => {
]);
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("<LeftPanelLiveShareWarning />", () => {
rerender(<LeftPanelLiveShareWarning />);
// 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("<LeftPanelLiveShareWarning />", () => {
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,

View File

@ -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(
<ForwardDialog
matrixClient={mockClient}
event={message}
permalinkCreator={new RoomPermalinkCreator(undefined, sourceRoom)}
permalinkCreator={new RoomPermalinkCreator(undefined!, sourceRoom)}
onFinished={jest.fn()}
/>,
);
@ -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<number>(modernLocationEvent.getContent());
const timestamp = M_TIMESTAMP.findIn<number>(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,

View File

@ -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(<SpotlightDialog initialFilter={null} onFinished={() => null} />);
const wrapper = mount(<SpotlightDialog onFinished={() => null} />);
await act(async () => {
await sleep(200);
});

View File

@ -61,7 +61,7 @@ describe("<StyledRadioGroup />", () => {
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("<StyledRadioGroup />", () => {
onChange,
});
fireEvent.click(getInputByValue(component, optionB.value));
fireEvent.click(getInputByValue(component, optionB.value)!);
expect(onChange).toHaveBeenCalledWith(optionB.value);
});

View File

@ -62,7 +62,7 @@ describe("<TooltipTarget />", () => {
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("<TooltipTarget />", () => {
});
it("hides tooltip on mouseleave", () => {
const wrapper = getComponent();
const wrapper = getComponent()!;
act(() => {
Simulate.mouseOver(wrapper);
});
@ -82,7 +82,7 @@ describe("<TooltipTarget />", () => {
});
it("displays tooltip on focus", () => {
const wrapper = getComponent();
const wrapper = getComponent()!;
act(() => {
Simulate.focus(wrapper);
});
@ -90,7 +90,7 @@ describe("<TooltipTarget />", () => {
});
it("hides tooltip on blur", async () => {
const wrapper = getComponent();
const wrapper = getComponent()!;
act(() => {
Simulate.focus(wrapper);
});

View File

@ -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", () => {

View File

@ -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);
});

View File

@ -236,9 +236,7 @@ describe("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
} 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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
);
//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("<MessageActionBar />", () => {
);
//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("<MessageActionBar />", () => {
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("<MessageActionBar />", () => {
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();
});
});

View File

@ -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;
});

View File

@ -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);
});

View File

@ -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));
}

View File

@ -88,7 +88,7 @@ describe("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
// 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("<SendMessageComposer/>", () => {
// 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("<SendMessageComposer/>", () => {
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("<SendMessageComposer/>", () => {
});
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("<SendMessageComposer/>", () => {
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",

View File

@ -310,11 +310,8 @@ describe("<Notifications />", () => {
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("<Notifications />", () => {
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("<Notifications />", () => {
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("<Notifications />", () => {
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("<Notifications />", () => {
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);
});

View File

@ -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,

View File

@ -94,13 +94,13 @@ describe("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
);
// 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("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
await toggleGuestAccessSection(component);
expect(getGuestAccessToggle(component).getAttribute("aria-disabled")).toEqual("true");
expect(getGuestAccessToggle(component)?.getAttribute("aria-disabled")).toEqual("true");
});
});
@ -197,7 +197,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
"",
);
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("<SpaceSettingsVisibilityTab />", () => {
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("<SpaceSettingsVisibilityTab />", () => {
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");
});
});

View File

@ -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<string, unknown>, Record<string, unknown>, TranslatedString];
type TestCase = [string, string, Record<string, unknown>, Record<string, unknown> | 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(<span lang="en">{result}</span>);
expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
},
);
});
@ -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(<span lang="en">{result}</span>);
expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
},
);
});
@ -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(
<span lang="en">{STRING_NOT_IN_THE_DICTIONARY}</span>,
);
});

View File

@ -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),

View File

@ -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<T = any>(settingName: string, _roomId: string = null, _excludeDefault = false): T {
return function getValue<T = any>(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 {