Conform more code to strict null checking (#10167)

* Conform more code to strict null checking

* Delint

* Iterate PR based on feedback
This commit is contained in:
Michael Telatynski 2023-02-16 17:21:44 +00:00 committed by GitHub
parent f7bea2cae5
commit 4574c665ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
103 changed files with 517 additions and 495 deletions

View file

@ -89,7 +89,7 @@ async function loadImageElement(imageFile: File): Promise<{
// check for hi-dpi PNGs and fudge display resolution as needed. // check for hi-dpi PNGs and fudge display resolution as needed.
// this is mainly needed for macOS screencaps // this is mainly needed for macOS screencaps
let parsePromise: Promise<boolean>; let parsePromise = Promise.resolve(false);
if (imageFile.type === "image/png") { if (imageFile.type === "image/png") {
// in practice macOS happens to order the chunks so they fall in // in practice macOS happens to order the chunks so they fall in
// the first 0x1000 bytes (thanks to a massive ICC header). // the first 0x1000 bytes (thanks to a massive ICC header).
@ -101,7 +101,7 @@ async function loadImageElement(imageFile: File): Promise<{
const chunks = extractPngChunks(buffer); const chunks = extractPngChunks(buffer);
for (const chunk of chunks) { for (const chunk of chunks) {
if (chunk.name === "pHYs") { if (chunk.name === "pHYs") {
if (chunk.data.byteLength !== PHYS_HIDPI.length) return; if (chunk.data.byteLength !== PHYS_HIDPI.length) return false;
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]); return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
} }
} }
@ -199,10 +199,10 @@ function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
reject(e); reject(e);
}; };
let dataUrl = ev.target.result as string; let dataUrl = ev.target?.result as string;
// Chrome chokes on quicktime but likes mp4, and `file.type` is // Chrome chokes on quicktime but likes mp4, and `file.type` is
// read only, so do this horrible hack to unbreak quicktime // read only, so do this horrible hack to unbreak quicktime
if (dataUrl.startsWith("data:video/quicktime;")) { if (dataUrl?.startsWith("data:video/quicktime;")) {
dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;"); dataUrl = dataUrl.replace("data:video/quicktime;", "data:video/mp4;");
} }
@ -258,7 +258,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = function (e): void { reader.onload = function (e): void {
resolve(e.target.result as ArrayBuffer); resolve(e.target?.result as ArrayBuffer);
}; };
reader.onerror = function (e): void { reader.onerror = function (e): void {
reject(e); reject(e);
@ -329,7 +329,7 @@ export async function uploadFile(
export default class ContentMessages { export default class ContentMessages {
private inprogress: RoomUpload[] = []; private inprogress: RoomUpload[] = [];
private mediaConfig: IMediaConfig = null; private mediaConfig: IMediaConfig | null = null;
public sendStickerContentToRoom( public sendStickerContentToRoom(
url: string, url: string,
@ -377,8 +377,8 @@ export default class ContentMessages {
modal.close(); modal.close();
} }
const tooBigFiles = []; const tooBigFiles: File[] = [];
const okFiles = []; const okFiles: File[] = [];
for (const file of files) { for (const file of files) {
if (this.isFileSizeAcceptable(file)) { if (this.isFileSizeAcceptable(file)) {
@ -420,7 +420,14 @@ export default class ContentMessages {
} }
promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) => promBefore = doMaybeLocalRoomAction(roomId, (actualRoomId) =>
this.sendContentToRoom(file, actualRoomId, relation, matrixClient, replyToEvent, loopPromiseBefore), this.sendContentToRoom(
file,
actualRoomId,
relation,
matrixClient,
replyToEvent ?? undefined,
loopPromiseBefore,
),
); );
} }
@ -584,7 +591,7 @@ export default class ContentMessages {
} }
private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise<void> { private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise<void> {
if (this.mediaConfig !== null) return; if (this.mediaConfig !== null) return Promise.resolve();
logger.log("[Media Config] Fetching"); logger.log("[Media Config] Fetching");
return matrixClient return matrixClient

View file

@ -138,7 +138,7 @@ export class DecryptionFailureTracker {
return; return;
} }
if (err) { if (err) {
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code)); this.addDecryptionFailure(new DecryptionFailure(e.getId()!, err.code));
} else { } else {
// Could be an event in the failures, remove it // Could be an event in the failures, remove it
this.removeDecryptionFailuresForEvent(e); this.removeDecryptionFailuresForEvent(e);
@ -146,7 +146,7 @@ export class DecryptionFailureTracker {
} }
public addVisibleEvent(e: MatrixEvent): void { public addVisibleEvent(e: MatrixEvent): void {
const eventId = e.getId(); const eventId = e.getId()!;
if (this.trackedEvents.has(eventId)) { if (this.trackedEvents.has(eventId)) {
return; return;
@ -154,7 +154,7 @@ export class DecryptionFailureTracker {
this.visibleEvents.add(eventId); this.visibleEvents.add(eventId);
if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) { if (this.failures.has(eventId) && !this.visibleFailures.has(eventId)) {
this.visibleFailures.set(eventId, this.failures.get(eventId)); this.visibleFailures.set(eventId, this.failures.get(eventId)!);
} }
} }
@ -172,7 +172,7 @@ export class DecryptionFailureTracker {
} }
public removeDecryptionFailuresForEvent(e: MatrixEvent): void { public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
const eventId = e.getId(); const eventId = e.getId()!;
this.failures.delete(eventId); this.failures.delete(eventId);
this.visibleFailures.delete(eventId); this.visibleFailures.delete(eventId);
} }
@ -193,8 +193,8 @@ export class DecryptionFailureTracker {
* Clear state and stop checking for and tracking failures. * Clear state and stop checking for and tracking failures.
*/ */
public stop(): void { public stop(): void {
clearInterval(this.checkInterval); if (this.checkInterval) clearInterval(this.checkInterval);
clearInterval(this.trackInterval); if (this.trackInterval) clearInterval(this.trackInterval);
this.failures = new Map(); this.failures = new Map();
this.visibleEvents = new Set(); this.visibleEvents = new Set();

View file

@ -51,7 +51,7 @@ import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulk
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
export default class DeviceListener { export default class DeviceListener {
private dispatcherRef: string; private dispatcherRef: string | null;
// device IDs for which the user has dismissed the verify toast ('Later') // device IDs for which the user has dismissed the verify toast ('Later')
private dismissed = new Set<string>(); private dismissed = new Set<string>();
// has the user dismissed any of the various nag toasts to setup encryption on this device? // has the user dismissed any of the various nag toasts to setup encryption on this device?
@ -152,7 +152,7 @@ export default class DeviceListener {
private ensureDeviceIdsAtStartPopulated(): void { private ensureDeviceIdsAtStartPopulated(): void {
if (this.ourDeviceIdsAtStart === null) { if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()).map((d) => d.deviceId)); this.ourDeviceIdsAtStart = new Set(cli.getStoredDevicesForUser(cli.getUserId()!).map((d) => d.deviceId));
} }
} }
@ -162,7 +162,7 @@ export default class DeviceListener {
// devicesAtStart list to the devices that we see after the fetch. // devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return; if (initialFetch) return;
const myUserId = MatrixClientPeg.get().getUserId(); const myUserId = MatrixClientPeg.get().getUserId()!;
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated(); if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices // No need to do a recheck here: we just need to get a snapshot of our devices
@ -170,7 +170,7 @@ export default class DeviceListener {
}; };
private onDevicesUpdated = (users: string[]): void => { private onDevicesUpdated = (users: string[]): void => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return; if (!users.includes(MatrixClientPeg.get().getUserId()!)) return;
this.recheck(); this.recheck();
}; };
@ -225,7 +225,7 @@ export default class DeviceListener {
// The server doesn't tell us when key backup is set up, so we poll // The server doesn't tell us when key backup is set up, so we poll
// & cache the result // & cache the result
private async getKeyBackupInfo(): Promise<IKeyBackupInfo> { private async getKeyBackupInfo(): Promise<IKeyBackupInfo | null> {
const now = new Date().getTime(); const now = new Date().getTime();
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
@ -265,10 +265,10 @@ export default class DeviceListener {
this.checkKeyBackupStatus(); this.checkKeyBackupStatus();
} else if (this.shouldShowSetupEncryptionToast()) { } else if (this.shouldShowSetupEncryptionToast()) {
// make sure our keys are finished downloading // make sure our keys are finished downloading
await cli.downloadKeys([cli.getUserId()]); await cli.downloadKeys([cli.getUserId()!]);
// cross signing isn't enabled - nag to enable it // cross signing isn't enabled - nag to enable it
// There are 3 different toasts for: // There are 3 different toasts for:
if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId())) { if (!cli.getCrossSigningId() && cli.getStoredCrossSigningForUser(cli.getUserId()!)) {
// Cross-signing on account but this device doesn't trust the master key (verify this session) // Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus(); this.checkKeyBackupStatus();
@ -310,13 +310,13 @@ export default class DeviceListener {
// as long as cross-signing isn't ready, // as long as cross-signing isn't ready,
// you can't see or dismiss any device toasts // you can't see or dismiss any device toasts
if (crossSigningReady) { if (crossSigningReady) {
const devices = cli.getStoredDevicesForUser(cli.getUserId()); const devices = cli.getStoredDevicesForUser(cli.getUserId()!);
for (const device of devices) { for (const device of devices) {
if (device.deviceId === cli.deviceId) continue; if (device.deviceId === cli.deviceId) continue;
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!); const deviceTrust = await cli.checkDeviceTrust(cli.getUserId()!, device.deviceId!);
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) { if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
if (this.ourDeviceIdsAtStart.has(device.deviceId)) { if (this.ourDeviceIdsAtStart?.has(device.deviceId)) {
oldUnverifiedDeviceIds.add(device.deviceId); oldUnverifiedDeviceIds.add(device.deviceId);
} else { } else {
newUnverifiedDeviceIds.add(device.deviceId); newUnverifiedDeviceIds.add(device.deviceId);

View file

@ -67,7 +67,7 @@ export default class IdentityAuthClient {
window.localStorage.setItem("mx_is_access_token", this.accessToken); window.localStorage.setItem("mx_is_access_token", this.accessToken);
} }
private readToken(): string { private readToken(): string | null {
if (this.tempClient) return null; // temporary client: ignore if (this.tempClient) return null; // temporary client: ignore
return window.localStorage.getItem("mx_is_access_token"); return window.localStorage.getItem("mx_is_access_token");
} }
@ -77,13 +77,13 @@ export default class IdentityAuthClient {
} }
// Returns a promise that resolves to the access_token string from the IS // Returns a promise that resolves to the access_token string from the IS
public async getAccessToken({ check = true } = {}): Promise<string> { public async getAccessToken({ check = true } = {}): Promise<string | null> {
if (!this.authEnabled) { if (!this.authEnabled) {
// The current IS doesn't support authentication // The current IS doesn't support authentication
return null; return null;
} }
let token = this.accessToken; let token: string | null = this.accessToken;
if (!token) { if (!token) {
token = this.readToken(); token = this.readToken();
} }

View file

@ -181,7 +181,7 @@ export default class LegacyCallHandler extends EventEmitter {
* Gets the user-facing room associated with a call (call.roomId may be the call "virtual room" * Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
* if a voip_mxid_translate_pattern is set in the config) * if a voip_mxid_translate_pattern is set in the config)
*/ */
public roomIdForCall(call: MatrixCall): string { public roomIdForCall(call?: MatrixCall): string | null {
if (!call) return null; if (!call) return null;
// check asserted identity: if we're not obeying asserted identity, // check asserted identity: if we're not obeying asserted identity,
@ -194,7 +194,7 @@ export default class LegacyCallHandler extends EventEmitter {
} }
} }
return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId; return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) ?? call.roomId ?? null;
} }
public start(): void { public start(): void {
@ -282,7 +282,7 @@ export default class LegacyCallHandler extends EventEmitter {
} }
public unSilenceCall(callId: string): void { public unSilenceCall(callId: string): void {
if (this.isForcedSilent) return; if (this.isForcedSilent()) return;
this.silencedCalls.delete(callId); this.silencedCalls.delete(callId);
this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls); this.emit(LegacyCallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring); this.play(AudioID.Ring);
@ -341,14 +341,14 @@ export default class LegacyCallHandler extends EventEmitter {
} }
private shouldObeyAssertedfIdentity(): boolean { private shouldObeyAssertedfIdentity(): boolean {
return SdkConfig.getObject("voip")?.get("obey_asserted_identity"); return !!SdkConfig.getObject("voip")?.get("obey_asserted_identity");
} }
public getSupportsPstnProtocol(): boolean { public getSupportsPstnProtocol(): boolean | null {
return this.supportsPstnProtocol; return this.supportsPstnProtocol;
} }
public getSupportsVirtualRooms(): boolean { public getSupportsVirtualRooms(): boolean | null {
return this.supportsSipNativeVirtual; return this.supportsSipNativeVirtual;
} }
@ -414,7 +414,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.prepareToEncrypt(cli.getRoom(call.roomId)); cli.prepareToEncrypt(cli.getRoom(call.roomId));
}; };
public getCallById(callId: string): MatrixCall { public getCallById(callId: string): MatrixCall | null {
for (const call of this.calls.values()) { for (const call of this.calls.values()) {
if (call.callId === callId) return call; if (call.callId === callId) return call;
} }
@ -435,7 +435,7 @@ export default class LegacyCallHandler extends EventEmitter {
} }
public getAllActiveCalls(): MatrixCall[] { public getAllActiveCalls(): MatrixCall[] {
const activeCalls = []; const activeCalls: MatrixCall[] = [];
for (const call of this.calls.values()) { for (const call of this.calls.values()) {
if (call.state !== CallState.Ended && call.state !== CallState.Ringing) { if (call.state !== CallState.Ended && call.state !== CallState.Ringing) {
@ -446,7 +446,7 @@ export default class LegacyCallHandler extends EventEmitter {
} }
public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] { public getAllActiveCallsNotInRoom(notInThisRoomId: string): MatrixCall[] {
const callsNotInThatRoom = []; const callsNotInThatRoom: MatrixCall[] = [];
for (const [roomId, call] of this.calls.entries()) { for (const [roomId, call] of this.calls.entries()) {
if (roomId !== notInThisRoomId && call.state !== CallState.Ended) { if (roomId !== notInThisRoomId && call.state !== CallState.Ended) {
@ -547,7 +547,7 @@ export default class LegacyCallHandler extends EventEmitter {
const mappedRoomId = this.roomIdForCall(call); const mappedRoomId = this.roomIdForCall(call);
const callForThisRoom = this.getCallForRoom(mappedRoomId); const callForThisRoom = this.getCallForRoom(mappedRoomId);
return callForThisRoom && call.callId === callForThisRoom.callId; return !!callForThisRoom && call.callId === callForThisRoom.callId;
} }
private setCallListeners(call: MatrixCall): void { private setCallListeners(call: MatrixCall): void {
@ -610,7 +610,7 @@ export default class LegacyCallHandler extends EventEmitter {
return; return;
} }
const newAssertedIdentity = call.getRemoteAssertedIdentity().id; const newAssertedIdentity = call.getRemoteAssertedIdentity()?.id;
let newNativeAssertedIdentity = newAssertedIdentity; let newNativeAssertedIdentity = newAssertedIdentity;
if (newAssertedIdentity) { if (newAssertedIdentity) {
const response = await this.sipNativeLookup(newAssertedIdentity); const response = await this.sipNativeLookup(newAssertedIdentity);
@ -642,7 +642,7 @@ export default class LegacyCallHandler extends EventEmitter {
}); });
} }
private onCallStateChanged = (newState: CallState, oldState: CallState, call: MatrixCall): void => { private onCallStateChanged = (newState: CallState, oldState: CallState | null, call: MatrixCall): void => {
if (!this.matchesCallForThisRoom(call)) return; if (!this.matchesCallForThisRoom(call)) return;
const mappedRoomId = this.roomIdForCall(call); const mappedRoomId = this.roomIdForCall(call);
@ -830,7 +830,7 @@ export default class LegacyCallHandler extends EventEmitter {
"<code>turn.matrix.org</code>, but this will not be as reliable, and " + "<code>turn.matrix.org</code>, but this will not be as reliable, and " +
"it will share your IP address with that server. You can also manage " + "it will share your IP address with that server. You can also manage " +
"this in Settings.", "this in Settings.",
null, undefined,
{ code }, { code },
)} )}
</p> </p>
@ -843,7 +843,7 @@ export default class LegacyCallHandler extends EventEmitter {
cli.setFallbackICEServerAllowed(allow); cli.setFallbackICEServerAllowed(allow);
}, },
}, },
null, undefined,
true, true,
); );
} }
@ -882,7 +882,7 @@ export default class LegacyCallHandler extends EventEmitter {
title, title,
description, description,
}, },
null, undefined,
true, true,
); );
} }

View file

@ -29,20 +29,17 @@ interface ILoginOptions {
} }
export default class Login { export default class Login {
private hsUrl: string; private flows: Array<LoginFlow> = [];
private isUrl: string; private readonly defaultDeviceDisplayName?: string;
private fallbackHsUrl: string; private tempClient: MatrixClient | null = null; // memoize
private flows: Array<LoginFlow>;
private defaultDeviceDisplayName: string;
private tempClient: MatrixClient;
public constructor(hsUrl: string, isUrl: string, fallbackHsUrl?: string, opts?: ILoginOptions) { public constructor(
this.hsUrl = hsUrl; private hsUrl: string,
this.isUrl = isUrl; private isUrl: string,
this.fallbackHsUrl = fallbackHsUrl; private fallbackHsUrl: string | null,
this.flows = []; opts: ILoginOptions,
) {
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName; this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
this.tempClient = null; // memoize
} }
public getHomeserverUrl(): string { public getHomeserverUrl(): string {
@ -96,7 +93,7 @@ export default class Login {
phoneNumber: string | undefined, phoneNumber: string | undefined,
password: string, password: string,
): Promise<IMatrixClientCreds> { ): Promise<IMatrixClientCreds> {
const isEmail = username?.indexOf("@") > 0; const isEmail = !!username && username.indexOf("@") > 0;
let identifier; let identifier;
if (phoneCountry && phoneNumber) { if (phoneCountry && phoneNumber) {
@ -127,7 +124,7 @@ export default class Login {
}; };
const tryFallbackHs = (originalError: Error): Promise<IMatrixClientCreds> => { const tryFallbackHs = (originalError: Error): Promise<IMatrixClientCreds> => {
return sendLoginRequest(this.fallbackHsUrl, this.isUrl, "m.login.password", loginParams).catch( return sendLoginRequest(this.fallbackHsUrl!, this.isUrl, "m.login.password", loginParams).catch(
(fallbackError) => { (fallbackError) => {
logger.log("fallback HS login failed", fallbackError); logger.log("fallback HS login failed", fallbackError);
// throw the original error // throw the original error
@ -136,13 +133,13 @@ export default class Login {
); );
}; };
let originalLoginError = null; let originalLoginError: Error | null = null;
return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams) return sendLoginRequest(this.hsUrl, this.isUrl, "m.login.password", loginParams)
.catch((error) => { .catch((error) => {
originalLoginError = error; originalLoginError = error;
if (error.httpStatus === 403) { if (error.httpStatus === 403) {
if (this.fallbackHsUrl) { if (this.fallbackHsUrl) {
return tryFallbackHs(originalLoginError); return tryFallbackHs(originalLoginError!);
} }
} }
throw originalLoginError; throw originalLoginError;

View file

@ -256,7 +256,7 @@ export default class ScalarAuthClient {
} }
} }
public getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string { public getScalarInterfaceUrlForRoom(room: Room, screen?: string, id?: string): string {
const roomId = room.roomId; const roomId = room.roomId;
const roomName = room.name; const roomName = room.name;
let url = this.uiUrl; let url = this.uiUrl;

View file

@ -102,7 +102,7 @@ async function getSecretStorageKey({
}): Promise<[string, Uint8Array]> { }): Promise<[string, Uint8Array]> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
let keyId = await cli.getDefaultSecretStorageKeyId(); let keyId = await cli.getDefaultSecretStorageKeyId();
let keyInfo: ISecretStorageKeyInfo; let keyInfo!: ISecretStorageKeyInfo;
if (keyId) { if (keyId) {
// use the default SSSS key if set // use the default SSSS key if set
keyInfo = keyInfos[keyId]; keyInfo = keyInfos[keyId];

View file

@ -82,6 +82,7 @@ const singleMxcUpload = async (): Promise<string | null> => {
fileSelector.setAttribute("type", "file"); fileSelector.setAttribute("type", "file");
fileSelector.onchange = (ev: HTMLInputEvent) => { fileSelector.onchange = (ev: HTMLInputEvent) => {
const file = ev.target.files?.[0]; const file = ev.target.files?.[0];
if (!file) return;
Modal.createDialog(UploadConfirmDialog, { Modal.createDialog(UploadConfirmDialog, {
file, file,
@ -304,7 +305,7 @@ export const Commands = [
if (args) { if (args) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) { if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
return reject( return reject(
newTranslatableError("You do not have the required permissions to use this command."), newTranslatableError("You do not have the required permissions to use this command."),
); );
@ -313,7 +314,7 @@ export const Commands = [
const { finished } = Modal.createDialog( const { finished } = Modal.createDialog(
RoomUpgradeWarningDialog, RoomUpgradeWarningDialog,
{ roomId: roomId, targetVersion: args }, { roomId: roomId, targetVersion: args },
/*className=*/ null, /*className=*/ undefined,
/*isPriority=*/ false, /*isPriority=*/ false,
/*isStatic=*/ true, /*isStatic=*/ true,
); );
@ -1199,7 +1200,7 @@ export const Commands = [
description: _td("Switches to this room's virtual room, if it has one"), description: _td("Switches to this room's virtual room, if it has one"),
category: CommandCategories.advanced, category: CommandCategories.advanced,
isEnabled(): boolean { isEnabled(): boolean {
return LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom(); return !!LegacyCallHandler.instance.getSupportsVirtualRooms() && !isCurrentLocalRoom();
}, },
runFn: (roomId) => { runFn: (roomId) => {
return success( return success(
@ -1389,7 +1390,7 @@ export function parseCommandString(input: string): { cmd?: string; args?: string
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/); const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
let cmd: string; let cmd: string;
let args: string; let args: string | undefined;
if (bits) { if (bits) {
cmd = bits[1].substring(1).toLowerCase(); cmd = bits[1].substring(1).toLowerCase();
args = bits[2]; args = bits[2];
@ -1414,7 +1415,7 @@ interface ICmd {
export function getCommand(input: string): ICmd { export function getCommand(input: string): ICmd {
const { cmd, args } = parseCommandString(input); const { cmd, args } = parseCommandString(input);
if (CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) { if (cmd && CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
return { return {
cmd: CommandMap.get(cmd), cmd: CommandMap.get(cmd),
args, args,

View file

@ -120,20 +120,20 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
// We're taking the display namke directly from the event content here so we need // We're taking the display namke directly from the event content here so we need
// to strip direction override chars which the js-sdk would normally do when // to strip direction override chars which the js-sdk would normally do when
// calculating the display name // calculating the display name
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname), oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
displayName: removeDirectionOverrideChars(content.displayname), displayName: removeDirectionOverrideChars(content.displayname!),
}); });
} else if (!prevContent.displayname && content.displayname) { } else if (!prevContent.displayname && content.displayname) {
return () => return () =>
_t("%(senderName)s set their display name to %(displayName)s", { _t("%(senderName)s set their display name to %(displayName)s", {
senderName: ev.getSender(), senderName: ev.getSender(),
displayName: removeDirectionOverrideChars(content.displayname), displayName: removeDirectionOverrideChars(content.displayname!),
}); });
} else if (prevContent.displayname && !content.displayname) { } else if (prevContent.displayname && !content.displayname) {
return () => return () =>
_t("%(senderName)s removed their display name (%(oldDisplayName)s)", { _t("%(senderName)s removed their display name (%(oldDisplayName)s)", {
senderName, senderName,
oldDisplayName: removeDirectionOverrideChars(prevContent.displayname), oldDisplayName: removeDirectionOverrideChars(prevContent.displayname!),
}); });
} else if (prevContent.avatar_url && !content.avatar_url) { } else if (prevContent.avatar_url && !content.avatar_url) {
return () => _t("%(senderName)s removed their profile picture", { senderName }); return () => _t("%(senderName)s removed their profile picture", { senderName });
@ -545,7 +545,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
if (newlyPinned.length === 1 && newlyUnpinned.length === 0) { if (newlyPinned.length === 1 && newlyUnpinned.length === 0) {
// A single message was pinned, include a link to that message. // A single message was pinned, include a link to that message.
if (allowJSX) { if (allowJSX) {
const messageId = newlyPinned.pop(); const messageId = newlyPinned.pop()!;
return () => ( return () => (
<span> <span>
@ -578,7 +578,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Rende
if (newlyUnpinned.length === 1 && newlyPinned.length === 0) { if (newlyUnpinned.length === 1 && newlyPinned.length === 0) {
// A single message was unpinned, include a link to that message. // A single message was unpinned, include a link to that message.
if (allowJSX) { if (allowJSX) {
const messageId = newlyUnpinned.pop(); const messageId = newlyUnpinned.pop()!;
return () => ( return () => (
<span> <span>

View file

@ -105,7 +105,7 @@ export interface IProps extends MenuProps {
} }
interface IState { interface IState {
contextMenuElem: HTMLDivElement; contextMenuElem?: HTMLDivElement;
} }
// Generic ContextMenu Portal wrapper // Generic ContextMenu Portal wrapper
@ -122,9 +122,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {};
contextMenuElem: null,
};
// persist what had focus when we got initialized so we can return it after // persist what had focus when we got initialized so we can return it after
this.initialFocus = document.activeElement as HTMLElement; this.initialFocus = document.activeElement as HTMLElement;
@ -181,7 +179,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
button: 0, // Left button: 0, // Left
relatedTarget: null, relatedTarget: null,
}); });
document.elementFromPoint(x, y).dispatchEvent(clickEvent); document.elementFromPoint(x, y)?.dispatchEvent(clickEvent);
}); });
} }
}; };
@ -239,7 +237,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
// MessageActionBar), we should close any ContextMenu that is open. // MessageActionBar), we should close any ContextMenu that is open.
KeyBindingAction.ArrowLeft, KeyBindingAction.ArrowLeft,
KeyBindingAction.ArrowRight, KeyBindingAction.ArrowRight,
].includes(action) ].includes(action!)
) { ) {
this.props.onFinished(); this.props.onFinished();
} }
@ -312,12 +310,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
position.top = Math.min(position.top, maxTop); position.top = Math.min(position.top, maxTop);
// Adjust the chevron if necessary // Adjust the chevron if necessary
if (chevronOffset.top !== undefined) { if (chevronOffset.top !== undefined) {
chevronOffset.top = propsChevronOffset + top - position.top; chevronOffset.top = propsChevronOffset! + top! - position.top;
} }
} else if (position.bottom !== undefined) { } else if (position.bottom !== undefined) {
position.bottom = Math.min(position.bottom, windowHeight - contextMenuRect.height - WINDOW_PADDING); position.bottom = Math.min(position.bottom, windowHeight - contextMenuRect.height - WINDOW_PADDING);
if (chevronOffset.top !== undefined) { if (chevronOffset.top !== undefined) {
chevronOffset.top = propsChevronOffset + position.bottom - bottom; chevronOffset.top = propsChevronOffset! + position.bottom - bottom!;
} }
} }
if (position.left !== undefined) { if (position.left !== undefined) {
@ -327,12 +325,12 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
} }
position.left = Math.min(position.left, maxLeft); position.left = Math.min(position.left, maxLeft);
if (chevronOffset.left !== undefined) { if (chevronOffset.left !== undefined) {
chevronOffset.left = propsChevronOffset + left - position.left; chevronOffset.left = propsChevronOffset! + left! - position.left;
} }
} else if (position.right !== undefined) { } else if (position.right !== undefined) {
position.right = Math.min(position.right, windowWidth - contextMenuRect.width - WINDOW_PADDING); position.right = Math.min(position.right, windowWidth - contextMenuRect.width - WINDOW_PADDING);
if (chevronOffset.left !== undefined) { if (chevronOffset.left !== undefined) {
chevronOffset.left = propsChevronOffset + position.right - right; chevronOffset.left = propsChevronOffset! + position.right - right!;
} }
} }
} }
@ -389,7 +387,7 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
const wrapperStyle: CSSProperties = {}; const wrapperStyle: CSSProperties = {};
if (!isNaN(Number(zIndex))) { if (!isNaN(Number(zIndex))) {
menuStyle["zIndex"] = zIndex + 1; menuStyle["zIndex"] = zIndex! + 1;
wrapperStyle["zIndex"] = zIndex; wrapperStyle["zIndex"] = zIndex;
} }

View file

@ -97,7 +97,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.onRemove(this.props.member); this.props.onRemove!(this.props.member);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
@ -132,7 +132,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
interface IDMRoomTileProps { interface IDMRoomTileProps {
member: Member; member: Member;
lastActiveTs: number; lastActiveTs?: number;
onToggle(member: Member): void; onToggle(member: Member): void;
highlightWord: string; highlightWord: string;
isSelected: boolean; isSelected: boolean;
@ -188,7 +188,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
} }
public render(): React.ReactNode { public render(): React.ReactNode {
let timestamp = null; let timestamp: JSX.Element | undefined;
if (this.props.lastActiveTs) { if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs); const humanTs = humanizeTime(this.props.lastActiveTs);
timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>; timestamp = <span className="mx_InviteDialog_tile--room_time">{humanTs}</span>;
@ -201,7 +201,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
<BaseAvatar <BaseAvatar
url={ url={
this.props.member.getMxcAvatarUrl() this.props.member.getMxcAvatarUrl()
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize) ? mediaFromMxc(this.props.member.getMxcAvatarUrl()!).getSquareThumbnailHttp(avatarSize)
: null : null
} }
name={this.props.member.name} name={this.props.member.name}
@ -211,7 +211,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
/> />
); );
let checkmark = null; let checkmark: JSX.Element | undefined;
if (this.props.isSelected) { if (this.props.isSelected) {
// To reduce flickering we put the 'selected' room tile above the real avatar // To reduce flickering we put the 'selected' room tile above the real avatar
checkmark = <div className="mx_InviteDialog_tile--room_selected" />; checkmark = <div className="mx_InviteDialog_tile--room_selected" />;
@ -301,7 +301,7 @@ interface IInviteDialogState {
// These two flags are used for the 'Go' button to communicate what is going on. // These two flags are used for the 'Go' button to communicate what is going on.
busy: boolean; busy: boolean;
errorText: string; errorText?: string;
} }
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> { export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
@ -324,7 +324,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog"); throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
} }
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]); const alreadyInvited = new Set([MatrixClientPeg.get().getUserId()!, SdkConfig.get("welcome_user_id")]);
if (isRoomInvite(props)) { if (isRoomInvite(props)) {
const room = MatrixClientPeg.get().getRoom(props.roomId); const room = MatrixClientPeg.get().getRoom(props.roomId);
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room"); if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
@ -351,7 +351,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
// These two flags are used for the 'Go' button to communicate what is going on. // These two flags are used for the 'Go' button to communicate what is going on.
busy: false, busy: false,
errorText: null,
}; };
} }
@ -386,7 +385,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
} }
} }
const recents = []; const recents: {
userId: string;
user: RoomMember;
lastActive: number;
}[] = [];
for (const userId in rooms) { for (const userId in rooms) {
// Filter out user IDs that are already in the room / should be excluded // Filter out user IDs that are already in the room / should be excluded
if (excludedTargetIds.has(userId)) { if (excludedTargetIds.has(userId)) {
@ -455,14 +458,16 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
// Check to see if there's anything to convert first // Check to see if there's anything to convert first
if (!this.state.filterText || !this.state.filterText.includes("@")) return this.state.targets || []; if (!this.state.filterText || !this.state.filterText.includes("@")) return this.state.targets || [];
let newMember: Member; let newMember: Member | undefined;
if (this.state.filterText.startsWith("@")) { if (this.state.filterText.startsWith("@")) {
// Assume mxid // Assume mxid
newMember = new DirectoryMember({ user_id: this.state.filterText, display_name: null, avatar_url: null }); newMember = new DirectoryMember({ user_id: this.state.filterText });
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) { } else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
// Assume email // Assume email
newMember = new ThreepidMember(this.state.filterText); newMember = new ThreepidMember(this.state.filterText);
} }
if (!newMember) return this.state.targets;
const newTargets = [...(this.state.targets || []), newMember]; const newTargets = [...(this.state.targets || []), newMember];
this.setState({ targets: newTargets, filterText: "" }); this.setState({ targets: newTargets, filterText: "" });
return newTargets; return newTargets;
@ -778,8 +783,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
...this.state.serverResultsMixin, ...this.state.serverResultsMixin,
...this.state.threepidResultsMixin, ...this.state.threepidResultsMixin,
]; ];
const toAdd = []; const toAdd: Member[] = [];
const failed = []; const failed: string[] = [];
const potentialAddresses = text const potentialAddresses = text
.split(/[\s,]+/) .split(/[\s,]+/)
.map((p) => p.trim()) .map((p) => p.trim())
@ -803,13 +808,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
try { try {
const profile = await MatrixClientPeg.get().getProfileInfo(address); const profile = await MatrixClientPeg.get().getProfileInfo(address);
const displayName = profile ? profile.displayname : null;
const avatarUrl = profile ? profile.avatar_url : null;
toAdd.push( toAdd.push(
new DirectoryMember({ new DirectoryMember({
user_id: address, user_id: address,
display_name: displayName, display_name: profile?.displayname,
avatar_url: avatarUrl, avatar_url: profile?.avatar_url,
}), }),
); );
} catch (e) { } catch (e) {
@ -859,11 +862,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.props.onFinished(false); this.props.onFinished(false);
}; };
private renderSection(kind: "recents" | "suggestions"): JSX.Element { private renderSection(kind: "recents" | "suggestions"): ReactNode {
let sourceMembers = kind === "recents" ? this.state.recents : this.state.suggestions; let sourceMembers = kind === "recents" ? this.state.recents : this.state.suggestions;
let showNum = kind === "recents" ? this.state.numRecentsShown : this.state.numSuggestionsShown; let showNum = kind === "recents" ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === "recents" ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this); const showMoreFn = kind === "recents" ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
const lastActive = (m: Result): number | null => (kind === "recents" ? m.lastActive : null); const lastActive = (m: Result): number | undefined => (kind === "recents" ? m.lastActive : undefined);
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions"); let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
if (this.props.kind === KIND_INVITE) { if (this.props.kind === KIND_INVITE) {
@ -924,7 +927,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
const toRender = sourceMembers.slice(0, showNum); const toRender = sourceMembers.slice(0, showNum);
const hasMore = toRender.length < sourceMembers.length; const hasMore = toRender.length < sourceMembers.length;
let showMore = null; let showMore: JSX.Element | undefined;
if (hasMore) { if (hasMore) {
showMore = ( showMore = (
<div className="mx_InviteDialog_section_showMore"> <div className="mx_InviteDialog_section_showMore">
@ -960,7 +963,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.state.targets.length === 0 && this.state.targets.length === 0 &&
this.state.filterText.length === 0; this.state.filterText.length === 0;
const targets = this.state.targets.map((t) => ( const targets = this.state.targets.map((t) => (
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} /> <DMUserTile member={t} onRemove={this.state.busy ? undefined : this.removeMember} key={t.userId} />
)); ));
const input = ( const input = (
<input <input
@ -973,7 +976,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
autoFocus={true} autoFocus={true}
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)} disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
autoComplete="off" autoComplete="off"
placeholder={hasPlaceholder ? _t("Search") : null} placeholder={hasPlaceholder ? _t("Search") : undefined}
data-testid="invite-dialog-input" data-testid="invite-dialog-input"
/> />
); );
@ -985,7 +988,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
); );
} }
private renderIdentityServerWarning(): JSX.Element { private renderIdentityServerWarning(): ReactNode {
if ( if (
!this.state.tryingIdentityServer || !this.state.tryingIdentityServer ||
this.state.canUseIdentityServer || this.state.canUseIdentityServer ||
@ -1080,7 +1083,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
selectText(e.currentTarget); selectText(e.currentTarget);
} }
private get screenName(): ScreenName { private get screenName(): ScreenName | undefined {
switch (this.props.kind) { switch (this.props.kind) {
case KIND_DM: case KIND_DM:
return "StartChat"; return "StartChat";
@ -1088,7 +1091,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
} }
public render(): React.ReactNode { public render(): React.ReactNode {
let spinner = null; let spinner: JSX.Element | undefined;
if (this.state.busy) { if (this.state.busy) {
spinner = <Spinner w={20} h={20} />; spinner = <Spinner w={20} h={20} />;
} }
@ -1108,7 +1111,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@")); this.state.targets.length > 0 || (this.state.filterText && this.state.filterText.includes("@"));
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const userId = cli.getUserId(); const userId = cli.getUserId()!;
if (this.props.kind === KIND_DM) { if (this.props.kind === KIND_DM) {
title = _t("Direct Messages"); title = _t("Direct Messages");
@ -1150,11 +1153,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
<p>{_t("If you can't see who you're looking for, send them your invite link below.")}</p> <p>{_t("If you can't see who you're looking for, send them your invite link below.")}</p>
</div> </div>
); );
const link = makeUserPermalink(MatrixClientPeg.get().getUserId()); const link = makeUserPermalink(MatrixClientPeg.get().getUserId()!);
footer = ( footer = (
<div className="mx_InviteDialog_footer"> <div className="mx_InviteDialog_footer">
<h3>{_t("Or send invite link")}</h3> <h3>{_t("Or send invite link")}</h3>
<CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId())}> <CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId()!)}>
<a href={link} onClick={this.onLinkClick}> <a href={link} onClick={this.onLinkClick}>
{link} {link}
</a> </a>
@ -1296,7 +1299,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
let dialogContent; let dialogContent;
if (this.props.kind === KIND_CALL_TRANSFER) { if (this.props.kind === KIND_CALL_TRANSFER) {
const tabs = []; const tabs: Tab[] = [];
tabs.push( tabs.push(
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection), new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
); );

View file

@ -179,7 +179,7 @@ const toPublicRoomResult = (publicRoom: IPublicRoomsChunkRoom): IPublicRoomResul
publicRoom.name?.toLowerCase(), publicRoom.name?.toLowerCase(),
sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }), sanitizeHtml(publicRoom.topic?.toLowerCase() ?? "", { allowedTags: [] }),
...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []), ...(publicRoom.aliases?.map((it) => it.toLowerCase()) || []),
].filter(Boolean), ].filter(Boolean) as string[],
}); });
const toRoomResult = (room: Room): IRoomResult => { const toRoomResult = (room: Room): IRoomResult => {
@ -310,7 +310,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}, [cli]); }, [cli]);
const msc3946ProcessDynamicPredecessor = useFeatureEnabled("feature_dynamic_room_predecessors"); const msc3946ProcessDynamicPredecessor = useFeatureEnabled("feature_dynamic_room_predecessors");
const ownInviteLink = makeUserPermalink(cli.getUserId()); const ownInviteLink = makeUserPermalink(cli.getUserId()!);
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false); const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
const trimmedQuery = useMemo(() => query.trim(), [query]); const trimmedQuery = useMemo(() => query.trim(), [query]);
@ -465,7 +465,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
useWebSearchMetrics(numResults, query.length, true); useWebSearchMetrics(numResults, query.length, true);
const activeSpace = SpaceStore.instance.activeSpaceRoom; const activeSpace = SpaceStore.instance.activeSpaceRoom;
const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace, query); const [spaceResults, spaceResultsLoading] = useSpaceResults(activeSpace ?? undefined, query);
const setQuery = (e: ChangeEvent<HTMLInputElement>): void => { const setQuery = (e: ChangeEvent<HTMLInputElement>): void => {
const newQuery = e.currentTarget.value; const newQuery = e.currentTarget.value;
@ -473,7 +473,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}; };
useEffect(() => { useEffect(() => {
setImmediate(() => { setImmediate(() => {
let ref: Ref; let ref: Ref | undefined;
if (rovingContext.state.refs) { if (rovingContext.state.refs) {
ref = rovingContext.state.refs[0]; ref = rovingContext.state.refs[0];
} }
@ -521,7 +521,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
onFinished(); onFinished();
}; };
let otherSearchesSection: JSX.Element; let otherSearchesSection: JSX.Element | undefined;
if (trimmedQuery || filter !== Filter.PublicRooms) { if (trimmedQuery || filter !== Filter.PublicRooms) {
otherSearchesSection = ( otherSearchesSection = (
<div <div
@ -693,7 +693,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
}; };
let peopleSection: JSX.Element; let peopleSection: JSX.Element | undefined;
if (results[Section.People].length) { if (results[Section.People].length) {
peopleSection = ( peopleSection = (
<div <div
@ -707,7 +707,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let suggestionsSection: JSX.Element; let suggestionsSection: JSX.Element | undefined;
if (results[Section.Suggestions].length && filter === Filter.People) { if (results[Section.Suggestions].length && filter === Filter.People) {
suggestionsSection = ( suggestionsSection = (
<div <div
@ -721,7 +721,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let roomsSection: JSX.Element; let roomsSection: JSX.Element | undefined;
if (results[Section.Rooms].length) { if (results[Section.Rooms].length) {
roomsSection = ( roomsSection = (
<div <div
@ -735,7 +735,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let spacesSection: JSX.Element; let spacesSection: JSX.Element | undefined;
if (results[Section.Spaces].length) { if (results[Section.Spaces].length) {
spacesSection = ( spacesSection = (
<div <div
@ -749,7 +749,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let publicRoomsSection: JSX.Element; let publicRoomsSection: JSX.Element | undefined;
if (filter === Filter.PublicRooms) { if (filter === Filter.PublicRooms) {
publicRoomsSection = ( publicRoomsSection = (
<div <div
@ -791,7 +791,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let spaceRoomsSection: JSX.Element; let spaceRoomsSection: JSX.Element | undefined;
if (spaceResults.length && activeSpace && filter === null) { if (spaceResults.length && activeSpace && filter === null) {
spaceRoomsSection = ( spaceRoomsSection = (
<div <div
@ -836,7 +836,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let joinRoomSection: JSX.Element; let joinRoomSection: JSX.Element | undefined;
if ( if (
trimmedQuery.startsWith("#") && trimmedQuery.startsWith("#") &&
trimmedQuery.includes(":") && trimmedQuery.includes(":") &&
@ -868,7 +868,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let hiddenResultsSection: JSX.Element; let hiddenResultsSection: JSX.Element | undefined;
if (filter === Filter.People) { if (filter === Filter.People) {
hiddenResultsSection = ( hiddenResultsSection = (
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group"> <div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group">
@ -921,7 +921,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let groupChatSection: JSX.Element; let groupChatSection: JSX.Element | undefined;
if (filter === Filter.People) { if (filter === Filter.People) {
groupChatSection = ( groupChatSection = (
<div <div
@ -941,7 +941,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
let messageSearchSection: JSX.Element; let messageSearchSection: JSX.Element | undefined;
if (filter === null) { if (filter === null) {
messageSearchSection = ( messageSearchSection = (
<div <div
@ -977,7 +977,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</> </>
); );
} else { } else {
let recentSearchesSection: JSX.Element; let recentSearchesSection: JSX.Element | undefined;
if (recentSearches.length) { if (recentSearches.length) {
recentSearchesSection = ( recentSearchesSection = (
<div <div
@ -1075,7 +1075,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
break; break;
} }
let ref: RefObject<HTMLElement>; let ref: RefObject<HTMLElement> | undefined;
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev); const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
switch (accessibilityAction) { switch (accessibilityAction) {
case KeyBindingAction.Escape: case KeyBindingAction.Escape:

View file

@ -24,11 +24,11 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
interface IProps { interface IProps {
avatarUrl?: string; avatarUrl?: string;
avatarDisabled?: boolean; avatarDisabled?: boolean;
name?: string; name: string;
nameDisabled?: boolean; nameDisabled?: boolean;
topic?: string; topic?: string;
topicDisabled?: boolean; topicDisabled?: boolean;
setAvatar(avatar: File): void; setAvatar(avatar?: File): void;
setName(name: string): void; setName(name: string): void;
setTopic(topic: string): void; setTopic(topic: string): void;
} }
@ -102,7 +102,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
setAvatar(file); setAvatar(file);
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (ev) => { reader.onload = (ev) => {
setAvatarDataUrl(ev.target.result as string); setAvatarDataUrl(ev.target?.result as string);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
}} }}

View file

@ -53,7 +53,7 @@ const SpecificChildrenPicker: React.FC<ISpecificChildrenPickerProps> = ({
const matcher = new QueryMatcher<Room>(rooms, { const matcher = new QueryMatcher<Room>(rooms, {
keys: ["name"], keys: ["name"],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)], funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean) as string[]],
shouldMatchWordsOnly: false, shouldMatchWordsOnly: false,
}); });

View file

@ -245,14 +245,14 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
const SpaceCreateMenu: React.FC<{ const SpaceCreateMenu: React.FC<{
onFinished(): void; onFinished(): void;
}> = ({ onFinished }) => { }> = ({ onFinished }) => {
const [visibility, setVisibility] = useState<Visibility>(null); const [visibility, setVisibility] = useState<Visibility | null>(null);
const [busy, setBusy] = useState<boolean>(false); const [busy, setBusy] = useState<boolean>(false);
const [name, setName] = useState(""); const [name, setName] = useState("");
const spaceNameField = useRef<Field>(); const spaceNameField = useRef<Field>();
const [alias, setAlias] = useState(""); const [alias, setAlias] = useState("");
const spaceAliasField = useRef<RoomAliasField>(); const spaceAliasField = useRef<RoomAliasField>();
const [avatar, setAvatar] = useState<File>(null); const [avatar, setAvatar] = useState<File | undefined>(undefined);
const [topic, setTopic] = useState<string>(""); const [topic, setTopic] = useState<string>("");
const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => { const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {

View file

@ -215,7 +215,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
} }
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps }, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
let contextMenu = null; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed) {
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />; contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
} }

View file

@ -121,11 +121,11 @@ export const SpaceButton = forwardRef<HTMLElement, IButtonProps>(
); );
} }
let contextMenu: JSX.Element; let contextMenu: JSX.Element | undefined;
if (menuDisplayed && ContextMenuComponent) { if (menuDisplayed && handle.current && ContextMenuComponent) {
contextMenu = ( contextMenu = (
<ContextMenuComponent <ContextMenuComponent
{...toRightOf(handle.current?.getBoundingClientRect(), 0)} {...toRightOf(handle.current.getBoundingClientRect(), 0)}
space={space} space={space}
onFinished={closeMenu} onFinished={closeMenu}
/> />
@ -242,7 +242,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
} }
private get isCollapsed(): boolean { private get isCollapsed(): boolean {
return this.state.collapsed || this.props.isPanelCollapsed; return this.state.collapsed || !!this.props.isPanelCollapsed;
} }
private toggleCollapse = (evt: ButtonEvent): void => { private toggleCollapse = (evt: ButtonEvent): void => {

View file

@ -30,7 +30,7 @@ const onClickSendDm = (ev: ButtonEvent): void => {
}; };
interface Props { interface Props {
useCase: UseCase; useCase: UseCase | null;
} }
export function UserOnboardingHeader({ useCase }: Props): JSX.Element { export function UserOnboardingHeader({ useCase }: Props): JSX.Element {

View file

@ -38,7 +38,7 @@ interface Props {
// We decided to only show the new user onboarding page to new users // We decided to only show the new user onboarding page to new users
// For now, that means we set the cutoff at 2022-07-01 00:00 UTC // For now, that means we set the cutoff at 2022-07-01 00:00 UTC
const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600); const USER_ONBOARDING_CUTOFF_DATE = new Date(1_656_633_600);
export function showUserOnboardingPage(useCase: UseCase): boolean { export function showUserOnboardingPage(useCase: UseCase | null): boolean {
return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE); return useCase !== null || MatrixClientPeg.userRegisteredAfter(USER_ONBOARDING_CUTOFF_DATE);
} }
@ -55,13 +55,11 @@ export function UserOnboardingPage({ justRegistered = false }: Props): JSX.Eleme
const [showList, setShowList] = useState<boolean>(false); const [showList, setShowList] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (initialSyncComplete) { if (initialSyncComplete) {
let handler: number | null = window.setTimeout(() => { const handler = window.setTimeout(() => {
handler = null;
setShowList(true); setShowList(true);
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
return () => { return () => {
clearTimeout(handler); clearTimeout(handler);
handler = null;
}; };
} else { } else {
setShowList(false); setShowList(false);

View file

@ -18,6 +18,7 @@ import * as React from "react";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { XOR } from "../../../@types/common";
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"]; const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"];
const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""]; const BUTTON_LETTERS = ["", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "", "+", ""];
@ -31,7 +32,7 @@ interface IButtonProps {
kind: DialPadButtonKind; kind: DialPadButtonKind;
digit?: string; digit?: string;
digitSubtext?: string; digitSubtext?: string;
onButtonPress: (digit: string, ev: ButtonEvent) => void; onButtonPress: (digit: string | undefined, ev: ButtonEvent) => void;
} }
class DialPadButton extends React.PureComponent<IButtonProps> { class DialPadButton extends React.PureComponent<IButtonProps> {
@ -60,16 +61,24 @@ class DialPadButton extends React.PureComponent<IButtonProps> {
} }
} }
interface IProps { interface IBaseProps {
onDigitPress: (digit: string, ev: ButtonEvent) => void; onDigitPress: (digit: string, ev: ButtonEvent) => void;
hasDial: boolean;
onDeletePress?: (ev: ButtonEvent) => void; onDeletePress?: (ev: ButtonEvent) => void;
onDialPress?: () => void; hasDial: boolean;
} }
export default class Dialpad extends React.PureComponent<IProps> { interface IProps extends IBaseProps {
hasDial: false;
}
interface IDialProps extends IBaseProps {
hasDial: true;
onDialPress: () => void;
}
export default class Dialpad extends React.PureComponent<XOR<IProps, IDialProps>> {
public render(): React.ReactNode { public render(): React.ReactNode {
const buttonNodes = []; const buttonNodes: JSX.Element[] = [];
for (let i = 0; i < BUTTONS.length; i++) { for (let i = 0; i < BUTTONS.length; i++) {
const button = BUTTONS[i]; const button = BUTTONS[i];

View file

@ -76,7 +76,7 @@ interface IState {
sidebarShown: boolean; sidebarShown: boolean;
} }
function getFullScreenElement(): Element | undefined { function getFullScreenElement(): Element | null {
return ( return (
document.fullscreenElement || document.fullscreenElement ||
// moz omitted because firefox supports this unprefixed now (webkit here for safari) // moz omitted because firefox supports this unprefixed now (webkit here for safari)
@ -180,7 +180,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
} }
}; };
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall | null): void { private updateCallListeners(oldCall: MatrixCall | null, newCall: MatrixCall | null): void {
if (oldCall === newCall) return; if (oldCall === newCall) return;
if (oldCall) { if (oldCall) {
@ -245,7 +245,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
}; };
} }
let primary: CallFeed; let primary: CallFeed | undefined;
// Try to use a screensharing as primary, a remote one if possible // Try to use a screensharing as primary, a remote one if possible
const screensharingFeeds = feeds.filter((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare); const screensharingFeeds = feeds.filter((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
@ -289,7 +289,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
if (this.state.screensharing) { if (this.state.screensharing) {
isScreensharing = await this.props.call.setScreensharingEnabled(false); isScreensharing = await this.props.call.setScreensharingEnabled(false);
} else { } else {
if (PlatformPeg.get().supportsDesktopCapturer()) { if (PlatformPeg.get()?.supportsDesktopCapturer()) {
const { finished } = Modal.createDialog<[string]>(DesktopCapturerSourcePicker); const { finished } = Modal.createDialog<[string]>(DesktopCapturerSourcePicker);
const [source] = await finished; const [source] = await finished;
if (!source) return; if (!source) return;
@ -403,7 +403,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
); );
} }
private renderToast(): JSX.Element { private renderToast(): JSX.Element | null {
const { call } = this.props; const { call } = this.props;
const someoneIsScreensharing = call.getFeeds().some((feed) => { const someoneIsScreensharing = call.getFeeds().some((feed) => {
return feed.purpose === SDPStreamMetadataPurpose.Screenshare; return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
@ -413,8 +413,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const isScreensharing = call.isScreensharing(); const isScreensharing = call.isScreensharing();
const { primaryFeed, sidebarShown } = this.state; const { primaryFeed, sidebarShown } = this.state;
const sharerName = primaryFeed?.getMember().name; const sharerName = primaryFeed?.getMember()?.name;
if (!sharerName) return; if (!sharerName) return null;
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName }); let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
if (!sidebarShown) { if (!sidebarShown) {
@ -495,7 +495,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
); );
} else if (isLocalOnHold) { } else if (isLocalOnHold) {
onHoldText = _t("%(peerName)s held the call", { onHoldText = _t("%(peerName)s held the call", {
peerName: call.getOpponentMember().name, peerName: call.getOpponentMember()?.name,
}); });
} }
@ -556,8 +556,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call); const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall); const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
const callRoom = client.getRoom(callRoomId); const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
const secCallRoom = secondaryCall ? client.getRoom(secondaryCallRoomId) : null; const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
const callViewClasses = classNames({ const callViewClasses = classNames({
mx_LegacyCallView: true, mx_LegacyCallView: true,

View file

@ -104,9 +104,9 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
onHover={(hovering) => setHoveringDropdown(hovering)} onHover={(hovering) => setHoveringDropdown(hovering)}
state={state} state={state}
/> />
{menuDisplayed && ( {menuDisplayed && buttonRef.current && (
<DeviceContextMenu <DeviceContextMenu
{...alwaysAboveRightOf(buttonRef.current?.getBoundingClientRect())} {...alwaysAboveRightOf(buttonRef.current.getBoundingClientRect())}
onFinished={closeMenu} onFinished={closeMenu}
deviceKinds={deviceKinds} deviceKinds={deviceKinds}
/> />
@ -117,7 +117,7 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
interface IProps { interface IProps {
call: MatrixCall; call: MatrixCall;
pipMode: boolean; pipMode?: boolean;
handlers: { handlers: {
onHangupClick: () => void; onHangupClick: () => void;
onScreenshareClick: () => void; onScreenshareClick: () => void;
@ -150,7 +150,7 @@ interface IState {
export default class LegacyCallViewButtons extends React.Component<IProps, IState> { export default class LegacyCallViewButtons extends React.Component<IProps, IState> {
private dialpadButton = createRef<HTMLDivElement>(); private dialpadButton = createRef<HTMLDivElement>();
private contextMenuButton = createRef<HTMLDivElement>(); private contextMenuButton = createRef<HTMLDivElement>();
private controlsHideTimer: number = null; private controlsHideTimer: number | null = null;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@ -223,7 +223,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
}); });
let dialPad; let dialPad;
if (this.state.showDialpad) { if (this.state.showDialpad && this.dialpadButton.current) {
dialPad = ( dialPad = (
<DialpadContextMenu <DialpadContextMenu
{...alwaysMenuProps( {...alwaysMenuProps(
@ -231,7 +231,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
ChevronFace.None, ChevronFace.None,
CONTEXT_MENU_VPADDING, CONTEXT_MENU_VPADDING,
)} )}
// We mount the context menus as a as a child typically in order to include the // We mount the context menus as a child typically in order to include the
// context menus when fullscreening the call content. // context menus when fullscreening the call content.
// However, this does not work as well when the call is embedded in a // However, this does not work as well when the call is embedded in a
// picture-in-picture frame. Thus, only mount as child when we are *not* in PiP. // picture-in-picture frame. Thus, only mount as child when we are *not* in PiP.
@ -243,7 +243,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
} }
let contextMenu; let contextMenu;
if (this.state.showMoreMenu) { if (this.state.showMoreMenu && this.contextMenuButton.current) {
contextMenu = ( contextMenu = (
<LegacyCallContextMenu <LegacyCallContextMenu
{...alwaysMenuProps( {...alwaysMenuProps(

View file

@ -71,9 +71,9 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
}; };
interface LegacyCallViewHeaderProps { interface LegacyCallViewHeaderProps {
pipMode: boolean; pipMode?: boolean;
callRooms?: Room[]; callRooms: [Room, Room | null];
onPipMouseDown: (event: React.MouseEvent<Element, MouseEvent>) => void; onPipMouseDown?: (event: React.MouseEvent<Element, MouseEvent>) => void;
onExpand?: () => void; onExpand?: () => void;
onPin?: () => void; onPin?: () => void;
onMaximize?: () => void; onMaximize?: () => void;
@ -81,7 +81,7 @@ interface LegacyCallViewHeaderProps {
const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({ const LegacyCallViewHeader: React.FC<LegacyCallViewHeaderProps> = ({
pipMode = false, pipMode = false,
callRooms = [], callRooms,
onPipMouseDown, onPipMouseDown,
onExpand, onExpand,
onPin, onPin,

View file

@ -95,7 +95,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
element.addEventListener("resize", this.onResize); element.addEventListener("resize", this.onResize);
}; };
private updateFeed(oldFeed: CallFeed, newFeed: CallFeed): void { private updateFeed(oldFeed: CallFeed | null, newFeed: CallFeed | null): void {
if (oldFeed === newFeed) return; if (oldFeed === newFeed) return;
if (oldFeed) { if (oldFeed) {

View file

@ -84,7 +84,7 @@ export class Media {
* The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined * The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
* if no thumbnail media recorded. * if no thumbnail media recorded.
*/ */
public get thumbnailHttp(): string | undefined | null { public get thumbnailHttp(): string | null {
if (!this.hasThumbnail) return null; if (!this.hasThumbnail) return null;
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc!); return this.client.mxcUrlToHttp(this.thumbnailMxc!);

View file

@ -45,7 +45,7 @@ export function isSlashCommand(model: EditorModel): boolean {
return false; return false;
} }
export function getSlashCommand(model: EditorModel): [Command, string, string] { export function getSlashCommand(model: EditorModel): [Command | undefined, string | undefined, string] {
const commandText = model.parts.reduce((text, part) => { const commandText = model.parts.reduce((text, part) => {
// use mxid to textify user pills in a command and room alias/id for room pills // use mxid to textify user pills in a command and room alias/id for room pills
if (part.type === Type.UserPill || part.type === Type.RoomPill) { if (part.type === Type.UserPill || part.type === Type.RoomPill) {
@ -69,7 +69,7 @@ export async function runSlashCommand(
if (result.promise) { if (result.promise) {
try { try {
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
messageContent = await result.promise; messageContent = (await result.promise) ?? null;
} else { } else {
await result.promise; await result.promise;
} }

View file

@ -32,7 +32,7 @@ export function walkDOMDepthFirst(rootNode: Node, enterNodeCallback: Predicate,
} else if (node.nextSibling) { } else if (node.nextSibling) {
node = node.nextSibling; node = node.nextSibling;
} else { } else {
while (!node.nextSibling && node !== rootNode) { while (node && !node.nextSibling && node !== rootNode) {
node = node.parentElement; node = node.parentElement;
if (node !== rootNode) { if (node !== rootNode) {
leaveNodeCallback(node); leaveNodeCallback(node);

View file

@ -127,7 +127,7 @@ export default class EditorModel {
return this._parts; return this._parts;
} }
public get autoComplete(): AutocompleteWrapperModel { public get autoComplete(): AutocompleteWrapperModel | null {
if (this.activePartIdx === this.autoCompletePartIdx) { if (this.activePartIdx === this.autoCompletePartIdx) {
return this._autoComplete; return this._autoComplete;
} }
@ -212,12 +212,12 @@ export default class EditorModel {
const transformAddedLen = this.getTransformAddedLen(newPosition, inputType, diff); const transformAddedLen = this.getTransformAddedLen(newPosition, inputType, diff);
newPosition = this.positionForOffset(caretOffset + transformAddedLen, true); newPosition = this.positionForOffset(caretOffset + transformAddedLen, true);
} }
this.updateCallback(newPosition, inputType, diff); this.updateCallback?.(newPosition, inputType, diff);
return acPromise; return acPromise;
} }
private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number { private getTransformAddedLen(newPosition: DocumentPosition, inputType: string, diff: IDiff): number {
const result = this.transformCallback(newPosition, inputType, diff); const result = this.transformCallback?.(newPosition, inputType, diff);
return Number.isFinite(result) ? (result as number) : 0; return Number.isFinite(result) ? (result as number) : 0;
} }
@ -268,13 +268,13 @@ export default class EditorModel {
// rerender even if editor contents didn't change // rerender even if editor contents didn't change
// to make sure the MessageEditor checks // to make sure the MessageEditor checks
// model.autoComplete being empty and closes it // model.autoComplete being empty and closes it
this.updateCallback(pos); this.updateCallback?.(pos);
}; };
private mergeAdjacentParts(): void { private mergeAdjacentParts(): void {
let prevPart: Part | undefined; let prevPart: Part | undefined;
for (let i = 0; i < this._parts.length; ++i) { for (let i = 0; i < this._parts.length; ++i) {
let part = this._parts[i]; let part: Part | undefined = this._parts[i];
const isEmpty = !part.text.length; const isEmpty = !part.text.length;
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part); const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
if (isEmpty || isMerged) { if (isEmpty || isMerged) {
@ -452,13 +452,13 @@ export default class EditorModel {
*/ */
public transform(callback: ManualTransformCallback): Promise<void> { public transform(callback: ManualTransformCallback): Promise<void> {
const pos = callback(); const pos = callback();
let acPromise: Promise<void> = null; let acPromise: Promise<void> | null = null;
if (!(pos instanceof Range)) { if (!(pos instanceof Range)) {
acPromise = this.setActivePart(pos, true); acPromise = this.setActivePart(pos, true);
} else { } else {
acPromise = Promise.resolve(); acPromise = Promise.resolve();
} }
this.updateCallback(pos); this.updateCallback?.(pos);
return acPromise; return acPromise;
} }
} }

View file

@ -422,7 +422,7 @@ class RoomPillPart extends PillPart {
protected setAvatar(node: HTMLElement): void { protected setAvatar(node: HTMLElement): void {
let initialLetter = ""; let initialLetter = "";
let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop"); let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
if (!avatarUrl) { if (!avatarUrl) {
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? ""; initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId); avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
@ -541,7 +541,7 @@ export class PartCreator {
public constructor( public constructor(
private readonly room: Room, private readonly room: Room,
private readonly client: MatrixClient, private readonly client: MatrixClient,
autoCompleteCreator: AutoCompleteCreator = null, autoCompleteCreator: AutoCompleteCreator | null = null,
) { ) {
// pre-create the creator as an object even without callback so it can already be passed // pre-create the creator as an object even without callback so it can already be passed
// to PillCandidatePart (e.g. while deserializing) and set later on // to PillCandidatePart (e.g. while deserializing) and set later on
@ -574,7 +574,7 @@ export class PartCreator {
return this.plain(text); return this.plain(text);
} }
public deserializePart(part: SerializedPart): Part { public deserializePart(part: SerializedPart): Part | undefined {
switch (part.type) { switch (part.type) {
case Type.Plain: case Type.Plain:
return this.plain(part.text); return this.plain(part.text);
@ -612,7 +612,7 @@ export class PartCreator {
public roomPill(alias: string, roomId?: string): RoomPillPart { public roomPill(alias: string, roomId?: string): RoomPillPart {
let room: Room | undefined; let room: Room | undefined;
if (roomId || alias[0] !== "#") { if (roomId || alias[0] !== "#") {
room = this.client.getRoom(roomId || alias); room = this.client.getRoom(roomId || alias) ?? undefined;
} else { } else {
room = this.client.getRooms().find((r) => { room = this.client.getRooms().find((r) => {
return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias); return r.getCanonicalAlias() === alias || r.getAltAliases().includes(alias);
@ -691,7 +691,7 @@ export class CommandPartCreator extends PartCreator {
return new CommandPart(text, this.autoCompleteCreator); return new CommandPart(text, this.autoCompleteCreator);
} }
public deserializePart(part: SerializedPart): Part { public deserializePart(part: SerializedPart): Part | undefined {
if (part.type === Type.Command) { if (part.type === Type.Command) {
return this.command(part.text); return this.command(part.text);
} else { } else {

View file

@ -93,8 +93,8 @@ function reconcileLine(lineContainer: ChildNode, parts: Part[]): void {
if (needsCaretNodeBefore(part, prevPart)) { if (needsCaretNodeBefore(part, prevPart)) {
if (isCaretNode(currentNode as Element)) { if (isCaretNode(currentNode as Element)) {
updateCaretNode(currentNode); updateCaretNode(currentNode!);
currentNode = currentNode.nextSibling; currentNode = currentNode!.nextSibling;
} else { } else {
lineContainer.insertBefore(createCaretNode(), currentNode); lineContainer.insertBefore(createCaretNode(), currentNode);
} }

View file

@ -63,7 +63,7 @@ interface ISerializeOpts {
export function htmlSerializeIfNeeded( export function htmlSerializeIfNeeded(
model: EditorModel, model: EditorModel,
{ forceHTML = false, useMarkdown = true }: ISerializeOpts = {}, { forceHTML = false, useMarkdown = true }: ISerializeOpts = {},
): string { ): string | undefined {
if (!useMarkdown) { if (!useMarkdown) {
return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>"); return escapeHtml(textSerialize(model)).replace(/\n/g, "<br/>");
} }
@ -72,7 +72,7 @@ export function htmlSerializeIfNeeded(
return htmlSerializeFromMdIfNeeded(md, { forceHTML }); return htmlSerializeFromMdIfNeeded(md, { forceHTML });
} }
export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string { export function htmlSerializeFromMdIfNeeded(md: string, { forceHTML = false } = {}): string | undefined {
// copy of raw input to remove unwanted math later // copy of raw input to remove unwanted math later
const orig = md; const orig = md;

View file

@ -139,7 +139,7 @@ export const usePublicRoomDirectory = (): {
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) || SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer) SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
) { ) {
roomServer = lsRoomServer; roomServer = lsRoomServer!;
} }
let instanceId: string | undefined = undefined; let instanceId: string | undefined = undefined;

View file

@ -19,7 +19,7 @@ import { useEffect, useState } from "react";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
// Hook to fetch the value of a setting and dynamically update when it changes // Hook to fetch the value of a setting and dynamically update when it changes
export const useSettingValue = <T>(settingName: string, roomId: string = null, excludeDefault = false): T => { export const useSettingValue = <T>(settingName: string, roomId: string | null = null, excludeDefault = false): T => {
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault)); const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
useEffect(() => { useEffect(() => {
@ -36,7 +36,7 @@ export const useSettingValue = <T>(settingName: string, roomId: string = null, e
}; };
// Hook to fetch whether a feature is enabled and dynamically update when that changes // Hook to fetch whether a feature is enabled and dynamically update when that changes
export const useFeatureEnabled = (featureName: string, roomId: string = null): boolean => { export const useFeatureEnabled = (featureName: string, roomId: string | null = null): boolean => {
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId)); const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
useEffect(() => { useEffect(() => {

View file

@ -23,7 +23,7 @@ import { SlidingSyncManager } from "../SlidingSyncManager";
export interface SlidingSyncRoomSearchOpts { export interface SlidingSyncRoomSearchOpts {
limit: number; limit: number;
query?: string; query: string;
} }
export const useSlidingSyncRoomSearch = (): { export const useSlidingSyncRoomSearch = (): {
@ -55,7 +55,7 @@ export const useSlidingSyncRoomSearch = (): {
room_name_like: term, room_name_like: term,
}, },
}); });
const rooms = []; const rooms: Room[] = [];
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData( const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(
SlidingSyncManager.ListSearch, SlidingSyncManager.ListSearch,
)!; )!;

View file

@ -31,7 +31,7 @@ export const useTimeout = (handler: Handler, timeoutMs: number): void => {
// Set up timer // Set up timer
useEffect(() => { useEffect(() => {
const timeoutID = window.setTimeout(() => { const timeoutID = window.setTimeout(() => {
savedHandler.current(); savedHandler.current?.();
}, timeoutMs); }, timeoutMs);
return () => clearTimeout(timeoutID); return () => clearTimeout(timeoutID);
}, [timeoutMs]); }, [timeoutMs]);
@ -50,7 +50,7 @@ export const useInterval = (handler: Handler, intervalMs: number): void => {
// Set up timer // Set up timer
useEffect(() => { useEffect(() => {
const intervalID = window.setInterval(() => { const intervalID = window.setInterval(() => {
savedHandler.current(); savedHandler.current?.();
}, intervalMs); }, intervalMs);
return () => clearInterval(intervalID); return () => clearInterval(intervalID);
}, [intervalMs]); }, [intervalMs]);

View file

@ -22,7 +22,7 @@ import { useLatestResult } from "./useLatestResult";
export interface IUserDirectoryOpts { export interface IUserDirectoryOpts {
limit: number; limit: number;
query?: string; query: string;
} }
export const useUserDirectory = (): { export const useUserDirectory = (): {

View file

@ -27,6 +27,7 @@ import { IEventWithRoomId, IMatrixProfile, IResultRoomEvents } from "matrix-js-s
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
import PlatformPeg from "../PlatformPeg"; import PlatformPeg from "../PlatformPeg";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
@ -50,11 +51,11 @@ interface ICrawler {
*/ */
export default class EventIndex extends EventEmitter { export default class EventIndex extends EventEmitter {
private crawlerCheckpoints: ICrawlerCheckpoint[] = []; private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
private crawler: ICrawler = null; private crawler: ICrawler | null = null;
private currentCheckpoint: ICrawlerCheckpoint = null; private currentCheckpoint: ICrawlerCheckpoint | null = null;
public async init(): Promise<void> { public async init(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.crawlerCheckpoints = await indexManager.loadCheckpoints(); this.crawlerCheckpoints = await indexManager.loadCheckpoints();
logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints); logger.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);
@ -91,7 +92,7 @@ export default class EventIndex extends EventEmitter {
* Get crawler checkpoints for the encrypted rooms and store them in the index. * Get crawler checkpoints for the encrypted rooms and store them in the index.
*/ */
public async addInitialCheckpoints(): Promise<void> { public async addInitialCheckpoints(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const rooms = client.getRooms(); const rooms = client.getRooms();
@ -157,8 +158,8 @@ export default class EventIndex extends EventEmitter {
* - Every other sync, tell the event index to commit all the queued up * - Every other sync, tell the event index to commit all the queued up
* live events * live events
*/ */
private onSync = async (state: string, prevState: string, data: object): Promise<void> => { private onSync = async (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): Promise<void> => {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (prevState === "PREPARED" && state === "SYNCING") { if (prevState === "PREPARED" && state === "SYNCING") {
// If our indexer is empty we're most likely running Element the // If our indexer is empty we're most likely running Element the
@ -188,7 +189,7 @@ export default class EventIndex extends EventEmitter {
*/ */
private onRoomTimeline = async ( private onRoomTimeline = async (
ev: MatrixEvent, ev: MatrixEvent,
room: Room | null, room: Room | undefined,
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
removed: boolean, removed: boolean,
data: IRoomTimelineData, data: IRoomTimelineData,
@ -198,7 +199,7 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
// We only index encrypted rooms locally. // We only index encrypted rooms locally.
if (!client.isRoomEncrypted(ev.getRoomId())) return; if (!client.isRoomEncrypted(ev.getRoomId()!)) return;
if (ev.isRedaction()) { if (ev.isRedaction()) {
return this.redactEvent(ev); return this.redactEvent(ev);
@ -228,7 +229,7 @@ export default class EventIndex extends EventEmitter {
* We cannot rely on Room.redaction as this only fires if the redaction applied to an event the js-sdk has loaded. * We cannot rely on Room.redaction as this only fires if the redaction applied to an event the js-sdk has loaded.
*/ */
private redactEvent = async (ev: MatrixEvent): Promise<void> => { private redactEvent = async (ev: MatrixEvent): Promise<void> => {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
try { try {
await indexManager.deleteEvent(ev.getAssociatedId()); await indexManager.deleteEvent(ev.getAssociatedId());
@ -321,15 +322,15 @@ export default class EventIndex extends EventEmitter {
* @param {MatrixEvent} ev The event that should be added to the index. * @param {MatrixEvent} ev The event that should be added to the index.
*/ */
private async addLiveEventToIndex(ev: MatrixEvent): Promise<void> { private async addLiveEventToIndex(ev: MatrixEvent): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (!this.isValidEvent(ev)) return; if (!indexManager || !this.isValidEvent(ev)) return;
const e = this.eventToJson(ev); const e = this.eventToJson(ev);
const profile = { const profile = {
displayname: ev.sender.rawDisplayName, displayname: ev.sender?.rawDisplayName,
avatar_url: ev.sender.getMxcAvatarUrl(), avatar_url: ev.sender?.getMxcAvatarUrl(),
}; };
await indexManager.addEventToIndex(e, profile); await indexManager.addEventToIndex(e, profile);
@ -353,7 +354,7 @@ export default class EventIndex extends EventEmitter {
} }
private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise<void> { private async addRoomCheckpoint(roomId: string, fullCrawl = false): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
@ -401,7 +402,7 @@ export default class EventIndex extends EventEmitter {
let cancelled = false; let cancelled = false;
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.crawler = { this.crawler = {
cancel: () => { cancel: () => {
@ -649,7 +650,7 @@ export default class EventIndex extends EventEmitter {
* task, and closes the index. * task, and closes the index.
*/ */
public async close(): Promise<void> { public async close(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
this.removeListeners(); this.removeListeners();
this.stopCrawler(); this.stopCrawler();
await indexManager.closeEventIndex(); await indexManager.closeEventIndex();
@ -665,7 +666,7 @@ export default class EventIndex extends EventEmitter {
* of search results once the search is done. * of search results once the search is done.
*/ */
public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents> { public async search(searchArgs: ISearchArgs): Promise<IResultRoomEvents> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs); return indexManager.searchEventIndex(searchArgs);
} }
@ -693,11 +694,11 @@ export default class EventIndex extends EventEmitter {
public async loadFileEvents( public async loadFileEvents(
room: Room, room: Room,
limit = 10, limit = 10,
fromEvent: string = null, fromEvent?: string,
direction: string = EventTimeline.BACKWARDS, direction: string = EventTimeline.BACKWARDS,
): Promise<MatrixEvent[]> { ): Promise<MatrixEvent[]> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const loadArgs: ILoadArgs = { const loadArgs: ILoadArgs = {
roomId: room.roomId, roomId: room.roomId,
@ -790,7 +791,7 @@ export default class EventIndex extends EventEmitter {
timeline: EventTimeline, timeline: EventTimeline,
room: Room, room: Room,
limit = 10, limit = 10,
fromEvent: string = null, fromEvent?: string,
direction: string = EventTimeline.BACKWARDS, direction: string = EventTimeline.BACKWARDS,
): Promise<boolean> { ): Promise<boolean> {
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
@ -807,7 +808,7 @@ export default class EventIndex extends EventEmitter {
// Add the events to the timeline of the file panel. // Add the events to the timeline of the file panel.
matrixEvents.forEach((e) => { matrixEvents.forEach((e) => {
if (!timelineSet.eventIdToTimeline(e.getId())) { if (!timelineSet.eventIdToTimeline(e.getId()!)) {
timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS); timelineSet.addEventToTimeline(e, timeline, direction == EventTimeline.BACKWARDS);
} }
}); });
@ -817,7 +818,7 @@ export default class EventIndex extends EventEmitter {
// Set the pagination token to the oldest event that we retrieved. // Set the pagination token to the oldest event that we retrieved.
if (matrixEvents.length > 0) { if (matrixEvents.length > 0) {
paginationToken = matrixEvents[matrixEvents.length - 1].getId(); paginationToken = matrixEvents[matrixEvents.length - 1].getId()!;
ret = true; ret = true;
} }
@ -878,11 +879,11 @@ export default class EventIndex extends EventEmitter {
): Promise<boolean> => { ): Promise<boolean> => {
const timeline = timelineIndex.timeline; const timeline = timelineIndex.timeline;
const timelineSet = timeline.getTimelineSet(); const timelineSet = timeline.getTimelineSet();
const token = timeline.getPaginationToken(direction); const token = timeline.getPaginationToken(direction) ?? undefined;
const ret = await this.populateFileTimeline(timelineSet, timeline, room, limit, token, direction); const ret = await this.populateFileTimeline(timelineSet, timeline, room, limit, token, direction);
timelineIndex.pendingPaginate = null; timelineIndex.pendingPaginate = undefined;
timelineWindow.extend(direction, limit); timelineWindow.extend(direction, limit);
return ret; return ret;
@ -900,9 +901,9 @@ export default class EventIndex extends EventEmitter {
* @return {Promise<IIndexStats>} A promise that will resolve to the index * @return {Promise<IIndexStats>} A promise that will resolve to the index
* statistics. * statistics.
*/ */
public async getStats(): Promise<IIndexStats> { public async getStats(): Promise<IIndexStats | undefined> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager.getStats(); return indexManager?.getStats();
} }
/** /**
@ -914,9 +915,9 @@ export default class EventIndex extends EventEmitter {
* @return {Promise<boolean>} Returns true if the index contains events for * @return {Promise<boolean>} Returns true if the index contains events for
* the given room, false otherwise. * the given room, false otherwise.
*/ */
public async isRoomIndexed(roomId: string): Promise<boolean> { public async isRoomIndexed(roomId: string): Promise<boolean | undefined> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
return indexManager.isRoomIndexed(roomId); return indexManager?.isRoomIndexed(roomId);
} }
/** /**

View file

@ -36,8 +36,8 @@ const INDEX_VERSION = 1;
* you'll find a `EventIndex` hanging on the `EventIndexPeg`. * you'll find a `EventIndex` hanging on the `EventIndexPeg`.
*/ */
export class EventIndexPeg { export class EventIndexPeg {
public index: EventIndex = null; public index: EventIndex | null = null;
public error: Error = null; public error: Error | null = null;
private _supportIsInstalled = false; private _supportIsInstalled = false;
@ -49,7 +49,7 @@ export class EventIndexPeg {
* EventIndex was successfully initialized, false otherwise. * EventIndex was successfully initialized, false otherwise.
*/ */
public async init(): Promise<boolean> { public async init(): Promise<boolean> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (!indexManager) { if (!indexManager) {
logger.log("EventIndex: Platform doesn't support event indexing, not initializing."); logger.log("EventIndex: Platform doesn't support event indexing, not initializing.");
return false; return false;
@ -78,11 +78,14 @@ export class EventIndexPeg {
*/ */
public async initEventIndex(): Promise<boolean> { public async initEventIndex(): Promise<boolean> {
const index = new EventIndex(); const index = new EventIndex();
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!indexManager || !client) {
throw new Error("Unable to init event index");
}
const userId = client.getUserId(); const userId = client.getUserId()!;
const deviceId = client.getDeviceId(); const deviceId = client.getDeviceId()!;
try { try {
await indexManager.initEventIndex(userId, deviceId); await indexManager.initEventIndex(userId, deviceId);
@ -120,7 +123,7 @@ export class EventIndexPeg {
* does not mean that support is installed. * does not mean that support is installed.
*/ */
public platformHasSupport(): boolean { public platformHasSupport(): boolean {
return PlatformPeg.get().getEventIndexingManager() !== null; return PlatformPeg.get()?.getEventIndexingManager() != null;
} }
/** /**
@ -141,7 +144,7 @@ export class EventIndexPeg {
* *
* @return {EventIndex} The current event index. * @return {EventIndex} The current event index.
*/ */
public get(): EventIndex { public get(): EventIndex | null {
return this.index; return this.index;
} }
@ -178,9 +181,9 @@ export class EventIndexPeg {
* deleted. * deleted.
*/ */
public async deleteEventIndex(): Promise<void> { public async deleteEventIndex(): Promise<void> {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get()?.getEventIndexingManager();
if (indexManager !== null) { if (indexManager) {
await this.unset(); await this.unset();
logger.log("EventIndex: Deleting event index."); logger.log("EventIndex: Deleting event index.");
await indexManager.deleteEventIndex(); await indexManager.deleteEventIndex();

View file

@ -33,18 +33,13 @@ export enum Kind {
} }
export class IntegrationManagerInstance { export class IntegrationManagerInstance {
public readonly apiUrl: string;
public readonly uiUrl: string;
public readonly kind: string;
public readonly id: string; // only applicable in some cases
// Per the spec: UI URL is optional. // Per the spec: UI URL is optional.
public constructor(kind: string, apiUrl: string, uiUrl: string = apiUrl, id?: string) { public constructor(
this.kind = kind; public readonly kind: string,
this.apiUrl = apiUrl; public readonly apiUrl: string,
this.uiUrl = uiUrl; public readonly uiUrl: string = apiUrl,
this.id = id; public readonly id?: string, // only applicable in some cases
} ) {}
public get name(): string { public get name(): string {
const parsed = url.parse(this.uiUrl); const parsed = url.parse(this.uiUrl);
@ -62,7 +57,7 @@ export class IntegrationManagerInstance {
return new ScalarAuthClient(this.apiUrl, this.uiUrl); return new ScalarAuthClient(this.apiUrl, this.uiUrl);
} }
public async open(room: Room = null, screen: string = null, integrationId: string = null): Promise<void> { public async open(room: Room, screen?: string, integrationId?: string): Promise<void> {
if (!SettingsStore.getValue("integrationProvisioning")) { if (!SettingsStore.getValue("integrationProvisioning")) {
return IntegrationManagers.sharedInstance().showDisabledDialog(); return IntegrationManagers.sharedInstance().showDisabledDialog();
} }

View file

@ -40,7 +40,7 @@ export class IntegrationManagers {
private managers: IntegrationManagerInstance[] = []; private managers: IntegrationManagerInstance[] = [];
private client: MatrixClient; private client: MatrixClient;
private primaryManager: IntegrationManagerInstance; private primaryManager: IntegrationManagerInstance | null;
public static sharedInstance(): IntegrationManagers { public static sharedInstance(): IntegrationManagers {
if (!IntegrationManagers.instance) { if (!IntegrationManagers.instance) {
@ -146,7 +146,7 @@ export class IntegrationManagers {
} }
public getOrderedManagers(): IntegrationManagerInstance[] { public getOrderedManagers(): IntegrationManagerInstance[] {
const ordered = []; const ordered: IntegrationManagerInstance[] = [];
for (const kind of KIND_PREFERENCE) { for (const kind of KIND_PREFERENCE) {
const managers = this.managers.filter((m) => m.kind === kind); const managers = this.managers.filter((m) => m.kind === kind);
if (!managers || !managers.length) continue; if (!managers || !managers.length) continue;
@ -161,7 +161,7 @@ export class IntegrationManagers {
return ordered; return ordered;
} }
public getPrimaryManager(): IntegrationManagerInstance { public getPrimaryManager(): IntegrationManagerInstance | null {
if (this.hasManager()) { if (this.hasManager()) {
if (this.primaryManager) return this.primaryManager; if (this.primaryManager) return this.primaryManager;
@ -195,7 +195,7 @@ export class IntegrationManagers {
* @returns {Promise<IntegrationManagerInstance>} Resolves to an integration manager instance, * @returns {Promise<IntegrationManagerInstance>} Resolves to an integration manager instance,
* or null if none was found. * or null if none was found.
*/ */
public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance> { public async tryDiscoverManager(domainName: string): Promise<IntegrationManagerInstance | null> {
logger.log("Looking up integration manager via .well-known"); logger.log("Looking up integration manager via .well-known");
if (domainName.startsWith("http:") || domainName.startsWith("https:")) { if (domainName.startsWith("http:") || domainName.startsWith("https:")) {
// trim off the scheme and just use the domain // trim off the scheme and just use the domain

View file

@ -194,7 +194,7 @@ const annotateStrings = (result: TranslatedString, translationKey: string): Tran
*/ */
// eslint-next-line @typescript-eslint/naming-convention // eslint-next-line @typescript-eslint/naming-convention
export function _t(text: string, variables?: IVariables): string; export function _t(text: string, variables?: IVariables): string;
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode; export function _t(text: string, variables: IVariables | undefined, tags: Tags): React.ReactNode;
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString { export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const { translated } = safeCounterpartTranslate(text, variables); const { translated } = safeCounterpartTranslate(text, variables);

View file

@ -19,7 +19,7 @@ import * as linkifyjs from "linkifyjs";
import { Opts, registerCustomProtocol, registerPlugin } from "linkifyjs"; import { Opts, registerCustomProtocol, registerPlugin } from "linkifyjs";
import linkifyElement from "linkify-element"; import linkifyElement from "linkify-element";
import linkifyString from "linkify-string"; import linkifyString from "linkify-string";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { User } from "matrix-js-sdk/src/matrix";
import { import {
parsePermalink, parsePermalink,
@ -105,13 +105,9 @@ function matrixOpaqueIdLinkifyParser({
function onUserClick(event: MouseEvent, userId: string): void { function onUserClick(event: MouseEvent, userId: string): void {
event.preventDefault(); event.preventDefault();
const member = new RoomMember(null, userId);
if (!member) {
return;
}
dis.dispatch<ViewUserPayload>({ dis.dispatch<ViewUserPayload>({
action: Action.ViewUser, action: Action.ViewUser,
member: member, member: new User(userId),
}); });
} }

View file

@ -101,10 +101,10 @@ export class ProxiedModuleApi implements ModuleApi {
password: string, password: string,
displayName?: string, displayName?: string,
): Promise<AccountAuthInfo> { ): Promise<AccountAuthInfo> {
const hsUrl = SdkConfig.get("validated_server_config").hsUrl; const hsUrl = SdkConfig.get("validated_server_config")?.hsUrl;
const client = Matrix.createClient({ baseUrl: hsUrl }); const client = Matrix.createClient({ baseUrl: hsUrl });
const deviceName = const deviceName =
SdkConfig.get("default_device_display_name") || PlatformPeg.get().getDefaultDeviceDisplayName(); SdkConfig.get("default_device_display_name") || PlatformPeg.get()?.getDefaultDeviceDisplayName();
const req: IRegisterRequestParams = { const req: IRegisterRequestParams = {
username, username,
password, password,
@ -134,9 +134,9 @@ export class ProxiedModuleApi implements ModuleApi {
return { return {
homeserverUrl: hsUrl, homeserverUrl: hsUrl,
userId: creds.user_id, userId: creds.user_id!,
deviceId: creds.device_id, deviceId: creds.device_id!,
accessToken: creds.access_token, accessToken: creds.access_token!,
}; };
} }
@ -163,8 +163,8 @@ export class ProxiedModuleApi implements ModuleApi {
navigateToPermalink(uri); navigateToPermalink(uri);
const parts = parsePermalink(uri); const parts = parsePermalink(uri);
if (parts.roomIdOrAlias && andJoin) { if (parts?.roomIdOrAlias && andJoin) {
let roomId = parts.roomIdOrAlias; let roomId: string | undefined = parts.roomIdOrAlias;
let servers = parts.viaServers; let servers = parts.viaServers;
if (roomId.startsWith("#")) { if (roomId.startsWith("#")) {
roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias); roomId = getCachedRoomIDForAlias(parts.roomIdOrAlias);

View file

@ -53,6 +53,7 @@ export class PushRuleVectorState {
} else if (pushRuleVectorState === VectorState.Loud) { } else if (pushRuleVectorState === VectorState.Loud) {
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND; return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
} }
return [];
} }
/** /**

View file

@ -76,7 +76,7 @@ export class ConsoleLogger {
} }
public bypassRageshake(fnName: LogFunctionName, ...args: (Error | DOMException | object | string)[]): void { public bypassRageshake(fnName: LogFunctionName, ...args: (Error | DOMException | object | string)[]): void {
this.originalFunctions[fnName](...args); this.originalFunctions[fnName]?.(...args);
} }
public log(level: string, ...args: (Error | DOMException | object | string)[]): void { public log(level: string, ...args: (Error | DOMException | object | string)[]): void {
@ -152,7 +152,7 @@ export class IndexedDBLogStore {
}; };
req.onerror = () => { req.onerror = () => {
const err = "Failed to open log database: " + req.error.name; const err = "Failed to open log database: " + req.error?.name;
logger.error(err); logger.error(err);
reject(new Error(err)); reject(new Error(err));
}; };
@ -234,7 +234,7 @@ export class IndexedDBLogStore {
}; };
txn.onerror = () => { txn.onerror = () => {
logger.error("Failed to flush logs : ", txn.error); logger.error("Failed to flush logs : ", txn.error);
reject(new Error("Failed to write logs: " + txn.error.message)); reject(new Error("Failed to write logs: " + txn.error?.message));
}; };
objStore.add(this.generateLogEntry(lines)); objStore.add(this.generateLogEntry(lines));
const lastModStore = txn.objectStore("logslastmod"); const lastModStore = txn.objectStore("logslastmod");
@ -267,7 +267,7 @@ export class IndexedDBLogStore {
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev"); const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev");
let lines = ""; let lines = "";
query.onerror = () => { query.onerror = () => {
reject(new Error("Query failed: " + query.error.message)); reject(new Error("Query failed: " + query.error?.message));
}; };
query.onsuccess = () => { query.onsuccess = () => {
const cursor = query.result; const cursor = query.result;
@ -322,7 +322,7 @@ export class IndexedDBLogStore {
resolve(); resolve();
}; };
txn.onerror = () => { txn.onerror = () => {
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error.message}`)); reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error?.message}`));
}; };
// delete last modified entries // delete last modified entries
const lastModStore = txn.objectStore("logslastmod"); const lastModStore = txn.objectStore("logslastmod");
@ -401,14 +401,14 @@ export class IndexedDBLogStore {
*/ */
function selectQuery<T>( function selectQuery<T>(
store: IDBIndex | IDBObjectStore, store: IDBIndex | IDBObjectStore,
keyRange: IDBKeyRange, keyRange: IDBKeyRange | undefined,
resultMapper: (cursor: IDBCursorWithValue) => T, resultMapper: (cursor: IDBCursorWithValue) => T,
): Promise<T[]> { ): Promise<T[]> {
const query = store.openCursor(keyRange); const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const results: T[] = []; const results: T[] = [];
query.onerror = () => { query.onerror = () => {
reject(new Error("Query failed: " + query.error.message)); reject(new Error("Query failed: " + query.error?.message));
}; };
// collect results // collect results
query.onsuccess = () => { query.onsuccess = () => {

View file

@ -437,7 +437,7 @@ export default class SettingsStore {
level: SettingLevel, level: SettingLevel,
roomId: string | null, roomId: string | null,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
let resultingValue = calculatedValue; let resultingValue = calculatedValue;

View file

@ -36,7 +36,7 @@ export default class IncompatibleController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
if (this.incompatibleSetting) { if (this.incompatibleSetting) {
return this.forcedValue; return this.forcedValue;

View file

@ -53,7 +53,7 @@ export class NotificationsEnabledController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
if (!getNotifier().isPossible()) return false; if (!getNotifier().isPossible()) return false;

View file

@ -35,7 +35,7 @@ export class OrderedMultiController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
for (const controller of this.controllers) { for (const controller of this.controllers) {
const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel);

View file

@ -27,7 +27,7 @@ export default class ReducedMotionController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
if (this.prefersReducedMotion()) { if (this.prefersReducedMotion()) {
return false; return false;

View file

@ -41,7 +41,7 @@ export default abstract class SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string | null, roomId: string | null,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
return null; // no override return null; // no override
} }

View file

@ -26,7 +26,7 @@ export default class ThemeController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
if (!calculatedValue) return null; // Don't override null themes if (!calculatedValue) return null; // Don't override null themes

View file

@ -34,7 +34,7 @@ export default class UIFeatureController extends SettingController {
level: SettingLevel, level: SettingLevel,
roomId: string, roomId: string,
calculatedValue: any, calculatedValue: any,
calculatedAtLevel: SettingLevel, calculatedAtLevel: SettingLevel | null,
): any { ): any {
if (this.settingDisabled) { if (this.settingDisabled) {
// per the docs: we force a disabled state when the feature isn't active // per the docs: we force a disabled state when the feature isn't active

View file

@ -22,7 +22,7 @@ const SIZE_LARGE = { w: 800, h: 600 };
const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h const SIZE_NORMAL_LANDSCAPE = { w: 324, h: 324 }; // for w > h
const SIZE_NORMAL_PORTRAIT = { w: Math.ceil(324 * (9 / 16)), h: 324 }; // for h > w const SIZE_NORMAL_PORTRAIT = { w: Math.ceil(324 * (9 / 16)), h: 324 }; // for h > w
type Dimensions = { w: number; h: number }; type Dimensions = { w?: number; h?: number };
export enum ImageSize { export enum ImageSize {
Normal = "normal", Normal = "normal",
@ -36,7 +36,7 @@ export enum ImageSize {
* @returns {Dimensions} The suggested maximum dimensions for the image * @returns {Dimensions} The suggested maximum dimensions for the image
*/ */
export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions { export function suggestedSize(size: ImageSize, contentSize: Dimensions, maxHeight?: number): Dimensions {
const aspectRatio = contentSize.w / contentSize.h; const aspectRatio = contentSize.w! / contentSize.h!;
const portrait = aspectRatio < 1; const portrait = aspectRatio < 1;
const maxSize = size === ImageSize.Large ? SIZE_LARGE : portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE; const maxSize = size === ImageSize.Large ? SIZE_LARGE : portrait ? SIZE_NORMAL_PORTRAIT : SIZE_NORMAL_LANDSCAPE;

View file

@ -38,7 +38,7 @@ export class RoomScrollStateStore {
// from the focussedEvent. // from the focussedEvent.
private scrollStateMap = new Map<string, ScrollState>(); private scrollStateMap = new Map<string, ScrollState>();
public getScrollState(roomId: string): ScrollState { public getScrollState(roomId: string): ScrollState | undefined {
return this.scrollStateMap.get(roomId); return this.scrollStateMap.get(roomId);
} }

View file

@ -712,7 +712,7 @@ export class RoomViewStore extends EventEmitter {
} }
// The mxEvent if one is currently being replied to/quoted // The mxEvent if one is currently being replied to/quoted
public getQuotingEvent(): Optional<MatrixEvent> { public getQuotingEvent(): MatrixEvent | null {
return this.state.replyingToEvent; return this.state.replyingToEvent;
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync"; import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
import { Optional } from "matrix-events-sdk";
import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models"; import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models";
import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
@ -79,12 +80,11 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {}; private tagIdToSortAlgo: Record<TagID, SortAlgorithm> = {};
private tagMap: ITagMap = {}; private tagMap: ITagMap = {};
private counts: Record<TagID, number> = {}; private counts: Record<TagID, number> = {};
private stickyRoomId: string | null; private stickyRoomId: Optional<string>;
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) { public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
super(dis); super(dis);
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
this.stickyRoomId = null;
} }
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> { public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {

View file

@ -24,14 +24,17 @@ import SettingsStore from "../../../settings/SettingsStore";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
export class ReactionEventPreview implements IPreview { export class ReactionEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms"); const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all"); const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
const roomId = event.getRoomId();
if (!roomId) return null; // not a room event
// If we're not showing all reactions, see if we're showing DMs instead // If we're not showing all reactions, see if we're showing DMs instead
if (!showAll) { if (!showAll) {
// If we're not showing reactions on DMs, or we are and the room isn't a DM, skip // If we're not showing reactions on DMs, or we are and the room isn't a DM, skip
if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(event.getRoomId()))) { if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(roomId))) {
return null; return null;
} }
} }
@ -42,7 +45,7 @@ export class ReactionEventPreview implements IPreview {
const reaction = relation.key; const reaction = relation.key;
if (!reaction) return null; // invalid reaction (unknown format) if (!reaction) return null; // invalid reaction (unknown format)
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { if (isThread || isSelf(event) || !shouldPrefixMessagesIn(roomId, tagId)) {
return reaction; return reaction;
} else { } else {
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction }); return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });

View file

@ -27,7 +27,7 @@ export function isSelf(event: MatrixEvent): boolean {
return event.getSender() === selfUserId; return event.getSender() === selfUserId;
} }
export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean { export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean {
if (tagId !== DefaultTagID.DM) return true; if (tagId !== DefaultTagID.DM) return true;
// We don't prefix anything in 1:1s // We don't prefix anything in 1:1s

View file

@ -137,7 +137,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
userIdsBySpace: new Map<Room["roomId"], Set<string>>(), userIdsBySpace: new Map<Room["roomId"], Set<string>>(),
}; };
// The space currently selected in the Space Panel // The space currently selected in the Space Panel
private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady private _activeSpace: SpaceKey = MetaSpace.Home; // set properly by onReady
private _suggestedRooms: ISuggestedRoom[] = []; private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>(); private _invitedSpaces = new Set<Room>();
private spaceOrderLocalEchoMap = new Map<string, string>(); private spaceOrderLocalEchoMap = new Map<string, string>();
@ -812,13 +812,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const spaceDiff = mapDiff(prevChildSpacesBySpace, this.childSpacesBySpace); const spaceDiff = mapDiff(prevChildSpacesBySpace, this.childSpacesBySpace);
// filter out keys which changed by reference only by checking whether the sets differ // filter out keys which changed by reference only by checking whether the sets differ
const roomsChanged = roomDiff.changed.filter((k) => { const roomsChanged = roomDiff.changed.filter((k) => {
return setHasDiff(prevRoomsBySpace.get(k), this.roomIdsBySpace.get(k)); return setHasDiff(prevRoomsBySpace.get(k)!, this.roomIdsBySpace.get(k)!);
}); });
const usersChanged = userDiff.changed.filter((k) => { const usersChanged = userDiff.changed.filter((k) => {
return setHasDiff(prevUsersBySpace.get(k), this.userIdsBySpace.get(k)); return setHasDiff(prevUsersBySpace.get(k)!, this.userIdsBySpace.get(k)!);
}); });
const spacesChanged = spaceDiff.changed.filter((k) => { const spacesChanged = spaceDiff.changed.filter((k) => {
return setHasDiff(prevChildSpacesBySpace.get(k), this.childSpacesBySpace.get(k)); return setHasDiff(prevChildSpacesBySpace.get(k)!, this.childSpacesBySpace.get(k)!);
}); });
const changeSet = new Set([ const changeSet = new Set([
@ -1142,9 +1142,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// restore selected state from last session if any and still valid // restore selected state from last session if any and still valid
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY); const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
const valid = const valid =
lastSpaceId && !isMetaSpace(lastSpaceId) lastSpaceId &&
? this.matrixClient.getRoom(lastSpaceId) (!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]);
: enabledMetaSpaces[lastSpaceId];
if (valid) { if (valid) {
// don't context switch here as it may break permalinks // don't context switch here as it may break permalinks
this.setActiveSpace(lastSpaceId, false); this.setActiveSpace(lastSpaceId, false);
@ -1285,7 +1284,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public getNotificationState(key: SpaceKey): SpaceNotificationState { public getNotificationState(key: SpaceKey): SpaceNotificationState {
if (this.notificationStateMap.has(key)) { if (this.notificationStateMap.has(key)) {
return this.notificationStateMap.get(key); return this.notificationStateMap.get(key)!;
} }
const state = new SpaceNotificationState(getRoomFn); const state = new SpaceNotificationState(getRoomFn);

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const getSpaceCollapsedKey = (roomId: string, parents: Set<string>): string => { const getSpaceCollapsedKey = (roomId: string, parents?: Set<string>): string => {
const separator = "/"; const separator = "/";
let path = ""; let path = "";
if (parents) { if (parents) {
@ -35,12 +35,12 @@ export default class SpaceTreeLevelLayoutStore {
return SpaceTreeLevelLayoutStore.internalInstance; return SpaceTreeLevelLayoutStore.internalInstance;
} }
public setSpaceCollapsedState(roomId: string, parents: Set<string>, collapsed: boolean): void { public setSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, collapsed: boolean): void {
// XXX: localStorage doesn't allow booleans // XXX: localStorage doesn't allow booleans
localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString()); localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString());
} }
public getSpaceCollapsedState(roomId: string, parents: Set<string>, fallback: boolean): boolean { public getSpaceCollapsedState(roomId: string, parents: Set<string> | undefined, fallback: boolean): boolean {
const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents)); const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents));
// XXX: localStorage doesn't allow booleans // XXX: localStorage doesn't allow booleans
return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback; return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback;

View file

@ -216,8 +216,8 @@ export class StopGapWidget extends EventEmitter {
const defaults: ITemplateParams = { const defaults: ITemplateParams = {
widgetRoomId: this.roomId, widgetRoomId: this.roomId,
currentUserId: this.client.getUserId()!, currentUserId: this.client.getUserId()!,
userDisplayName: OwnProfileStore.instance.displayName, userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
clientId: ELEMENT_CLIENT_ID, clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"), clientTheme: SettingsStore.getValue("theme"),
clientLanguage: getUserLanguage(), clientLanguage: getUserLanguage(),

View file

@ -477,7 +477,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
this.updateUserLayout(room, newLayout); this.updateUserLayout(room, newLayout);
} }
public hasMaximisedWidget(room: Room): boolean { public hasMaximisedWidget(room?: Room): boolean {
return this.getContainerWidgets(room, Container.Center).length > 0; return this.getContainerWidgets(room, Container.Center).length > 0;
} }

View file

@ -85,11 +85,11 @@ export const showToast = (kind: Kind): void => {
const onAccept = async (): Promise<void> => { const onAccept = async (): Promise<void> => {
if (kind === Kind.VERIFY_THIS_SESSION) { if (kind === Kind.VERIFY_THIS_SESSION) {
Modal.createDialog(SetupEncryptionDialog, {}, null, /* priority = */ false, /* static = */ true); Modal.createDialog(SetupEncryptionDialog, {}, undefined, /* priority = */ false, /* static = */ true);
} else { } else {
const modal = Modal.createDialog( const modal = Modal.createDialog(
Spinner, Spinner,
null, undefined,
"mx_Dialog_spinner", "mx_Dialog_spinner",
/* priority */ false, /* priority */ false,
/* static */ true, /* static */ true,

View file

@ -19,6 +19,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { Policies } from "../Terms";
export function getDefaultIdentityServerUrl(): string { export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get("validated_server_config").isUrl; return SdkConfig.get("validated_server_config").isUrl;
@ -33,7 +34,7 @@ export function setToDefaultIdentityServer(): void {
} }
export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> { export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<boolean> {
let terms; let terms: { policies?: Policies } | null;
try { try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
} catch (e) { } catch (e) {
@ -45,7 +46,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<bool
} }
} }
return terms && terms["policies"] && Object.keys(terms["policies"]).length > 0; return !!terms?.["policies"] && Object.keys(terms["policies"]).length > 0;
} }
export function doesAccountDataHaveIdentityServer(): boolean { export function doesAccountDataHaveIdentityServer(): boolean {

View file

@ -28,12 +28,12 @@ import { IDestroyable } from "./IDestroyable";
export class MediaEventHelper implements IDestroyable { export class MediaEventHelper implements IDestroyable {
// Either an HTTP or Object URL (when encrypted) to the media. // Either an HTTP or Object URL (when encrypted) to the media.
public readonly sourceUrl: LazyValue<string>; public readonly sourceUrl: LazyValue<string | null>;
public readonly thumbnailUrl: LazyValue<string>; public readonly thumbnailUrl: LazyValue<string | null>;
// Either the raw or decrypted (when encrypted) contents of the file. // Either the raw or decrypted (when encrypted) contents of the file.
public readonly sourceBlob: LazyValue<Blob>; public readonly sourceBlob: LazyValue<Blob>;
public readonly thumbnailBlob: LazyValue<Blob>; public readonly thumbnailBlob: LazyValue<Blob | null>;
public readonly media: Media; public readonly media: Media;
@ -56,12 +56,12 @@ export class MediaEventHelper implements IDestroyable {
public destroy(): void { public destroy(): void {
if (this.media.isEncrypted) { if (this.media.isEncrypted) {
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue); if (this.sourceUrl.cachedValue) URL.revokeObjectURL(this.sourceUrl.cachedValue);
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue); if (this.thumbnailUrl.cachedValue) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
} }
} }
private prepareSourceUrl = async (): Promise<string> => { private prepareSourceUrl = async (): Promise<string | null> => {
if (this.media.isEncrypted) { if (this.media.isEncrypted) {
const blob = await this.sourceBlob.value; const blob = await this.sourceBlob.value;
return URL.createObjectURL(blob); return URL.createObjectURL(blob);
@ -70,7 +70,7 @@ export class MediaEventHelper implements IDestroyable {
} }
}; };
private prepareThumbnailUrl = async (): Promise<string> => { private prepareThumbnailUrl = async (): Promise<string | null> => {
if (this.media.isEncrypted) { if (this.media.isEncrypted) {
const blob = await this.thumbnailBlob.value; const blob = await this.thumbnailBlob.value;
if (blob === null) return null; if (blob === null) return null;
@ -83,12 +83,12 @@ export class MediaEventHelper implements IDestroyable {
private fetchSource = (): Promise<Blob> => { private fetchSource = (): Promise<Blob> => {
if (this.media.isEncrypted) { if (this.media.isEncrypted) {
const content = this.event.getContent<IMediaEventContent>(); const content = this.event.getContent<IMediaEventContent>();
return decryptFile(content.file, content.info); return decryptFile(content.file!, content.info);
} }
return this.media.downloadSource().then((r) => r.blob()); return this.media.downloadSource().then((r) => r.blob());
}; };
private fetchThumbnail = (): Promise<Blob> => { private fetchThumbnail = (): Promise<Blob | null> => {
if (!this.media.hasThumbnail) return Promise.resolve(null); if (!this.media.hasThumbnail) return Promise.resolve(null);
if (this.media.isEncrypted) { if (this.media.isEncrypted) {
@ -113,7 +113,7 @@ export class MediaEventHelper implements IDestroyable {
const content = event.getContent(); const content = event.getContent();
const mediaMsgTypes: string[] = [MsgType.Video, MsgType.Audio, MsgType.Image, MsgType.File]; const mediaMsgTypes: string[] = [MsgType.Video, MsgType.Audio, MsgType.Image, MsgType.File];
if (mediaMsgTypes.includes(content.msgtype)) return true; if (mediaMsgTypes.includes(content.msgtype!)) return true;
if (typeof content.url === "string") return true; if (typeof content.url === "string") return true;
// Finally, it's probably not media // Finally, it's probably not media

View file

@ -96,7 +96,7 @@ export default class WidgetUtils {
* @param {[type]} testUrlString URL to check * @param {[type]} testUrlString URL to check
* @return {Boolean} True if specified URL is a scalar URL * @return {Boolean} True if specified URL is a scalar URL
*/ */
public static isScalarUrl(testUrlString: string): boolean { public static isScalarUrl(testUrlString?: string): boolean {
if (!testUrlString) { if (!testUrlString) {
logger.error("Scalar URL check failed. No URL specified"); logger.error("Scalar URL check failed. No URL specified");
return false; return false;
@ -554,7 +554,7 @@ export default class WidgetUtils {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
IntegrationManagers.sharedInstance() IntegrationManagers.sharedInstance()
.getPrimaryManager() .getPrimaryManager()
.open(room, "type_" + app.type, app.id); ?.open(room, "type_" + app.type, app.id);
} }
public static isManagedByManager(app: IApp): boolean { public static isManagedByManager(app: IApp): boolean {
@ -563,7 +563,7 @@ export default class WidgetUtils {
if (managers.hasManager()) { if (managers.hasManager()) {
// TODO: Pick the right manager for the widget // TODO: Pick the right manager for the widget
const defaultManager = managers.getPrimaryManager(); const defaultManager = managers.getPrimaryManager();
return WidgetUtils.isScalarUrl(defaultManager.apiUrl); return WidgetUtils.isScalarUrl(defaultManager?.apiUrl);
} }
} }
return false; return false;

View file

@ -29,7 +29,7 @@ export const useBeacon = (beaconInfoEvent: MatrixEvent): Beacon | undefined => {
const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent); const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent);
const room = matrixClient.getRoom(roomId); const room = matrixClient.getRoom(roomId);
const beaconInstance = room.currentState.beacons.get(beaconIdentifier); const beaconInstance = room?.currentState.beacons.get(beaconIdentifier);
// TODO could this be less stupid? // TODO could this be less stupid?

View file

@ -173,7 +173,7 @@ export class ThreepidMember extends Member {
export interface IDMUserTileProps { export interface IDMUserTileProps {
member: Member; member: Member;
onRemove(member: Member): void; onRemove?(member: Member): void;
} }
/** /**

View file

@ -92,7 +92,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
type: EventType.RoomMember, type: EventType.RoomMember,
content: { content: {
displayname: target.name, displayname: target.name,
avatar_url: target.getMxcAvatarUrl(), avatar_url: target.getMxcAvatarUrl() ?? undefined,
membership: "invite", membership: "invite",
isDirect: true, isDirect: true,
}, },
@ -107,7 +107,7 @@ export async function createDmLocalRoom(client: MatrixClient, targets: Member[])
type: EventType.RoomMember, type: EventType.RoomMember,
content: { content: {
displayname: target.name, displayname: target.name,
avatar_url: target.getMxcAvatarUrl(), avatar_url: target.getMxcAvatarUrl() ?? undefined,
membership: "join", membership: "join",
}, },
state_key: target.userId, state_key: target.userId,

View file

@ -37,7 +37,7 @@ import { bulkSpaceBehaviour } from "./space";
import { SdkContextClass } from "../contexts/SDKContext"; import { SdkContextClass } from "../contexts/SDKContext";
export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true): Promise<void> { export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true): Promise<void> {
let spinnerModal: IHandle<any>; let spinnerModal: IHandle<any> | undefined;
if (spinner) { if (spinner) {
spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner"); spinnerModal = Modal.createDialog(Spinner, undefined, "mx_Dialog_spinner");
} }
@ -60,7 +60,7 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
room room
.getPendingEvents() .getPendingEvents()
.filter((ev) => { .filter((ev) => {
return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status); return [EventStatus.QUEUED, EventStatus.ENCRYPTING, EventStatus.SENDING].includes(ev.status!);
}) })
.map( .map(
(ev) => (ev) =>

View file

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
export const parseGeoUri = (uri: string): GeolocationCoordinates => { export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => {
function parse(s: string): number { function parse(s: string): number | undefined {
const ret = parseFloat(s); const ret = parseFloat(s);
if (Number.isNaN(ret)) { if (Number.isNaN(ret)) {
return undefined; return undefined;
@ -28,7 +28,7 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates => {
if (!m) return; if (!m) return;
const parts = m[1].split(";"); const parts = m[1].split(";");
const coords = parts[0].split(","); const coords = parts[0].split(",");
let uncertainty: number; let uncertainty: number | undefined;
for (const param of parts.slice(1)) { for (const param of parts.slice(1)) {
const m = param.match(/u=(.*)/); const m = param.match(/u=(.*)/);
if (m) uncertainty = parse(m[1]); if (m) uncertainty = parse(m[1]);

View file

@ -23,5 +23,5 @@ export function isLoggedIn(): boolean {
// store to hold this state. // store to hold this state.
// See also https://github.com/vector-im/element-web/issues/15034. // See also https://github.com/vector-im/element-web/issues/15034.
const app = window.matrixChat; const app = window.matrixChat;
return app && (app as MatrixChat).state.view === Views.LOGGED_IN; return (app as MatrixChat)?.state.view === Views.LOGGED_IN;
} }

View file

@ -38,7 +38,7 @@ import { parsePermalink } from "./permalinks/Permalinks";
* The initial caller should pass in an empty array to seed the accumulator. * The initial caller should pass in an empty array to seed the accumulator.
*/ */
export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]): void { export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pills: Element[]): void {
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()) ?? undefined;
const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
let node = nodes[0]; let node = nodes[0];
while (node) { while (node) {
@ -49,7 +49,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
node = node.nextSibling as Element; node = node.nextSibling as Element;
continue; continue;
} else if (node.tagName === "A" && node.getAttribute("href")) { } else if (node.tagName === "A" && node.getAttribute("href")) {
const href = node.getAttribute("href"); const href = node.getAttribute("href")!;
const parts = parsePermalink(href); const parts = parsePermalink(href);
// If the link is a (localised) matrix.to link, replace it with a pill // If the link is a (localised) matrix.to link, replace it with a pill
// We don't want to pill event permalinks, so those are ignored. // We don't want to pill event permalinks, so those are ignored.

View file

@ -71,17 +71,17 @@ export class VoiceBroadcastPlaybacksStore
} }
public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback { public getByInfoEvent(infoEvent: MatrixEvent, client: MatrixClient): VoiceBroadcastPlayback {
const infoEventId = infoEvent.getId(); const infoEventId = infoEvent.getId()!;
if (!this.playbacks.has(infoEventId)) { if (!this.playbacks.has(infoEventId)) {
this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings)); this.addPlayback(new VoiceBroadcastPlayback(infoEvent, client, this.recordings));
} }
return this.playbacks.get(infoEventId); return this.playbacks.get(infoEventId)!;
} }
private addPlayback(playback: VoiceBroadcastPlayback): void { private addPlayback(playback: VoiceBroadcastPlayback): void {
const infoEventId = playback.infoEvent.getId(); const infoEventId = playback.infoEvent.getId()!;
if (this.playbacks.has(infoEventId)) return; if (this.playbacks.has(infoEventId)) return;

View file

@ -57,7 +57,7 @@ describe("ContentMessages", () => {
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }), uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
} as unknown as MatrixClient; } as unknown as MatrixClient;
contentMessages = new ContentMessages(); contentMessages = new ContentMessages();
prom = Promise.resolve(null); prom = Promise.resolve<ISendEventResponse>({ event_id: "$event_id" });
}); });
describe("sendStickerContentToRoom", () => { describe("sendStickerContentToRoom", () => {
@ -98,7 +98,7 @@ describe("ContentMessages", () => {
mocked(doMaybeLocalRoomAction).mockImplementation( mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId), <T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
); );
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined); mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue("blurhashstring");
}); });
it("should use m.image for image files", async () => { it("should use m.image for image files", async () => {
@ -134,7 +134,7 @@ describe("ContentMessages", () => {
const element = createElement(tagName); const element = createElement(tagName);
if (tagName === "video") { if (tagName === "video") {
(<HTMLVideoElement>element).load = jest.fn(); (<HTMLVideoElement>element).load = jest.fn();
(<HTMLVideoElement>element).play = () => element.onloadeddata(new Event("loadeddata")); (<HTMLVideoElement>element).play = () => element.onloadeddata!(new Event("loadeddata"));
(<HTMLVideoElement>element).pause = jest.fn(); (<HTMLVideoElement>element).pause = jest.fn();
Object.defineProperty(element, "videoHeight", { Object.defineProperty(element, "videoHeight", {
get() { get() {
@ -200,8 +200,8 @@ describe("ContentMessages", () => {
expect(upload.loaded).toBe(0); expect(upload.loaded).toBe(0);
expect(upload.total).toBe(file.size); expect(upload.total).toBe(file.size);
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1]; const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1]!;
progressHandler({ loaded: 123, total: 1234 }); progressHandler!({ loaded: 123, total: 1234 });
expect(upload.loaded).toBe(123); expect(upload.loaded).toBe(123);
expect(upload.total).toBe(1234); expect(upload.total).toBe(1234);
await prom; await prom;
@ -256,11 +256,11 @@ describe("ContentMessages", () => {
mocked(client.uploadContent).mockReturnValue(deferred.promise); mocked(client.uploadContent).mockReturnValue(deferred.promise);
const file1 = new File([], "file1"); const file1 = new File([], "file1");
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined); const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
const { abortController } = mocked(client.uploadContent).mock.calls[0][1]; const { abortController } = mocked(client.uploadContent).mock.calls[0][1]!;
expect(abortController.signal.aborted).toBeFalsy(); expect(abortController!.signal.aborted).toBeFalsy();
const [upload] = contentMessages.getCurrentUploads(); const [upload] = contentMessages.getCurrentUploads();
contentMessages.cancelUpload(upload); contentMessages.cancelUpload(upload);
expect(abortController.signal.aborted).toBeTruthy(); expect(abortController!.signal.aborted).toBeTruthy();
deferred.resolve({} as UploadResponse); deferred.resolve({} as UploadResponse);
await prom; await prom;
}); });
@ -325,7 +325,7 @@ describe("uploadFile", () => {
const file = new Blob([]); const file = new Blob([]);
const prom = uploadFile(client, "!roomId:server", file); const prom = uploadFile(client, "!roomId:server", file);
mocked(client.uploadContent).mock.calls[0][1].abortController.abort(); mocked(client.uploadContent).mock.calls[0][1]!.abortController!.abort();
deferred.resolve({ content_uri: "mxc://foo/bar" }); deferred.resolve({ content_uri: "mxc://foo/bar" });
await expect(prom).rejects.toThrowError(UploadCanceledError); await expect(prom).rejects.toThrowError(UploadCanceledError);
}); });

View file

@ -134,12 +134,14 @@ But this is not
expect(getNestedReplyText(event, mockPermalinkGenerator)).toMatchSnapshot(); expect(getNestedReplyText(event, mockPermalinkGenerator)).toMatchSnapshot();
}); });
[ (
["m.room.message", MsgType.Location, LocationAssetType.Pin], [
["m.room.message", MsgType.Location, LocationAssetType.Self], ["m.room.message", MsgType.Location, LocationAssetType.Pin],
[M_BEACON_INFO.name, undefined, LocationAssetType.Pin], ["m.room.message", MsgType.Location, LocationAssetType.Self],
[M_BEACON_INFO.name, undefined, LocationAssetType.Self], [M_BEACON_INFO.name, undefined, LocationAssetType.Pin],
].forEach(([type, msgType, assetType]) => { [M_BEACON_INFO.name, undefined, LocationAssetType.Self],
] as const
).forEach(([type, msgType, assetType]) => {
it(`should create the expected fallback text for ${assetType} ${type}/${msgType}`, () => { it(`should create the expected fallback text for ${assetType} ${type}/${msgType}`, () => {
const event = makeTestEvent(type, { const event = makeTestEvent(type, {
body: "body", body: "body",

View file

@ -49,7 +49,7 @@ describe("Terms", function () {
beforeEach(function () { beforeEach(function () {
jest.clearAllMocks(); jest.clearAllMocks();
mockClient.getAccountData.mockReturnValue(null); mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getTerms.mockResolvedValue(null); mockClient.getTerms.mockResolvedValue(null);
mockClient.setAccountData.mockResolvedValue({}); mockClient.setAccountData.mockResolvedValue({});
}); });
@ -59,7 +59,7 @@ describe("Terms", function () {
}); });
it("should prompt for all terms & services if no account data", async function () { it("should prompt for all terms & services if no account data", async function () {
mockClient.getAccountData.mockReturnValue(null); mockClient.getAccountData.mockReturnValue(undefined);
mockClient.getTerms.mockResolvedValue({ mockClient.getTerms.mockResolvedValue({
policies: { policies: {
policy_the_first: POLICY_ONE, policy_the_first: POLICY_ONE,

View file

@ -34,7 +34,7 @@ describe("RoomStatusBar", () => {
stubClient(); stubClient();
client = MatrixClientPeg.get(); client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), { room = new Room(ROOM_ID, client, client.getUserId()!, {
pendingEventOrdering: PendingEventOrdering.Detached, pendingEventOrdering: PendingEventOrdering.Detached,
}); });
event = mkEvent({ event = mkEvent({
@ -72,7 +72,7 @@ describe("RoomStatusBar", () => {
length: 2, length: 2,
}); });
rootEvent.status = EventStatus.NOT_SENT; rootEvent.status = EventStatus.NOT_SENT;
room.addPendingEvent(rootEvent, rootEvent.getId()); room.addPendingEvent(rootEvent, rootEvent.getId()!);
for (const event of events) { for (const event of events) {
event.status = EventStatus.NOT_SENT; event.status = EventStatus.NOT_SENT;
room.addPendingEvent(event, Date.now() + Math.random() + ""); room.addPendingEvent(event, Date.now() + Math.random() + "");

View file

@ -99,10 +99,10 @@ describe("ThreadView", () => {
"is_falling_back": true, "is_falling_back": true,
"m.in_reply_to": { "m.in_reply_to": {
event_id: rootEvent event_id: rootEvent
.getThread() .getThread()!
.lastReply((ev: MatrixEvent) => { .lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name); return ev.isRelation(THREAD_RELATION_TYPE.name);
}) })!
.getId(), .getId(),
}, },
"rel_type": RelationType.Thread, "rel_type": RelationType.Thread,
@ -126,8 +126,8 @@ describe("ThreadView", () => {
const res = mkThread({ const res = mkThread({
room, room,
client: mockClient, client: mockClient,
authorId: mockClient.getUserId(), authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()], participantUserIds: [mockClient.getUserId()!],
}); });
rootEvent = res.rootEvent; rootEvent = res.rootEvent;
@ -154,8 +154,8 @@ describe("ThreadView", () => {
const { rootEvent: rootEvent2 } = mkThread({ const { rootEvent: rootEvent2 } = mkThread({
room, room,
client: mockClient, client: mockClient,
authorId: mockClient.getUserId(), authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()], participantUserIds: [mockClient.getUserId()!],
}); });
act(() => { act(() => {

View file

@ -54,8 +54,8 @@ describe("ThreadListContextMenu", () => {
const res = mkThread({ const res = mkThread({
room, room,
client: mockClient, client: mockClient,
authorId: mockClient.getUserId(), authorId: mockClient.getUserId()!,
participantUserIds: [mockClient.getUserId()], participantUserIds: [mockClient.getUserId()!],
}); });
event = res.rootEvent; event = res.rootEvent;

View file

@ -109,7 +109,7 @@ describe("<ExportDialog />", () => {
plainTextExporterInstance.export.mockClear(); plainTextExporterInstance.export.mockClear();
// default setting value // default setting value
ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({}); mocked(ChatExportMock.getForceChatExportParameters!).mockClear().mockReturnValue({});
}); });
it("renders export dialog", () => { it("renders export dialog", () => {
@ -145,7 +145,7 @@ describe("<ExportDialog />", () => {
}); });
it("exports room using values set from ForceRoomExportParameters", async () => { it("exports room using values set from ForceRoomExportParameters", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
format: ExportFormat.PlainText, format: ExportFormat.PlainText,
range: ExportType.Beginning, range: ExportType.Beginning,
sizeMb: 7000, sizeMb: 7000,
@ -198,7 +198,7 @@ describe("<ExportDialog />", () => {
}); });
it("does not render export format when set in ForceRoomExportParameters", () => { it("does not render export format when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
format: ExportFormat.PlainText, format: ExportFormat.PlainText,
}); });
const component = getComponent(); const component = getComponent();
@ -219,7 +219,7 @@ describe("<ExportDialog />", () => {
}); });
it("does not render export type when set in ForceRoomExportParameters", () => { it("does not render export type when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
range: ExportType.Beginning, range: ExportType.Beginning,
}); });
const component = getComponent(); const component = getComponent();
@ -310,7 +310,7 @@ describe("<ExportDialog />", () => {
}); });
it("does not render size limit input when set in ForceRoomExportParameters", () => { it("does not render size limit input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
sizeMb: 10000, sizeMb: 10000,
}); });
const component = getComponent(); const component = getComponent();
@ -321,7 +321,7 @@ describe("<ExportDialog />", () => {
* 2000mb size limit does not apply when higher limit is configured in config * 2000mb size limit does not apply when higher limit is configured in config
*/ */
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => { it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
sizeMb: 10000, sizeMb: 10000,
}); });
const component = getComponent(); const component = getComponent();
@ -344,7 +344,7 @@ describe("<ExportDialog />", () => {
}); });
it("does not render input when set in ForceRoomExportParameters", () => { it("does not render input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({ mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
includeAttachments: false, includeAttachments: false,
}); });
const component = getComponent(); const component = getComponent();

View file

@ -74,7 +74,7 @@ describe("<DevicesPanel />", () => {
const toggleDeviceSelection = (container: HTMLElement, deviceId: string) => const toggleDeviceSelection = (container: HTMLElement, deviceId: string) =>
act(() => { act(() => {
const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`); const checkbox = container.querySelector(`#device-tile-checkbox-${deviceId}`)!;
fireEvent.click(checkbox); fireEvent.click(checkbox);
}); });
@ -204,7 +204,7 @@ describe("<DevicesPanel />", () => {
// close the modal without submission // close the modal without submission
act(() => { act(() => {
const modalCloseButton = document.querySelector('[aria-label="Close dialog"]'); const modalCloseButton = document.querySelector('[aria-label="Close dialog"]')!;
fireEvent.click(modalCloseButton); fireEvent.click(modalCloseButton);
}); });

View file

@ -49,6 +49,7 @@ describe("PreferencesUserSettingsTab", () => {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => { jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
if (version === "v1.4") return val; if (version === "v1.4") return val;
return false;
}); });
}; };
@ -61,8 +62,12 @@ describe("PreferencesUserSettingsTab", () => {
}; };
}; };
const expectSetValueToHaveBeenCalled = (name: string, roomId: string, level: SettingLevel, value: boolean) => const expectSetValueToHaveBeenCalled = (
expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value); name: string,
roomId: string | undefined,
level: SettingLevel,
value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
describe("with server support", () => { describe("with server support", () => {
beforeEach(() => { beforeEach(() => {

View file

@ -923,7 +923,7 @@ describe("ElementCall", () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({ jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([sourceId])), finished: new Promise((r) => r([sourceId])),
} as IHandle<any[]>); } as IHandle<any[]>);
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true); jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect(); await call.connect();
@ -951,7 +951,7 @@ describe("ElementCall", () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({ jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([null])), finished: new Promise((r) => r([null])),
} as IHandle<any[]>); } as IHandle<any[]>);
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true); jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect(); await call.connect();
@ -976,7 +976,7 @@ describe("ElementCall", () => {
}); });
it("replies with pending: false if we don't support desktop capturer", async () => { it("replies with pending: false if we don't support desktop capturer", async () => {
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false); jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(false);
await call.connect(); await call.connect();

View file

@ -31,7 +31,7 @@ describe("ImageSize", () => {
expect(size).toStrictEqual({ w: 800, h: 400 }); expect(size).toStrictEqual({ w: 800, h: 400 });
}); });
it("returns max values if content size is not specified", () => { it("returns max values if content size is not specified", () => {
const size = suggestedSize(ImageSize.Normal, { w: null, h: null }); const size = suggestedSize(ImageSize.Normal, {});
expect(size).toStrictEqual({ w: 324, h: 324 }); expect(size).toStrictEqual({ w: 324, h: 324 });
}); });
it("returns integer values", () => { it("returns integer values", () => {

View file

@ -102,9 +102,9 @@ describe("OwnBeaconStore", () => {
}; };
const expireBeaconAndEmit = (store: OwnBeaconStore, beaconInfoEvent: MatrixEvent): void => { const expireBeaconAndEmit = (store: OwnBeaconStore, beaconInfoEvent: MatrixEvent): void => {
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent)); const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
// time travel until beacon is expired // time travel until beacon is expired
advanceDateAndTime(beacon.beaconInfo.timeout + 100); advanceDateAndTime(beacon.beaconInfo!.timeout + 100);
// force an update on the beacon // force an update on the beacon
// @ts-ignore // @ts-ignore
@ -118,13 +118,13 @@ describe("OwnBeaconStore", () => {
beaconInfoEvent: MatrixEvent, beaconInfoEvent: MatrixEvent,
isLive: boolean, isLive: boolean,
): void => { ): void => {
const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent)); const beacon = store.getBeaconById(getBeaconInfoIdentifier(beaconInfoEvent))!;
// matches original state of event content // matches original state of event content
// except for live property // except for live property
const updateEvent = makeBeaconInfoEvent( const updateEvent = makeBeaconInfoEvent(
beaconInfoEvent.getSender(), beaconInfoEvent.getSender()!,
beaconInfoEvent.getRoomId(), beaconInfoEvent.getRoomId()!,
{ isLive, timeout: beacon.beaconInfo.timeout }, { isLive, timeout: beacon.beaconInfo!.timeout },
"update-event-id", "update-event-id",
); );
beacon.update(updateEvent); beacon.update(updateEvent);
@ -236,12 +236,12 @@ describe("OwnBeaconStore", () => {
expect(mockClient.sendEvent).toHaveBeenCalledWith( expect(mockClient.sendEvent).toHaveBeenCalledWith(
room1Id, room1Id,
M_BEACON.name, M_BEACON.name,
makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()), makeBeaconContent(defaultLocationUri, now, alicesRoom1BeaconInfo.getId()!),
); );
expect(mockClient.sendEvent).toHaveBeenCalledWith( expect(mockClient.sendEvent).toHaveBeenCalledWith(
room2Id, room2Id,
M_BEACON.name, M_BEACON.name,
makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()), makeBeaconContent(defaultLocationUri, now, alicesRoom2BeaconInfo.getId()!),
); );
}); });
}); });
@ -263,7 +263,7 @@ describe("OwnBeaconStore", () => {
it("destroys beacons", async () => { it("destroys beacons", async () => {
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo]); const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo]);
const store = await makeOwnBeaconStore(); const store = await makeOwnBeaconStore();
const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo)); const beacon = room1.currentState.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
const destroySpy = jest.spyOn(beacon, "destroy"); const destroySpy = jest.spyOn(beacon, "destroy");
// @ts-ignore // @ts-ignore
store.onNotReady(); store.onNotReady();
@ -559,7 +559,7 @@ describe("OwnBeaconStore", () => {
const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo, alicesRoom2BeaconInfo]); const [room1] = makeRoomsWithStateEvents([alicesRoom1BeaconInfo, alicesRoom2BeaconInfo]);
const store = await makeOwnBeaconStore(); const store = await makeOwnBeaconStore();
const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo)); const room1BeaconInstance = store.beacons.get(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, "destroy"); const beaconDestroySpy = jest.spyOn(room1BeaconInstance, "destroy");
const emitSpy = jest.spyOn(store, "emit"); const emitSpy = jest.spyOn(store, "emit");
@ -610,7 +610,7 @@ describe("OwnBeaconStore", () => {
expect(store.hasLiveBeacons()).toBe(true); expect(store.hasLiveBeacons()).toBe(true);
const emitSpy = jest.spyOn(store, "emit"); const emitSpy = jest.spyOn(store, "emit");
const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo)); const beacon = store.getBeaconById(getBeaconInfoIdentifier(alicesRoom1BeaconInfo))!;
beacon.destroy(); beacon.destroy();
mockClient.emit(BeaconEvent.Destroy, beacon.identifier); mockClient.emit(BeaconEvent.Destroy, beacon.identifier);

View file

@ -103,10 +103,11 @@ describe("SpaceStore", () => {
const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true); const viewRoom = (roomId: string) => defaultDispatcher.dispatch({ action: Action.ViewRoom, room_id: roomId }, true);
const run = async () => { const run = async () => {
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId)); mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => [ mocked(client).getRoomUpgradeHistory.mockImplementation((roomId) => {
rooms.find((room) => room.roomId === roomId), const room = rooms.find((room) => room.roomId === roomId);
]); return room ? [room] : [];
});
await testUtils.setupAsyncStoreWithClient(store, client); await testUtils.setupAsyncStoreWithClient(store, client);
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
}; };
@ -312,10 +313,12 @@ describe("SpaceStore", () => {
mkSpace(space3, [invite2]); mkSpace(space3, [invite2]);
mkSpace(space4, [room4, fav2, space2, space3]); mkSpace(space4, [room4, fav2, space2, space3]);
mocked(client).getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId)); mocked(client).getRoom.mockImplementation(
(roomId) => rooms.find((room) => room.roomId === roomId) || null,
);
[fav1, fav2, fav3].forEach((roomId) => { [fav1, fav2, fav3].forEach((roomId) => {
client.getRoom(roomId).tags = { client.getRoom(roomId)!.tags = {
"m.favourite": { "m.favourite": {
order: 0.5, order: 0.5,
}, },
@ -323,20 +326,20 @@ describe("SpaceStore", () => {
}); });
[invite1, invite2].forEach((roomId) => { [invite1, invite2].forEach((roomId) => {
mocked(client.getRoom(roomId)).getMyMembership.mockReturnValue("invite"); mocked(client.getRoom(roomId)!).getMyMembership.mockReturnValue("invite");
}); });
// have dmPartner1 be in space1 with you // have dmPartner1 be in space1 with you
const mySpace1Member = new RoomMember(space1, testUserId); const mySpace1Member = new RoomMember(space1, testUserId);
mySpace1Member.membership = "join"; mySpace1Member.membership = "join";
(rooms.find((r) => r.roomId === space1).getMembers as jest.Mock).mockReturnValue([ (rooms.find((r) => r.roomId === space1)!.getMembers as jest.Mock).mockReturnValue([
mySpace1Member, mySpace1Member,
dm1Partner, dm1Partner,
]); ]);
// have dmPartner2 be in space2 with you // have dmPartner2 be in space2 with you
const mySpace2Member = new RoomMember(space2, testUserId); const mySpace2Member = new RoomMember(space2, testUserId);
mySpace2Member.membership = "join"; mySpace2Member.membership = "join";
(rooms.find((r) => r.roomId === space2).getMembers as jest.Mock).mockReturnValue([ (rooms.find((r) => r.roomId === space2)!.getMembers as jest.Mock).mockReturnValue([
mySpace2Member, mySpace2Member,
dm2Partner, dm2Partner,
]); ]);
@ -349,15 +352,15 @@ describe("SpaceStore", () => {
event: true, event: true,
type: EventType.SpaceParent, type: EventType.SpaceParent,
room: room2, room: room2,
user: client.getUserId(), user: client.getUserId()!,
skey: space2, skey: space2,
content: { via: [], canonical: true }, content: { via: [], canonical: true },
ts: Date.now(), ts: Date.now(),
}) as MatrixEvent, }) as MatrixEvent,
]); ]);
mocked(cliRoom2.currentState).getStateEvents.mockImplementation(room2MockStateEvents); mocked(cliRoom2!.currentState).getStateEvents.mockImplementation(room2MockStateEvents);
const cliSpace2 = client.getRoom(space2); const cliSpace2 = client.getRoom(space2);
mocked(cliSpace2.currentState).maySendStateEvent.mockImplementation( mocked(cliSpace2!.currentState).maySendStateEvent.mockImplementation(
(evType: string, userId: string) => { (evType: string, userId: string) => {
if (evType === EventType.SpaceChild) { if (evType === EventType.SpaceChild) {
return userId === client.getUserId(); return userId === client.getUserId();
@ -368,13 +371,13 @@ describe("SpaceStore", () => {
// room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions) // room 3 claims to be a child of space3 but is not due to invalid m.space.parent (permissions)
const cliRoom3 = client.getRoom(room3); const cliRoom3 = client.getRoom(room3);
mocked(cliRoom3.currentState).getStateEvents.mockImplementation( mocked(cliRoom3!.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([ testUtils.mockStateEventImplementation([
mkEvent({ mkEvent({
event: true, event: true,
type: EventType.SpaceParent, type: EventType.SpaceParent,
room: room3, room: room3,
user: client.getUserId(), user: client.getUserId()!,
skey: space3, skey: space3,
content: { via: [], canonical: true }, content: { via: [], canonical: true },
ts: Date.now(), ts: Date.now(),
@ -382,7 +385,7 @@ describe("SpaceStore", () => {
]), ]),
); );
const cliSpace3 = client.getRoom(space3); const cliSpace3 = client.getRoom(space3);
mocked(cliSpace3.currentState).maySendStateEvent.mockImplementation( mocked(cliSpace3!.currentState).maySendStateEvent.mockImplementation(
(evType: string, userId: string) => { (evType: string, userId: string) => {
if (evType === EventType.SpaceChild) { if (evType === EventType.SpaceChild) {
return false; return false;
@ -813,7 +816,7 @@ describe("SpaceStore", () => {
content: { membership: "join" }, content: { membership: "join" },
ts: Date.now(), ts: Date.now(),
}); });
const spaceRoom = client.getRoom(spaceId); const spaceRoom = client.getRoom(spaceId)!;
mocked(spaceRoom.currentState).getStateEvents.mockImplementation( mocked(spaceRoom.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([memberEvent]), testUtils.mockStateEventImplementation([memberEvent]),
); );
@ -929,7 +932,7 @@ describe("SpaceStore", () => {
it("switch to unknown space is a nop", async () => { it("switch to unknown space is a nop", async () => {
expect(store.activeSpace).toBe(MetaSpace.Home); expect(store.activeSpace).toBe(MetaSpace.Home);
const space = client.getRoom(room1); // not a space const space = client.getRoom(room1)!; // not a space
store.setActiveSpace(space.roomId); store.setActiveSpace(space.roomId);
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId);
expect(store.activeSpace).toBe(MetaSpace.Home); expect(store.activeSpace).toBe(MetaSpace.Home);
@ -962,6 +965,7 @@ describe("SpaceStore", () => {
member.membership = "join"; member.membership = "join";
return member; return member;
} }
return null;
}); });
client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner); client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner);
@ -1088,7 +1092,7 @@ describe("SpaceStore", () => {
mkSpace(space1, [room1, room2, room3]); mkSpace(space1, [room1, room2, room3]);
mkSpace(space2, [room1, room2]); mkSpace(space2, [room1, room2]);
const cliRoom2 = client.getRoom(room2); const cliRoom2 = client.getRoom(room2)!;
mocked(cliRoom2.currentState).getStateEvents.mockImplementation( mocked(cliRoom2.currentState).getStateEvents.mockImplementation(
testUtils.mockStateEventImplementation([ testUtils.mockStateEventImplementation([
mkEvent({ mkEvent({
@ -1267,8 +1271,9 @@ describe("SpaceStore", () => {
case dm1Partner.userId: case dm1Partner.userId:
return rootSpaceFriend; return rootSpaceFriend;
} }
return null;
}); });
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeFalsy(); expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeFalsy();
const memberEvent = mkEvent({ const memberEvent = mkEvent({
event: true, event: true,
type: EventType.RoomMember, type: EventType.RoomMember,
@ -1281,7 +1286,7 @@ describe("SpaceStore", () => {
}); });
client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner); client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner);
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy(); expect(SpaceStore.instance.getSpaceFilteredUserIds(space1)!.has(dm1Partner.userId)).toBeTruthy();
const dm1Room = mkRoom(dm1); const dm1Room = mkRoom(dm1);
dm1Room.getMyMembership.mockReturnValue("join"); dm1Room.getMyMembership.mockReturnValue("join");
client.emit(ClientEvent.Room, dm1Room); client.emit(ClientEvent.Room, dm1Room);

View file

@ -198,10 +198,10 @@ describe("SlidingRoomListStore", () => {
return keyToListData[key] || null; return keyToListData[key] || null;
}); });
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()))).toEqual([ expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()!))).toEqual([
DefaultTagID.Untagged, DefaultTagID.Untagged,
]); ]);
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()))).toEqual([ expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()!))).toEqual([
DefaultTagID.Favourite, DefaultTagID.Favourite,
DefaultTagID.Untagged, DefaultTagID.Untagged,
]); ]);
@ -221,9 +221,9 @@ describe("SlidingRoomListStore", () => {
0: roomA, 0: roomA,
}; };
const rooms = [ const rooms = [
new Room(roomA, context.client!, context.client!.getUserId()), new Room(roomA, context.client!, context.client!.getUserId()!),
new Room(roomB, context.client!, context.client!.getUserId()), new Room(roomB, context.client!, context.client!.getUserId()!),
new Room(roomC, context.client!, context.client!.getUserId()), new Room(roomC, context.client!, context.client!.getUserId()!),
]; ];
mocked(context.client!.getRoom).mockImplementation((roomId: string) => { mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) { switch (roomId) {
@ -257,9 +257,9 @@ describe("SlidingRoomListStore", () => {
2: roomIdC, 2: roomIdC,
0: roomIdA, 0: roomIdA,
}; };
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()); const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId()); const roomB = new Room(roomIdB, context.client!, context.client!.getUserId()!);
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()); const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
mocked(context.client!.getRoom).mockImplementation((roomId: string) => { mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) { switch (roomId) {
case roomIdA: case roomIdA:
@ -321,8 +321,8 @@ describe("SlidingRoomListStore", () => {
const tagId = DefaultTagID.Favourite; const tagId = DefaultTagID.Favourite;
const joinCount = 10; const joinCount = 10;
// seed the store with 2 rooms // seed the store with 2 rooms
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()); const roomA = new Room(roomIdA, context.client!, context.client!.getUserId()!);
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()); const roomC = new Room(roomIdC, context.client!, context.client!.getUserId()!);
mocked(context.client!.getRoom).mockImplementation((roomId: string) => { mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
switch (roomId) { switch (roomId) {
case roomIdA: case roomIdA:

View file

@ -29,11 +29,13 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition"; import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition";
import DMRoomMap from "../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../src/utils/DMRoomMap";
let filter: SpaceFilterCondition = null; let filter: SpaceFilterCondition | null = null;
const mockRoomListStore = { const mockRoomListStore = {
addFilter: (f: SpaceFilterCondition) => (filter = f), addFilter: (f: SpaceFilterCondition) => (filter = f),
removeFilter: (): void => (filter = null), removeFilter: (): void => {
filter = null;
},
} as unknown as RoomListStoreClass; } as unknown as RoomListStoreClass;
const getUserIdForRoomId = jest.fn(); const getUserIdForRoomId = jest.fn();
@ -74,7 +76,7 @@ describe("SpaceWatcher", () => {
[MetaSpace.Orphans]: true, [MetaSpace.Orphans]: true,
}); });
client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId)); client.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
await setupAsyncStoreWithClient(store, client); await setupAsyncStoreWithClient(store, client);
}); });
@ -99,7 +101,7 @@ describe("SpaceWatcher", () => {
await setShowAllRooms(false); await setShowAllRooms(false);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Home); expect(filter!["space"]).toBe(MetaSpace.Home);
}); });
it("sets filter correctly for all -> space transition", async () => { it("sets filter correctly for all -> space transition", async () => {
@ -109,7 +111,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1); SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
}); });
it("removes filter for home -> all transition", async () => { it("removes filter for home -> all transition", async () => {
@ -128,7 +130,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1); SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
}); });
it("removes filter for space -> all transition", async () => { it("removes filter for space -> all transition", async () => {
@ -137,7 +139,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(space1); SpaceStore.instance.setActiveSpace(space1);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Home); SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull(); expect(filter).toBeNull();
@ -149,7 +151,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites); SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Favourites); expect(filter!["space"]).toBe(MetaSpace.Favourites);
SpaceStore.instance.setActiveSpace(MetaSpace.Home); SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull(); expect(filter).toBeNull();
@ -161,7 +163,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.People); SpaceStore.instance.setActiveSpace(MetaSpace.People);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.People); expect(filter!["space"]).toBe(MetaSpace.People);
SpaceStore.instance.setActiveSpace(MetaSpace.Home); SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull(); expect(filter).toBeNull();
@ -173,7 +175,7 @@ describe("SpaceWatcher", () => {
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans); expect(filter!["space"]).toBe(MetaSpace.Orphans);
SpaceStore.instance.setActiveSpace(MetaSpace.Home); SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeNull(); expect(filter).toBeNull();
@ -185,11 +187,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Home); SpaceStore.instance.setActiveSpace(MetaSpace.Home);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Home); expect(filter!["space"]).toBe(MetaSpace.Home);
}); });
it("updates filter correctly for space -> orphans transition", async () => { it("updates filter correctly for space -> orphans transition", async () => {
@ -198,11 +200,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); SpaceStore.instance.setActiveSpace(MetaSpace.Orphans);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans); expect(filter!["space"]).toBe(MetaSpace.Orphans);
}); });
it("updates filter correctly for orphans -> people transition", async () => { it("updates filter correctly for orphans -> people transition", async () => {
@ -211,11 +213,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.Orphans); expect(filter!["space"]).toBe(MetaSpace.Orphans);
SpaceStore.instance.setActiveSpace(MetaSpace.People); SpaceStore.instance.setActiveSpace(MetaSpace.People);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(MetaSpace.People); expect(filter!["space"]).toBe(MetaSpace.People);
}); });
it("updates filter correctly for space -> space transition", async () => { it("updates filter correctly for space -> space transition", async () => {
@ -224,11 +226,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
SpaceStore.instance.setActiveSpace(space2); SpaceStore.instance.setActiveSpace(space2);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space2); expect(filter!["space"]).toBe(space2);
}); });
it("doesn't change filter when changing showAllRooms mode to true", async () => { it("doesn't change filter when changing showAllRooms mode to true", async () => {
@ -237,11 +239,11 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
await setShowAllRooms(true); await setShowAllRooms(true);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
}); });
it("doesn't change filter when changing showAllRooms mode to false", async () => { it("doesn't change filter when changing showAllRooms mode to false", async () => {
@ -250,10 +252,10 @@ describe("SpaceWatcher", () => {
new SpaceWatcher(mockRoomListStore); new SpaceWatcher(mockRoomListStore);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
await setShowAllRooms(false); await setShowAllRooms(false);
expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter).toBeInstanceOf(SpaceFilterCondition);
expect(filter["space"]).toBe(space1); expect(filter!["space"]).toBe(space1);
}); });
}); });

View file

@ -109,14 +109,14 @@ describe("VisibilityProvider", () => {
}); });
it("should return false if visibility customisation returns false", () => { it("should return false if visibility customisation returns false", () => {
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(false); mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(false);
const room = createRoom(); const room = createRoom();
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false); expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(false);
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room); expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);
}); });
it("should return true if visibility customisation returns true", () => { it("should return true if visibility customisation returns true", () => {
mocked(RoomListCustomisations.isRoomVisible).mockReturnValue(true); mocked(RoomListCustomisations.isRoomVisible!).mockReturnValue(true);
const room = createRoom(); const room = createRoom();
expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true); expect(VisibilityProvider.instance.isRoomVisible(room)).toBe(true);
expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room); expect(RoomListCustomisations.isRoomVisible).toHaveBeenCalledWith(room);

View file

@ -80,8 +80,8 @@ describe("StopGapWidget", () => {
beforeEach(() => { beforeEach(() => {
voiceBroadcastInfoEvent = mkEvent({ voiceBroadcastInfoEvent = mkEvent({
event: true, event: true,
room: client.getRoom("x").roomId, room: client.getRoom("x")?.roomId,
user: client.getUserId(), user: client.getUserId()!,
type: VoiceBroadcastInfoEventType, type: VoiceBroadcastInfoEventType,
content: {}, content: {},
}); });

View file

@ -59,22 +59,22 @@ describe("WidgetPermissionStore", () => {
}); });
it("should persist OIDCState.Allowed for a widget", () => { it("should persist OIDCState.Allowed for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed); widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
// check it remembered the value // check it remembered the value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Allowed); expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Allowed);
}); });
it("should persist OIDCState.Denied for a widget", () => { it("should persist OIDCState.Denied for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied); widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
// check it remembered the value // check it remembered the value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied); expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
}); });
it("should update OIDCState for a widget", () => { it("should update OIDCState for a widget", () => {
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed); widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Allowed);
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied); widgetPermissionStore.setOIDCState(w, WidgetKind.Account, roomId, OIDCState.Denied);
// check it remembered the latest value // check it remembered the latest value
expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null)).toEqual(OIDCState.Denied); expect(widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId)).toEqual(OIDCState.Denied);
}); });
it("should scope the location for a widget when setting OIDC state", () => { it("should scope the location for a widget when setting OIDC state", () => {

View file

@ -197,7 +197,7 @@ export const makeRoomWithBeacons = (
locationEvents?: MatrixEvent[], locationEvents?: MatrixEvent[],
): Beacon[] => { ): Beacon[] => {
const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient }); const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient });
const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event))); const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event))!);
if (locationEvents) { if (locationEvents) {
beacons.forEach((beacon) => { beacons.forEach((beacon) => {
// this filtering happens in roomState, which is bypassed here // this filtering happens in roomState, which is bypassed here

View file

@ -31,7 +31,7 @@ export const addTextToComposer = (container: HTMLElement, text: string) =>
getData: (type: string) => (type === "text/plain" ? text : undefined), getData: (type: string) => (type === "text/plain" ? text : undefined),
} as unknown as DataTransfer, } as unknown as DataTransfer,
}; };
fireEvent.paste(container.querySelector('[role="textbox"]'), pasteEvent); fireEvent.paste(container.querySelector('[role="textbox"]')!, pasteEvent);
}); });
export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) => export const addTextToComposerEnzyme = (wrapper: ReactWrapper, text: string) =>

View file

@ -37,7 +37,7 @@ export function untilDispatch(
dispatcher = defaultDispatcher, dispatcher = defaultDispatcher,
timeout = 1000, timeout = 1000,
): Promise<ActionPayload> { ): Promise<ActionPayload> {
const callerLine = new Error().stack.toString().split("\n")[2]; const callerLine = new Error().stack!.toString().split("\n")[2];
if (typeof waitForAction === "string") { if (typeof waitForAction === "string") {
const action = waitForAction; const action = waitForAction;
waitForAction = (payload) => { waitForAction = (payload) => {
@ -89,10 +89,10 @@ export function untilDispatch(
export function untilEmission( export function untilEmission(
emitter: EventEmitter, emitter: EventEmitter,
eventName: string, eventName: string,
check: (...args: any[]) => boolean = undefined, check?: (...args: any[]) => boolean,
timeout = 1000, timeout = 1000,
): Promise<void> { ): Promise<void> {
const callerLine = new Error().stack.toString().split("\n")[2]; const callerLine = new Error().stack!.toString().split("\n")[2];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let fulfilled = false; let fulfilled = false;
let timeoutId: number; let timeoutId: number;

View file

@ -58,7 +58,7 @@ describe("theme", () => {
// When // When
await new Promise((resolve) => { await new Promise((resolve) => {
setTheme("light").then(resolve); setTheme("light").then(resolve);
lightTheme.onload(void 0); lightTheme.onload!({} as Event);
}); });
// Then // Then
@ -72,7 +72,7 @@ describe("theme", () => {
return expect( return expect(
new Promise((resolve) => { new Promise((resolve) => {
setTheme("light").catch((e) => resolve(e)); setTheme("light").catch((e) => resolve(e));
lightTheme.onerror("call onerror"); lightTheme.onerror!("call onerror");
}), }),
).resolves.toBe("call onerror"); ).resolves.toBe("call onerror");
}); });

View file

@ -42,7 +42,7 @@ describe("useTopic", () => {
function RoomTopic() { function RoomTopic() {
const topic = useTopic(room); const topic = useTopic(room);
return <p>{topic.text}</p>; return <p>{topic!.text}</p>;
} }
render(<RoomTopic />); render(<RoomTopic />);

View file

@ -85,10 +85,6 @@ describe("MegolmExportEncryption", function () {
MegolmExportEncryption = require("../../src/utils/MegolmExportEncryption"); MegolmExportEncryption = require("../../src/utils/MegolmExportEncryption");
}); });
afterAll(() => {
window.crypto = undefined;
});
describe("decrypt", function () { describe("decrypt", function () {
it("should handle missing header", function () { it("should handle missing header", function () {
const input = stringToArray(`-----`); const input = stringToArray(`-----`);

Some files were not shown because too many files have changed in this diff Show more