Conform more code to strict null checking (#10153)

* Conform more code to strict null checking

* Conform more code to strict null checking

* Iterate

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

View file

@ -193,7 +193,7 @@ export default abstract class BasePlatform {
public displayNotification( public displayNotification(
title: string, title: string,
msg: string, msg: string,
avatarUrl: string, avatarUrl: string | null,
room: Room, room: Room,
ev?: MatrixEvent, ev?: MatrixEvent,
): Notification { ): Notification {

View file

@ -33,7 +33,7 @@ export interface IModal<T extends any[]> {
beforeClosePromise?: Promise<boolean>; beforeClosePromise?: Promise<boolean>;
closeReason?: string; closeReason?: string;
onBeforeClose?(reason?: string): Promise<boolean>; onBeforeClose?(reason?: string): Promise<boolean>;
onFinished(...args: T): void; onFinished?(...args: T): void;
close(...args: T): void; close(...args: T): void;
hidden?: boolean; hidden?: boolean;
} }
@ -68,11 +68,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// The modal to prioritise over all others. If this is set, only show // The modal to prioritise over all others. If this is set, only show
// this modal. Remove all other modals from the stack when this modal // this modal. Remove all other modals from the stack when this modal
// is closed. // is closed.
private priorityModal: IModal<any> = null; private priorityModal: IModal<any> | null = null;
// The modal to keep open underneath other modals if possible. Useful // The modal to keep open underneath other modals if possible. Useful
// for cases like Settings where the modal should remain open while the // for cases like Settings where the modal should remain open while the
// user is prompted for more information/errors. // user is prompted for more information/errors.
private staticModal: IModal<any> = null; private staticModal: IModal<any> | null = null;
// A list of the modals we have stacked up, with the most recent at [0] // A list of the modals we have stacked up, with the most recent at [0]
// Neither the static nor priority modal will be in this list. // Neither the static nor priority modal will be in this list.
private modals: IModal<any>[] = []; private modals: IModal<any>[] = [];
@ -144,17 +144,14 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
closeDialog: IHandle<T>["close"]; closeDialog: IHandle<T>["close"];
onFinishedProm: IHandle<T>["finished"]; onFinishedProm: IHandle<T>["finished"];
} { } {
const modal: IModal<T> = { const modal = {
onFinished: props ? props.onFinished : null, onFinished: props?.onFinished,
onBeforeClose: options.onBeforeClose, onBeforeClose: options?.onBeforeClose,
beforeClosePromise: null,
closeReason: null,
className, className,
// these will be set below but we need an object reference to pass to getCloseFn before we can do that // these will be set below but we need an object reference to pass to getCloseFn before we can do that
elem: null, elem: null,
close: null, } as IModal<T>;
};
// never call this from onFinished() otherwise it will loop // never call this from onFinished() otherwise it will loop
const [closeDialog, onFinishedProm] = this.getCloseFn<T>(modal, props); const [closeDialog, onFinishedProm] = this.getCloseFn<T>(modal, props);
@ -173,7 +170,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
private getCloseFn<T extends any[]>( private getCloseFn<T extends any[]>(
modal: IModal<T>, modal: IModal<T>,
props: IProps<T>, props?: IProps<T>,
): [IHandle<T>["close"], IHandle<T>["finished"]] { ): [IHandle<T>["close"], IHandle<T>["finished"]] {
const deferred = defer<T>(); const deferred = defer<T>();
return [ return [
@ -183,13 +180,13 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
} else if (modal.onBeforeClose) { } else if (modal.onBeforeClose) {
modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason); modal.beforeClosePromise = modal.onBeforeClose(modal.closeReason);
const shouldClose = await modal.beforeClosePromise; const shouldClose = await modal.beforeClosePromise;
modal.beforeClosePromise = null; modal.beforeClosePromise = undefined;
if (!shouldClose) { if (!shouldClose) {
return; return;
} }
} }
deferred.resolve(args); deferred.resolve(args);
if (props && props.onFinished) props.onFinished.apply(null, args); if (props?.onFinished) props.onFinished.apply(null, args);
const i = this.modals.indexOf(modal); const i = this.modals.indexOf(modal);
if (i >= 0) { if (i >= 0) {
this.modals.splice(i, 1); this.modals.splice(i, 1);
@ -317,7 +314,7 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
// so, pass the reason to close through a member variable // so, pass the reason to close through a member variable
modal.closeReason = "backgroundClick"; modal.closeReason = "backgroundClick";
modal.close(); modal.close();
modal.closeReason = null; modal.closeReason = undefined;
}; };
private getCurrentModal(): IModal<any> { private getCurrentModal(): IModal<any> {

View file

@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype
This is useful when the content body contains fallback text that would explain that the client can't handle a particular This is useful when the content body contains fallback text that would explain that the client can't handle a particular
type of tile. type of tile.
*/ */
const msgTypeHandlers: Record<string, (event: MatrixEvent) => string> = { const msgTypeHandlers: Record<string, (event: MatrixEvent) => string | null> = {
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => { [MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
const name = (event.sender || {}).name; const name = (event.sender || {}).name;
return _t("%(name)s is requesting verification", { name }); return _t("%(name)s is requesting verification", { name });
@ -156,7 +156,7 @@ class NotifierClass {
msg = ""; msg = "";
} }
let avatarUrl = null; let avatarUrl: string | null = null;
if (ev.sender && !SettingsStore.getValue("lowBandwidth")) { if (ev.sender && !SettingsStore.getValue("lowBandwidth")) {
avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop"); avatarUrl = Avatar.avatarUrlForMember(ev.sender, 40, 40, "crop");
} }
@ -166,8 +166,8 @@ class NotifierClass {
// if displayNotification returns non-null, the platform supports // if displayNotification returns non-null, the platform supports
// clearing notifications later, so keep track of this. // clearing notifications later, so keep track of this.
if (notif) { if (notif) {
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = []; if (this.notifsByRoom[ev.getRoomId()!] === undefined) this.notifsByRoom[ev.getRoomId()!] = [];
this.notifsByRoom[ev.getRoomId()].push(notif); this.notifsByRoom[ev.getRoomId()!].push(notif);
} }
} }
@ -219,7 +219,7 @@ class NotifierClass {
sound ? `audio[src='${sound.url}']` : "#messageAudio", sound ? `audio[src='${sound.url}']` : "#messageAudio",
); );
let audioElement = selector; let audioElement = selector;
if (!selector) { if (!audioElement) {
if (!sound) { if (!sound) {
logger.error("No audio element or sound to play for notification"); logger.error("No audio element or sound to play for notification");
return; return;
@ -378,11 +378,11 @@ class NotifierClass {
return global.localStorage.getItem("notifications_hidden") === "true"; return global.localStorage.getItem("notifications_hidden") === "true";
} }
return this.toolbarHidden; return !!this.toolbarHidden;
} }
// XXX: Exported for tests // XXX: Exported for tests
public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => { public onSyncStateChange = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => {
if (state === SyncState.Syncing) { if (state === SyncState.Syncing) {
this.isSyncing = true; this.isSyncing = true;
} else if (state === SyncState.Stopped || state === SyncState.Error) { } else if (state === SyncState.Stopped || state === SyncState.Error) {
@ -411,7 +411,7 @@ class NotifierClass {
// If it's an encrypted event and the type is still 'm.room.encrypted', // If it's an encrypted event and the type is still 'm.room.encrypted',
// it hasn't yet been decrypted, so wait until it is. // it hasn't yet been decrypted, so wait until it is.
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) { if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
this.pendingEncryptedEventIds.push(ev.getId()); this.pendingEncryptedEventIds.push(ev.getId()!);
// don't let the list fill up indefinitely // don't let the list fill up indefinitely
while (this.pendingEncryptedEventIds.length > MAX_PENDING_ENCRYPTED) { while (this.pendingEncryptedEventIds.length > MAX_PENDING_ENCRYPTED) {
this.pendingEncryptedEventIds.shift(); this.pendingEncryptedEventIds.shift();
@ -427,7 +427,7 @@ class NotifierClass {
// in which case it might decrypt soon if the keys arrive // in which case it might decrypt soon if the keys arrive
if (ev.isDecryptionFailure()) return; if (ev.isDecryptionFailure()) return;
const idx = this.pendingEncryptedEventIds.indexOf(ev.getId()); const idx = this.pendingEncryptedEventIds.indexOf(ev.getId()!);
if (idx === -1) return; if (idx === -1) return;
this.pendingEncryptedEventIds.splice(idx, 1); this.pendingEncryptedEventIds.splice(idx, 1);
@ -456,7 +456,7 @@ class NotifierClass {
public evaluateEvent(ev: MatrixEvent): void { public evaluateEvent(ev: MatrixEvent): void {
// Mute notifications for broadcast info events // Mute notifications for broadcast info events
if (ev.getType() === VoiceBroadcastInfoEventType) return; if (ev.getType() === VoiceBroadcastInfoEventType) return;
let roomId = ev.getRoomId(); let roomId = ev.getRoomId()!;
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) { if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
// Attempt to translate a virtual room to a native one // Attempt to translate a virtual room to a native one
const nativeRoomId = VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(roomId); const nativeRoomId = VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(roomId);
@ -492,7 +492,7 @@ class NotifierClass {
this.displayPopupNotification(ev, room); this.displayPopupNotification(ev, room);
} }
if (actions.tweaks.sound && this.isAudioEnabled()) { if (actions.tweaks.sound && this.isAudioEnabled()) {
PlatformPeg.get().loudNotification(ev, room); PlatformPeg.get()?.loudNotification(ev, room);
this.playAudioNotification(ev, room); this.playAudioNotification(ev, room);
} }
} }
@ -504,7 +504,7 @@ class NotifierClass {
private performCustomEventHandling(ev: MatrixEvent): void { private performCustomEventHandling(ev: MatrixEvent): void {
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) { if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
ToastStore.sharedInstance().addOrReplaceToast({ ToastStore.sharedInstance().addOrReplaceToast({
key: getIncomingCallToastKey(ev.getStateKey()), key: getIncomingCallToastKey(ev.getStateKey()!),
priority: 100, priority: 100,
component: IncomingCallToast, component: IncomingCallToast,
bodyClassName: "mx_IncomingCallToast", bodyClassName: "mx_IncomingCallToast",

View file

@ -238,11 +238,11 @@ export class PosthogAnalytics {
} }
} }
private static async getPlatformProperties(): Promise<PlatformProperties> { private static async getPlatformProperties(): Promise<Partial<PlatformProperties>> {
const platform = PlatformPeg.get(); const platform = PlatformPeg.get();
let appVersion: string; let appVersion: string | undefined;
try { try {
appVersion = await platform.getAppVersion(); appVersion = await platform?.getAppVersion();
} catch (e) { } catch (e) {
// this happens if no version is set i.e. in dev // this happens if no version is set i.e. in dev
appVersion = "unknown"; appVersion = "unknown";
@ -250,7 +250,7 @@ export class PosthogAnalytics {
return { return {
appVersion, appVersion,
appPlatform: platform.getHumanReadableName(), appPlatform: platform?.getHumanReadableName(),
}; };
} }
@ -411,7 +411,7 @@ export class PosthogAnalytics {
// All other scenarios should not track a user before they have given // All other scenarios should not track a user before they have given
// explicit consent that they are ok with their analytics data being collected // explicit consent that they are ok with their analytics data being collected
const options: IPostHogEventOptions = {}; const options: IPostHogEventOptions = {};
const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10); const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time")!, 10);
if (!isNaN(registrationTime)) { if (!isNaN(registrationTime)) {
options.timestamp = new Date(registrationTime); options.timestamp = new Date(registrationTime);
} }

View file

@ -142,7 +142,7 @@ export function showAnyInviteErrors(
}); });
return false; return false;
} else { } else {
const errorList = []; const errorList: string[] = [];
for (const addr of failedUsers) { for (const addr of failedUsers) {
if (states[addr] === "error") { if (states[addr] === "error") {
const reason = inviter.getErrorText(addr); const reason = inviter.getErrorText(addr);
@ -173,8 +173,11 @@ export function showAnyInviteErrors(
<div key={addr} className="mx_InviteDialog_tile mx_InviteDialog_tile--inviterError"> <div key={addr} className="mx_InviteDialog_tile mx_InviteDialog_tile--inviterError">
<div className="mx_InviteDialog_tile_avatarStack"> <div className="mx_InviteDialog_tile_avatarStack">
<BaseAvatar <BaseAvatar
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} url={
name={name} (avatarUrl && mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24)) ??
undefined
}
name={name!}
idName={user?.userId} idName={user?.userId}
width={36} width={36}
height={36} height={36}

View file

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

View file

@ -358,7 +358,7 @@ function inviteUser(event: MessageEvent<any>, roomId: string, userId: string): v
if (room) { if (room) {
// if they are already invited or joined we can resolve immediately. // if they are already invited or joined we can resolve immediately.
const member = room.getMember(userId); const member = room.getMember(userId);
if (member && ["join", "invite"].includes(member.membership)) { if (member && ["join", "invite"].includes(member.membership!)) {
sendResponse(event, { sendResponse(event, {
success: true, success: true,
}); });
@ -389,7 +389,7 @@ function kickUser(event: MessageEvent<any>, roomId: string, userId: string): voi
if (room) { if (room) {
// if they are already not in the room we can resolve immediately. // if they are already not in the room we can resolve immediately.
const member = room.getMember(userId); const member = room.getMember(userId);
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) { if (!member || getEffectiveMembership(member.membership!) === EffectiveMembership.Leave) {
sendResponse(event, { sendResponse(event, {
success: true, success: true,
}); });
@ -472,7 +472,7 @@ function setWidget(event: MessageEvent<any>, roomId: string | null): void {
} else { } else {
// Room widget // Room widget
if (!roomId) { if (!roomId) {
sendError(event, _t("Missing roomId."), null); sendError(event, _t("Missing roomId."));
return; return;
} }
WidgetUtils.setRoomWidget( WidgetUtils.setRoomWidget(
@ -675,7 +675,7 @@ function canSendEvent(event: MessageEvent<any>, roomId: string): void {
sendError(event, _t("You are not in this room.")); sendError(event, _t("You are not in this room."));
return; return;
} }
const me = client.credentials.userId; const me = client.credentials.userId!;
let canSend = false; let canSend = false;
if (isState) { if (isState) {

View file

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

View file

@ -109,7 +109,7 @@ async function getSecretStorageKey({
if (!keyInfo) { if (!keyInfo) {
// if the default key is not available, pretend the default key // if the default key is not available, pretend the default key
// isn't set // isn't set
keyId = undefined; keyId = null;
} }
} }
if (!keyId) { if (!keyId) {
@ -156,7 +156,7 @@ async function getSecretStorageKey({
return MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo); return MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo);
}, },
}, },
/* className= */ null, /* className= */ undefined,
/* isPriorityModal= */ false, /* isPriorityModal= */ false,
/* isStaticModal= */ false, /* isStaticModal= */ false,
/* options= */ { /* options= */ {
@ -206,7 +206,7 @@ export async function getDehydrationKey(
} }
}, },
}, },
/* className= */ null, /* className= */ undefined,
/* isPriorityModal= */ false, /* isPriorityModal= */ false,
/* isStaticModal= */ false, /* isStaticModal= */ false,
/* options= */ { /* options= */ {
@ -243,7 +243,7 @@ async function onSecretRequested(
requestId: string, requestId: string,
name: string, name: string,
deviceTrust: DeviceTrustLevel, deviceTrust: DeviceTrustLevel,
): Promise<string> { ): Promise<string | undefined> {
logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (userId !== client.getUserId()) { if (userId !== client.getUserId()) {
@ -259,19 +259,19 @@ async function onSecretRequested(
name === "m.cross_signing.user_signing" name === "m.cross_signing.user_signing"
) { ) {
const callbacks = client.getCrossSigningCacheCallbacks(); const callbacks = client.getCrossSigningCacheCallbacks();
if (!callbacks.getCrossSigningKeyCache) return; if (!callbacks?.getCrossSigningKeyCache) return;
const keyId = name.replace("m.cross_signing.", ""); const keyId = name.replace("m.cross_signing.", "");
const key = await callbacks.getCrossSigningKeyCache(keyId); const key = await callbacks.getCrossSigningKeyCache(keyId);
if (!key) { if (!key) {
logger.log(`${keyId} requested by ${deviceId}, but not found in cache`); logger.log(`${keyId} requested by ${deviceId}, but not found in cache`);
} }
return key && encodeBase64(key); return key ? encodeBase64(key) : undefined;
} else if (name === "m.megolm_backup.v1") { } else if (name === "m.megolm_backup.v1") {
const key = await client.crypto.getSessionBackupPrivateKey(); const key = await client.crypto?.getSessionBackupPrivateKey();
if (!key) { if (!key) {
logger.log(`session backup key requested by ${deviceId}, but not found in cache`); logger.log(`session backup key requested by ${deviceId}, but not found in cache`);
} }
return key && encodeBase64(key); return key ? encodeBase64(key) : undefined;
} }
logger.warn("onSecretRequested didn't recognise the secret named ", name); logger.warn("onSecretRequested didn't recognise the secret named ", name);
} }
@ -284,7 +284,7 @@ export const crossSigningCallbacks: ICryptoCallbacks = {
}; };
export async function promptForBackupPassphrase(): Promise<Uint8Array> { export async function promptForBackupPassphrase(): Promise<Uint8Array> {
let key: Uint8Array; let key!: Uint8Array;
const { finished } = Modal.createDialog( const { finished } = Modal.createDialog(
RestoreKeyBackupDialog, RestoreKeyBackupDialog,
@ -292,7 +292,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
showSummary: false, showSummary: false,
keyCallback: (k: Uint8Array) => (key = k), keyCallback: (k: Uint8Array) => (key = k),
}, },
null, undefined,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );
@ -338,7 +338,7 @@ export async function accessSecretStorage(func = async (): Promise<void> => {},
{ {
forceReset, forceReset,
}, },
null, undefined,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
/* options = */ { /* options = */ {

View file

@ -81,7 +81,7 @@ const singleMxcUpload = async (): Promise<string | null> => {
const fileSelector = document.createElement("input"); const fileSelector = document.createElement("input");
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];
Modal.createDialog(UploadConfirmDialog, { Modal.createDialog(UploadConfirmDialog, {
file, file,
@ -111,7 +111,7 @@ export const CommandCategories = {
export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise<IContent | undefined> }>; export type RunResult = XOR<{ error: Error | ITranslatableError }, { promise: Promise<IContent | undefined> }>;
type RunFn = (this: Command, roomId: string, args: string) => RunResult; type RunFn = (this: Command, roomId: string, args?: string) => RunResult;
interface ICommandOpts { interface ICommandOpts {
command: string; command: string;
@ -159,7 +159,7 @@ export class Command {
return this.getCommand() + " " + this.args; return this.getCommand() + " " + this.args;
} }
public run(roomId: string, threadId: string, args: string): RunResult { public run(roomId: string, threadId: string | null, args?: string): RunResult {
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
if (!this.runFn) { if (!this.runFn) {
return reject(newTranslatableError("Command error: Unable to handle slash command.")); return reject(newTranslatableError("Command error: Unable to handle slash command."));
@ -395,12 +395,12 @@ export const Commands = [
runFn: function (roomId, args) { runFn: function (roomId, args) {
if (args) { if (args) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const ev = cli.getRoom(roomId).currentState.getStateEvents("m.room.member", cli.getUserId()); const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getUserId()!);
const content = { const content = {
...(ev ? ev.getContent() : { membership: "join" }), ...(ev ? ev.getContent() : { membership: "join" }),
displayname: args, displayname: args,
}; };
return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId())); return success(cli.sendStateEvent(roomId, "m.room.member", content, cli.getUserId()!));
} }
return reject(this.getUsage()); return reject(this.getUsage());
}, },
@ -413,7 +413,7 @@ export const Commands = [
description: _td("Changes the avatar of the current room"), description: _td("Changes the avatar of the current room"),
isEnabled: () => !isCurrentLocalRoom(), isEnabled: () => !isCurrentLocalRoom(),
runFn: function (roomId, args) { runFn: function (roomId, args) {
let promise = Promise.resolve(args); let promise = Promise.resolve(args ?? null);
if (!args) { if (!args) {
promise = singleMxcUpload(); promise = singleMxcUpload();
} }
@ -436,9 +436,9 @@ export const Commands = [
runFn: function (roomId, args) { runFn: function (roomId, args) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
const userId = cli.getUserId(); const userId = cli.getUserId()!;
let promise = Promise.resolve(args); let promise = Promise.resolve(args ?? null);
if (!args) { if (!args) {
promise = singleMxcUpload(); promise = singleMxcUpload();
} }
@ -446,7 +446,7 @@ export const Commands = [
return success( return success(
promise.then((url) => { promise.then((url) => {
if (!url) return; if (!url) return;
const ev = room.currentState.getStateEvents("m.room.member", userId); const ev = room?.currentState.getStateEvents("m.room.member", userId);
const content = { const content = {
...(ev ? ev.getContent() : { membership: "join" }), ...(ev ? ev.getContent() : { membership: "join" }),
avatar_url: url, avatar_url: url,
@ -463,7 +463,7 @@ export const Commands = [
args: "[<mxc_url>]", args: "[<mxc_url>]",
description: _td("Changes your avatar in all rooms"), description: _td("Changes your avatar in all rooms"),
runFn: function (roomId, args) { runFn: function (roomId, args) {
let promise = Promise.resolve(args); let promise = Promise.resolve(args ?? null);
if (!args) { if (!args) {
promise = singleMxcUpload(); promise = singleMxcUpload();
} }
@ -496,7 +496,7 @@ export const Commands = [
); );
} }
const content: MRoomTopicEventContent = room.currentState.getStateEvents("m.room.topic", "")?.getContent(); const content = room.currentState.getStateEvents("m.room.topic", "")?.getContent<MRoomTopicEventContent>();
const topic = !!content const topic = !!content
? ContentHelpers.parseTopicContent(content) ? ContentHelpers.parseTopicContent(content)
: { text: _t("This room has no topic.") }; : { text: _t("This room has no topic.") };
@ -874,7 +874,8 @@ export const Commands = [
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return ( return (
room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room) !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) &&
!isLocalRoom(room)
); );
}, },
runFn: function (roomId, args) { runFn: function (roomId, args) {
@ -916,7 +917,8 @@ export const Commands = [
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()); const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
return ( return (
room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()) && !isLocalRoom(room) !!room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId()!) &&
!isLocalRoom(room)
); );
}, },
runFn: function (roomId, args) { runFn: function (roomId, args) {
@ -932,7 +934,7 @@ export const Commands = [
} }
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent.getContent().users[args]) { if (!powerLevelEvent?.getContent().users[args]) {
return reject(newTranslatableError("Could not find user in room")); return reject(newTranslatableError("Could not find user in room"));
} }
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
@ -1113,9 +1115,9 @@ export const Commands = [
MatrixClientPeg.get().forceDiscardSession(roomId); MatrixClientPeg.get().forceDiscardSession(roomId);
return success( return success(
room.getEncryptionTargetMembers().then((members) => { room?.getEncryptionTargetMembers().then((members) => {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
MatrixClientPeg.get().crypto.ensureOlmSessionsForUsers( MatrixClientPeg.get().crypto?.ensureOlmSessionsForUsers(
members.map((m) => m.userId), members.map((m) => m.userId),
true, true,
); );
@ -1167,7 +1169,7 @@ export const Commands = [
return reject(this.getUsage()); return reject(this.getUsage());
} }
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); const member = MatrixClientPeg.get().getRoom(roomId)?.getMember(userId);
dis.dispatch<ViewUserPayload>({ dis.dispatch<ViewUserPayload>({
action: Action.ViewUser, action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the receiver wants. // XXX: We should be using a real member object and not assuming what the receiver wants.
@ -1412,7 +1414,7 @@ interface ICmd {
export function getCommand(input: string): ICmd { 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 (CommandMap.has(cmd) && CommandMap.get(cmd)!.isEnabled()) {
return { return {
cmd: CommandMap.get(cmd), cmd: CommandMap.get(cmd),
args, args,

View file

@ -43,12 +43,12 @@ import { getSenderName } from "./utils/event/getSenderName";
function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string { function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const roomId = event.getRoomId(); const roomId = event.getRoomId();
const member = client.getRoom(roomId)?.getMember(userId); const member = client.getRoom(roomId)?.getMember(userId!);
return member?.name || member?.rawDisplayName || userId || _t("Someone"); return member?.name || member?.rawDisplayName || userId || _t("Someone");
} }
function textForCallEvent(event: MatrixEvent): () => string { function textForCallEvent(event: MatrixEvent): () => string {
const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!).name; const roomName = MatrixClientPeg.get().getRoom(event.getRoomId()!)?.name;
const isSupported = MatrixClientPeg.get().supportsVoip(); const isSupported = MatrixClientPeg.get().supportsVoip();
return isSupported return isSupported
@ -60,7 +60,7 @@ function textForCallEvent(event: MatrixEvent): () => string {
// any text to display at all. For this reason they return deferred values // any text to display at all. For this reason they return deferred values
// to avoid the expense of looking up translations when they're not needed. // to avoid the expense of looking up translations when they're not needed.
function textForCallInviteEvent(event: MatrixEvent): () => string | null { function textForCallInviteEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
// FIXME: Find a better way to determine this from the event? // FIXME: Find a better way to determine this from the event?
const isVoice = !event.getContent().offer?.sdp?.includes("m=video"); const isVoice = !event.getContent().offer?.sdp?.includes("m=video");
@ -78,9 +78,11 @@ function textForCallInviteEvent(event: MatrixEvent): () => string | null {
} else if (!isVoice && !isSupported) { } else if (!isVoice && !isSupported) {
return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { senderName }); return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { senderName });
} }
return null;
} }
function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): () => string | null { function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): (() => string) | null {
// XXX: SYJS-16 "sender is sometimes null for join messages" // XXX: SYJS-16 "sender is sometimes null for join messages"
const senderName = ev.sender?.name || getRoomMemberDisplayname(ev); const senderName = ev.sender?.name || getRoomMemberDisplayname(ev);
const targetName = ev.target?.name || getRoomMemberDisplayname(ev, ev.getStateKey()); const targetName = ev.target?.name || getRoomMemberDisplayname(ev, ev.getStateKey());
@ -187,9 +189,11 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents
return null; return null;
} }
} }
return null;
} }
function textForTopicEvent(ev: MatrixEvent): () => string | null { function textForTopicEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return () => return () =>
_t('%(senderDisplayName)s changed the topic to "%(topic)s".', { _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {
@ -198,12 +202,12 @@ function textForTopicEvent(ev: MatrixEvent): () => string | null {
}); });
} }
function textForRoomAvatarEvent(ev: MatrixEvent): () => string | null { function textForRoomAvatarEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev?.sender?.name || ev.getSender(); const senderDisplayName = ev?.sender?.name || ev.getSender();
return () => _t("%(senderDisplayName)s changed the room avatar.", { senderDisplayName }); return () => _t("%(senderDisplayName)s changed the room avatar.", { senderDisplayName });
} }
function textForRoomNameEvent(ev: MatrixEvent): () => string | null { function textForRoomNameEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
@ -224,7 +228,7 @@ function textForRoomNameEvent(ev: MatrixEvent): () => string | null {
}); });
} }
function textForTombstoneEvent(ev: MatrixEvent): () => string | null { function textForTombstoneEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return () => _t("%(senderDisplayName)s upgraded this room.", { senderDisplayName }); return () => _t("%(senderDisplayName)s upgraded this room.", { senderDisplayName });
} }
@ -281,7 +285,7 @@ function textForJoinRulesEvent(ev: MatrixEvent, allowJSX: boolean): () => Render
} }
} }
function textForGuestAccessEvent(ev: MatrixEvent): () => string | null { function textForGuestAccessEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
switch (ev.getContent().guest_access) { switch (ev.getContent().guest_access) {
case GuestAccess.CanJoin: case GuestAccess.CanJoin:
@ -298,7 +302,7 @@ function textForGuestAccessEvent(ev: MatrixEvent): () => string | null {
} }
} }
function textForServerACLEvent(ev: MatrixEvent): () => string | null { function textForServerACLEvent(ev: MatrixEvent): (() => string) | null {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const prevContent = ev.getPrevContent(); const prevContent = ev.getPrevContent();
const current = ev.getContent(); const current = ev.getContent();
@ -308,7 +312,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
allow_ip_literals: prevContent.allow_ip_literals !== false, allow_ip_literals: prevContent.allow_ip_literals !== false,
}; };
let getText: () => string = null; let getText: () => string;
if (prev.deny.length === 0 && prev.allow.length === 0) { if (prev.deny.length === 0 && prev.allow.length === 0) {
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName }); getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
} else { } else {
@ -328,7 +332,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
return getText; return getText;
} }
function textForMessageEvent(ev: MatrixEvent): () => string | null { function textForMessageEvent(ev: MatrixEvent): (() => string) | null {
if (isLocationEvent(ev)) { if (isLocationEvent(ev)) {
return textForLocationEvent(ev); return textForLocationEvent(ev);
} }
@ -354,7 +358,7 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
}; };
} }
function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null { function textForCanonicalAliasEvent(ev: MatrixEvent): (() => string) | null {
const senderName = getSenderName(ev); const senderName = getSenderName(ev);
const oldAlias = ev.getPrevContent().alias; const oldAlias = ev.getPrevContent().alias;
const oldAltAliases = ev.getPrevContent().alt_aliases || []; const oldAltAliases = ev.getPrevContent().alt_aliases || [];
@ -414,7 +418,7 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
}); });
} }
function textForThreePidInviteEvent(event: MatrixEvent): () => string | null { function textForThreePidInviteEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
if (!isValid3pidInvite(event)) { if (!isValid3pidInvite(event)) {
@ -432,7 +436,7 @@ function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
}); });
} }
function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null { function textForHistoryVisibilityEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
switch (event.getContent().history_visibility) { switch (event.getContent().history_visibility) {
case HistoryVisibility.Invited: case HistoryVisibility.Invited:
@ -463,7 +467,7 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null
} }
// Currently will only display a change if a user's power level is changed // Currently will only display a change if a user's power level is changed
function textForPowerEvent(event: MatrixEvent): () => string | null { function textForPowerEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
if (!event.getPrevContent()?.users || !event.getContent()?.users) { if (!event.getPrevContent()?.users || !event.getContent()?.users) {
return null; return null;
@ -528,10 +532,10 @@ const onPinnedMessagesClick = (): void => {
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
}; };
function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Renderable { function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): (() => Renderable) | null {
if (!SettingsStore.getValue("feature_pinning")) return null; if (!SettingsStore.getValue("feature_pinning")) return null;
const senderName = getSenderName(event); const senderName = getSenderName(event);
const roomId = event.getRoomId(); const roomId = event.getRoomId()!;
const pinned = event.getContent<{ pinned: string[] }>().pinned ?? []; const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
const previouslyPinned: string[] = event.getPrevContent().pinned ?? []; const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
@ -625,7 +629,7 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
return () => _t("%(senderName)s changed the pinned messages for the room.", { senderName }); return () => _t("%(senderName)s changed the pinned messages for the room.", { senderName });
} }
function textForWidgetEvent(event: MatrixEvent): () => string | null { function textForWidgetEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
const { name: prevName, type: prevType, url: prevUrl } = event.getPrevContent(); const { name: prevName, type: prevType, url: prevUrl } = event.getPrevContent();
const { name, type, url } = event.getContent() || {}; const { name, type, url } = event.getContent() || {};
@ -661,12 +665,12 @@ function textForWidgetEvent(event: MatrixEvent): () => string | null {
} }
} }
function textForWidgetLayoutEvent(event: MatrixEvent): () => string | null { function textForWidgetLayoutEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
return () => _t("%(senderName)s has updated the room layout", { senderName }); return () => _t("%(senderName)s has updated the room layout", { senderName });
} }
function textForMjolnirEvent(event: MatrixEvent): () => string | null { function textForMjolnirEvent(event: MatrixEvent): (() => string) | null {
const senderName = getSenderName(event); const senderName = getSenderName(event);
const { entity: prevEntity } = event.getPrevContent(); const { entity: prevEntity } = event.getPrevContent();
const { entity, recommendation, reason } = event.getContent(); const { entity, recommendation, reason } = event.getContent();
@ -795,7 +799,7 @@ function textForMjolnirEvent(event: MatrixEvent): () => string | null {
); );
} }
export function textForLocationEvent(event: MatrixEvent): () => string | null { export function textForLocationEvent(event: MatrixEvent): () => string {
return () => return () =>
_t("%(senderName)s has shared their location", { _t("%(senderName)s has shared their location", {
senderName: getSenderName(event), senderName: getSenderName(event),
@ -817,7 +821,7 @@ function textForRedactedPollAndMessageEvent(ev: MatrixEvent): string {
return message; return message;
} }
function textForPollStartEvent(event: MatrixEvent): () => string | null { function textForPollStartEvent(event: MatrixEvent): (() => string) | null {
return () => { return () => {
let message = ""; let message = "";
@ -836,7 +840,7 @@ function textForPollStartEvent(event: MatrixEvent): () => string | null {
}; };
} }
function textForPollEndEvent(event: MatrixEvent): () => string | null { function textForPollEndEvent(event: MatrixEvent): (() => string) | null {
return () => return () =>
_t("%(senderName)s has ended a poll", { _t("%(senderName)s has ended a poll", {
senderName: getSenderName(event), senderName: getSenderName(event),
@ -846,7 +850,7 @@ function textForPollEndEvent(event: MatrixEvent): () => string | null {
type Renderable = string | React.ReactNode | null; type Renderable = string | React.ReactNode | null;
interface IHandlers { interface IHandlers {
[type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => () => Renderable; [type: string]: (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => (() => Renderable) | null;
} }
const handlers: IHandlers = { const handlers: IHandlers = {

View file

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

View file

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

View file

@ -23,7 +23,7 @@ export interface SettingUpdatedPayload extends ActionPayload {
action: Action.SettingUpdated; action: Action.SettingUpdated;
settingName: string; settingName: string;
roomId: string; roomId: string | null;
level: SettingLevel; level: SettingLevel;
newValueAtLevel: SettingLevel; newValueAtLevel: SettingLevel;
newValue: SettingValueType; newValue: SettingValueType;

View file

@ -288,7 +288,7 @@ export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode;
export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode {
// We initially store our output as an array of strings and objects (e.g. React components). // We initially store our output as an array of strings and objects (e.g. React components).
// This will then be converted to a string or a <span> at the end // This will then be converted to a string or a <span> at the end
const output = [text]; const output: SubstitutionValue[] = [text];
// If we insert any components we need to wrap the output in a span. React doesn't like just an array of components. // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
let shouldWrapInSpan = false; let shouldWrapInSpan = false;
@ -319,7 +319,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
// The textual part before the first match // The textual part before the first match
const head = inputText.slice(0, match.index); const head = inputText.slice(0, match.index);
const parts = []; const parts: SubstitutionValue[] = [];
// keep track of prevMatch // keep track of prevMatch
let prevMatch; let prevMatch;
while (match) { while (match) {
@ -327,7 +327,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
prevMatch = match; prevMatch = match;
const capturedGroups = match.slice(2); const capturedGroups = match.slice(2);
let replaced; let replaced: SubstitutionValue;
// If substitution is a function, call it // If substitution is a function, call it
if (mapping[regexpString] instanceof Function) { if (mapping[regexpString] instanceof Function) {
replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups); replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups);
@ -434,7 +434,7 @@ export function setLanguage(preferredLangs: string | string[]): Promise<void> {
return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName);
}) })
.then(async (langData): Promise<ICounterpartTranslation> => { .then(async (langData): Promise<ICounterpartTranslation | undefined> => {
counterpart.registerTranslations(langToUse, langData); counterpart.registerTranslations(langToUse, langData);
await registerCustomTranslations(); await registerCustomTranslations();
counterpart.setLocale(langToUse); counterpart.setLocale(langToUse);
@ -664,7 +664,7 @@ export async function registerCustomTranslations(): Promise<void> {
if (!lookupUrl) return; // easy - nothing to do if (!lookupUrl) return; // easy - nothing to do
try { try {
let json: ICustomTranslations; let json: Optional<ICustomTranslations>;
if (Date.now() >= cachedCustomTranslationsExpire) { if (Date.now() >= cachedCustomTranslationsExpire) {
json = CustomTranslationOptions.lookupFn json = CustomTranslationOptions.lookupFn
? CustomTranslationOptions.lookupFn(lookupUrl) ? CustomTranslationOptions.lookupFn(lookupUrl)

View file

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

View file

@ -55,10 +55,10 @@ export class NotificationUtils {
// "highlight: true/false, // "highlight: true/false,
// } // }
// If the actions couldn't be decoded then returns null. // If the actions couldn't be decoded then returns null.
public static decodeActions(actions: PushRuleAction[]): IEncodedActions { public static decodeActions(actions: PushRuleAction[]): IEncodedActions | null {
let notify = false; let notify = false;
let sound = null; let sound: string | undefined;
let highlight = false; let highlight: boolean | undefined = false;
for (let i = 0; i < actions.length; ++i) { for (let i = 0; i < actions.length; ++i) {
const action = actions[i]; const action = actions[i];
@ -87,7 +87,7 @@ export class NotificationUtils {
} }
const result: IEncodedActions = { notify, highlight }; const result: IEncodedActions = { notify, highlight };
if (sound !== null) { if (sound !== undefined) {
result.sound = sound; result.sound = sound;
} }
return result; return result;

View file

@ -62,7 +62,7 @@ export class PushRuleVectorState {
* category or in PushRuleVectorState.LOUD, regardless of its enabled * category or in PushRuleVectorState.LOUD, regardless of its enabled
* state. Returns null if it does not match these categories. * state. Returns null if it does not match these categories.
*/ */
public static contentRuleVectorStateKind(rule: IPushRule): VectorState { public static contentRuleVectorStateKind(rule: IPushRule): VectorState | null {
const decoded = NotificationUtils.decodeActions(rule.actions); const decoded = NotificationUtils.decodeActions(rule.actions);
if (!decoded) { if (!decoded) {
@ -77,7 +77,7 @@ export class PushRuleVectorState {
if (decoded.highlight) { if (decoded.highlight) {
tweaks++; tweaks++;
} }
let stateKind = null; let stateKind: VectorState | null = null;
switch (tweaks) { switch (tweaks) {
case 0: case 0:
stateKind = VectorState.On; stateKind = VectorState.On;

View file

@ -28,5 +28,5 @@ export class StandardActions {
public static ACTION_HIGHLIGHT = encodeActions({ notify: true, highlight: true }); public static ACTION_HIGHLIGHT = encodeActions({ notify: true, highlight: true });
public static ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default", highlight: true }); public static ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({ notify: true, sound: "default", highlight: true });
public static ACTION_DONT_NOTIFY = encodeActions({ notify: false }); public static ACTION_DONT_NOTIFY = encodeActions({ notify: false });
public static ACTION_DISABLED: PushRuleAction[] | null = null; public static ACTION_DISABLED: PushRuleAction[] | undefined = undefined;
} }

View file

@ -41,7 +41,7 @@ class VectorPushRuleDefinition {
} }
// Translate the rule actions and its enabled value into vector state // Translate the rule actions and its enabled value into vector state
public ruleToVectorState(rule: IAnnotatedPushRule): VectorState { public ruleToVectorState(rule: IAnnotatedPushRule): VectorState | undefined {
let enabled = false; let enabled = false;
if (rule) { if (rule) {
enabled = rule.enabled; enabled = rule.enabled;

View file

@ -81,8 +81,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
} }
if (client) { if (client) {
body.append("user_id", client.credentials.userId); body.append("user_id", client.credentials.userId!);
body.append("device_id", client.deviceId); body.append("device_id", client.deviceId!);
// TODO: make this work with rust crypto // TODO: make this work with rust crypto
if (client.isCryptoEnabled() && client.crypto) { if (client.isCryptoEnabled() && client.crypto) {
@ -181,7 +181,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
} }
} }
body.append("mx_local_settings", localStorage.getItem("mx_local_settings")); body.append("mx_local_settings", localStorage.getItem("mx_local_settings")!);
if (opts.sendLogs) { if (opts.sendLogs) {
progressCallback(_t("Collecting logs")); progressCallback(_t("Collecting logs"));
@ -293,9 +293,9 @@ export async function submitFeedback(
canContact = false, canContact = false,
extraData: Record<string, string> = {}, extraData: Record<string, string> = {},
): Promise<void> { ): Promise<void> {
let version = "UNKNOWN"; let version: string | undefined;
try { try {
version = await PlatformPeg.get().getAppVersion(); version = await PlatformPeg.get()?.getAppVersion();
} catch (err) {} // PlatformPeg already logs this. } catch (err) {} // PlatformPeg already logs this.
const body = new FormData(); const body = new FormData();
@ -304,7 +304,7 @@ export async function submitFeedback(
body.append("can_contact", canContact ? "yes" : "no"); body.append("can_contact", canContact ? "yes" : "no");
body.append("app", "element-web"); body.append("app", "element-web");
body.append("version", version); body.append("version", version || "UNKNOWN");
body.append("platform", PlatformPeg.get().getHumanReadableName()); body.append("platform", PlatformPeg.get().getHumanReadableName());
body.append("user_id", MatrixClientPeg.get()?.getUserId()); body.append("user_id", MatrixClientPeg.get()?.getUserId());

View file

@ -86,9 +86,9 @@ export default class Sizer {
public clearItemSize(item: HTMLElement): void { public clearItemSize(item: HTMLElement): void {
if (this.vertical) { if (this.vertical) {
item.style.height = null; item.style.height = null!;
} else { } else {
item.style.width = null; item.style.width = null!;
} }
} }

View file

@ -91,7 +91,7 @@ function getLevelOrder(setting: ISetting): SettingLevel[] {
export type CallbackFn = ( export type CallbackFn = (
settingName: string, settingName: string,
roomId: string, roomId: string | null,
atLevel: SettingLevel, atLevel: SettingLevel,
newValAtLevel: any, newValAtLevel: any,
newVal: any, newVal: any,
@ -133,7 +133,7 @@ export default class SettingsStore {
// when the setting changes. We track which rooms we're monitoring though to ensure we // when the setting changes. We track which rooms we're monitoring though to ensure we
// don't duplicate updates on the bus. // don't duplicate updates on the bus.
private static watchers = new Map<string, WatchCallbackFn>(); private static watchers = new Map<string, WatchCallbackFn>();
private static monitors = new Map<string, Map<string, string>>(); // { settingName => { roomId => callbackRef } } private static monitors = new Map<string, Map<string | null, string>>(); // { settingName => { roomId => callbackRef } }
// Counter used for generation of watcher IDs // Counter used for generation of watcher IDs
private static watcherCount = 1; private static watcherCount = 1;
@ -205,7 +205,7 @@ export default class SettingsStore {
return; return;
} }
defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)); defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)!);
SettingsStore.watchers.delete(watcherReference); SettingsStore.watchers.delete(watcherReference);
} }
@ -223,7 +223,7 @@ export default class SettingsStore {
if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map());
const registerWatcher = (): void => { const registerWatcher = (): void => {
this.monitors.get(settingName).set( this.monitors.get(settingName)!.set(
roomId, roomId,
SettingsStore.watchSetting( SettingsStore.watchSetting(
settingName, settingName,
@ -242,7 +242,7 @@ export default class SettingsStore {
); );
}; };
const rooms = Array.from(this.monitors.get(settingName).keys()); const rooms = Array.from(this.monitors.get(settingName)!.keys());
const hasRoom = rooms.find((r) => r === roomId || r === null); const hasRoom = rooms.find((r) => r === roomId || r === null);
if (!hasRoom) { if (!hasRoom) {
registerWatcher(); registerWatcher();
@ -250,9 +250,9 @@ export default class SettingsStore {
if (roomId === null) { if (roomId === null) {
// Unregister all existing watchers and register the new one // Unregister all existing watchers and register the new one
rooms.forEach((roomId) => { rooms.forEach((roomId) => {
SettingsStore.unwatchSetting(this.monitors.get(settingName).get(roomId)); SettingsStore.unwatchSetting(this.monitors.get(settingName)!.get(roomId));
}); });
this.monitors.get(settingName).clear(); this.monitors.get(settingName)!.clear();
registerWatcher(); registerWatcher();
} // else a watcher is already registered for the room, so don't bother registering it again } // else a watcher is already registered for the room, so don't bother registering it again
} }
@ -265,7 +265,7 @@ export default class SettingsStore {
* The level to get the display name for; Defaults to 'default'. * The level to get the display name for; Defaults to 'default'.
* @return {String} The display name for the setting, or null if not found. * @return {String} The display name for the setting, or null if not found.
*/ */
public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string { public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string | null {
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null; if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
let displayName = SETTINGS[settingName].displayName; let displayName = SETTINGS[settingName].displayName;
@ -296,7 +296,7 @@ export default class SettingsStore {
*/ */
public static isFeature(settingName: string): boolean { public static isFeature(settingName: string): boolean {
if (!SETTINGS[settingName]) return false; if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].isFeature; return !!SETTINGS[settingName].isFeature;
} }
/** /**
@ -319,7 +319,7 @@ export default class SettingsStore {
} }
} }
public static getLabGroup(settingName: string): LabGroup { public static getLabGroup(settingName: string): LabGroup | undefined {
if (SettingsStore.isFeature(settingName)) { if (SettingsStore.isFeature(settingName)) {
return (<IFeature>SETTINGS[settingName]).labsGroup; return (<IFeature>SETTINGS[settingName]).labsGroup;
} }
@ -333,7 +333,7 @@ export default class SettingsStore {
*/ */
public static isEnabled(settingName: string): boolean { public static isEnabled(settingName: string): boolean {
if (!SETTINGS[settingName]) return false; if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; return !SETTINGS[settingName].controller?.settingDisabled ?? true;
} }
/** /**
@ -559,7 +559,7 @@ export default class SettingsStore {
throw new Error("Setting '" + settingName + "' does not appear to be a setting."); throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
} }
return level === SettingLevel.DEFAULT || setting.supportedLevels.includes(level); return level === SettingLevel.DEFAULT || !!setting.supportedLevels?.includes(level);
} }
/** /**
@ -568,7 +568,7 @@ export default class SettingsStore {
* @param {string} settingName The setting name. * @param {string} settingName The setting name.
* @return {SettingLevel} * @return {SettingLevel}
*/ */
public static firstSupportedLevel(settingName: string): SettingLevel { public static firstSupportedLevel(settingName: string): SettingLevel | null {
// Verify that the setting is actually a setting // Verify that the setting is actually a setting
const setting = SETTINGS[settingName]; const setting = SETTINGS[settingName];
if (!setting) { if (!setting) {
@ -723,10 +723,10 @@ export default class SettingsStore {
logger.log(`--- END DEBUG`); logger.log(`--- END DEBUG`);
} }
private static getHandler(settingName: string, level: SettingLevel): SettingsHandler { private static getHandler(settingName: string, level: SettingLevel): SettingsHandler | null {
const handlers = SettingsStore.getHandlers(settingName); const handlers = SettingsStore.getHandlers(settingName);
if (!handlers[level]) return null; if (!handlers[level]) return null;
return handlers[level]; return handlers[level]!;
} }
private static getHandlers(settingName: string): HandlerMap { private static getHandlers(settingName: string): HandlerMap {

View file

@ -16,9 +16,9 @@ limitations under the License.
import { SettingLevel } from "./SettingLevel"; import { SettingLevel } from "./SettingLevel";
export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; export type CallbackFn = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any) => void;
const IRRELEVANT_ROOM: string = null; const IRRELEVANT_ROOM: string | null = null;
/** /**
* Generalized management class for dealing with watchers on a per-handler (per-level) * Generalized management class for dealing with watchers on a per-handler (per-level)
@ -26,13 +26,13 @@ const IRRELEVANT_ROOM: string = null;
* class, which are then proxied outwards to any applicable watchers. * class, which are then proxied outwards to any applicable watchers.
*/ */
export class WatchManager { export class WatchManager {
private watchers = new Map<string, Map<string | symbol, CallbackFn[]>>(); // settingName -> roomId -> CallbackFn[] private watchers = new Map<string, Map<string | null, CallbackFn[]>>(); // settingName -> roomId -> CallbackFn[]
// Proxy for handlers to delegate changes to this manager // Proxy for handlers to delegate changes to this manager
public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn): void { public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn): void {
if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map()); if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map());
if (!this.watchers.get(settingName).has(roomId)) this.watchers.get(settingName).set(roomId, []); if (!this.watchers.get(settingName)!.has(roomId)) this.watchers.get(settingName)!.set(roomId, []);
this.watchers.get(settingName).get(roomId).push(cb); this.watchers.get(settingName)!.get(roomId)!.push(cb);
} }
// Proxy for handlers to delegate changes to this manager // Proxy for handlers to delegate changes to this manager
@ -59,18 +59,18 @@ export class WatchManager {
if (!this.watchers.has(settingName)) return; if (!this.watchers.has(settingName)) return;
const roomWatchers = this.watchers.get(settingName); const roomWatchers = this.watchers.get(settingName)!;
const callbacks = []; const callbacks: CallbackFn[] = [];
if (inRoomId !== null && roomWatchers.has(inRoomId)) { if (inRoomId !== null && roomWatchers.has(inRoomId)) {
callbacks.push(...roomWatchers.get(inRoomId)); callbacks.push(...roomWatchers.get(inRoomId)!);
} }
if (!inRoomId) { if (!inRoomId) {
// Fire updates to all the individual room watchers too, as they probably care about the change higher up. // Fire updates to all the individual room watchers too, as they probably care about the change higher up.
callbacks.push(...Array.from(roomWatchers.values()).flat(1)); callbacks.push(...Array.from(roomWatchers.values()).flat(1));
} else if (roomWatchers.has(IRRELEVANT_ROOM)) { } else if (roomWatchers.has(IRRELEVANT_ROOM)) {
callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)); callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)!);
} }
for (const callback of callbacks) { for (const callback of callbacks) {

View file

@ -20,6 +20,6 @@ import { SettingLevel } from "../SettingLevel";
export default class ReloadOnChangeController extends SettingController { export default class ReloadOnChangeController extends SettingController {
public onChange(level: SettingLevel, roomId: string, newValue: any): void { public onChange(level: SettingLevel, roomId: string, newValue: any): void {
PlatformPeg.get().reload(); PlatformPeg.get()?.reload();
} }
} }

View file

@ -53,7 +53,7 @@ export default abstract class SettingController {
* @param {*} newValue The new value for the setting, may be null. * @param {*} newValue The new value for the setting, may be null.
* @return {boolean} Whether the settings change should be accepted. * @return {boolean} Whether the settings change should be accepted.
*/ */
public async beforeChange(level: SettingLevel, roomId: string, newValue: any): Promise<boolean> { public async beforeChange(level: SettingLevel, roomId: string | null, newValue: any): Promise<boolean> {
return true; return true;
} }
@ -63,7 +63,7 @@ export default abstract class SettingController {
* @param {String} roomId The room ID, may be null. * @param {String} roomId The room ID, may be null.
* @param {*} newValue The new value for the setting, may be null. * @param {*} newValue The new value for the setting, may be null.
*/ */
public onChange(level: SettingLevel, roomId: string, newValue: any): void { public onChange(level: SettingLevel, roomId: string | null, newValue: any): void {
// do nothing by default // do nothing by default
} }

View file

@ -22,7 +22,7 @@ import SettingsHandler from "./SettingsHandler";
*/ */
export default abstract class AbstractLocalStorageSettingsHandler extends SettingsHandler { export default abstract class AbstractLocalStorageSettingsHandler extends SettingsHandler {
// Shared cache between all subclass instances // Shared cache between all subclass instances
private static itemCache = new Map<string, string>(); private static itemCache = new Map<string, string | null>();
private static objectCache = new Map<string, object>(); private static objectCache = new Map<string, object>();
private static storageListenerBound = false; private static storageListenerBound = false;
@ -51,14 +51,14 @@ export default abstract class AbstractLocalStorageSettingsHandler extends Settin
} }
} }
protected getItem(key: string): string { protected getItem(key: string): string | null {
if (!AbstractLocalStorageSettingsHandler.itemCache.has(key)) { if (!AbstractLocalStorageSettingsHandler.itemCache.has(key)) {
const value = localStorage.getItem(key); const value = localStorage.getItem(key);
AbstractLocalStorageSettingsHandler.itemCache.set(key, value); AbstractLocalStorageSettingsHandler.itemCache.set(key, value);
return value; return value;
} }
return AbstractLocalStorageSettingsHandler.itemCache.get(key); return AbstractLocalStorageSettingsHandler.itemCache.get(key)!;
} }
protected getBoolean(key: string): boolean | null { protected getBoolean(key: string): boolean | null {
@ -72,7 +72,7 @@ export default abstract class AbstractLocalStorageSettingsHandler extends Settin
protected getObject<T extends object>(key: string): T | null { protected getObject<T extends object>(key: string): T | null {
if (!AbstractLocalStorageSettingsHandler.objectCache.has(key)) { if (!AbstractLocalStorageSettingsHandler.objectCache.has(key)) {
try { try {
const value = JSON.parse(localStorage.getItem(key)); const value = JSON.parse(localStorage.getItem(key)!);
AbstractLocalStorageSettingsHandler.objectCache.set(key, value); AbstractLocalStorageSettingsHandler.objectCache.set(key, value);
return value; return value;
} catch (err) { } catch (err) {

View file

@ -40,7 +40,7 @@ export default class PlatformSettingsHandler extends SettingsHandler {
this.store = {}; this.store = {};
// Load setting values as they are async and `getValue` must be synchronous // Load setting values as they are async and `getValue` must be synchronous
Object.entries(SETTINGS).forEach(([key, setting]) => { Object.entries(SETTINGS).forEach(([key, setting]) => {
if (setting.supportedLevels.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) { if (setting.supportedLevels?.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) {
payload.platform.getSettingValue(key).then((value: any) => { payload.platform.getSettingValue(key).then((value: any) => {
this.store[key] = value; this.store[key] = value;
}); });
@ -50,19 +50,19 @@ export default class PlatformSettingsHandler extends SettingsHandler {
}; };
public canSetValue(settingName: string, roomId: string): boolean { public canSetValue(settingName: string, roomId: string): boolean {
return PlatformPeg.get().supportsSetting(settingName); return PlatformPeg.get()?.supportsSetting(settingName) ?? false;
} }
public getValue(settingName: string, roomId: string): any { public getValue(settingName: string, roomId: string): any {
return this.store[settingName]; return this.store[settingName];
} }
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> { public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
this.store[settingName] = newValue; // keep cache up to date for synchronous access this.store[settingName] = newValue; // keep cache up to date for synchronous access
return PlatformPeg.get().setSettingValue(settingName, newValue); await PlatformPeg.get()?.setSettingValue(settingName, newValue);
} }
public isSupported(): boolean { public isSupported(): boolean {
return PlatformPeg.get().supportsSetting(); return PlatformPeg.get()?.supportsSetting() ?? false;
} }
} }

View file

@ -44,7 +44,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
} }
private onEvent = (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent): void => { private onEvent = (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent): void => {
const roomId = event.getRoomId(); const roomId = event.getRoomId()!;
const room = this.client.getRoom(roomId); const room = this.client.getRoom(roomId);
// Note: in tests and during the encryption setup on initial load we might not have // Note: in tests and during the encryption setup on initial load we might not have
@ -124,7 +124,7 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
let eventType = DEFAULT_SETTINGS_EVENT_TYPE; let eventType = DEFAULT_SETTINGS_EVENT_TYPE;
if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls"; if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls";
return room?.currentState.maySendStateEvent(eventType, this.client.getUserId()) ?? false; return room?.currentState.maySendStateEvent(eventType, this.client.getUserId()!) ?? false;
} }
public isSupported(): boolean { public isSupported(): boolean {

View file

@ -32,7 +32,7 @@ export default abstract class SettingsHandler {
* @param {String} roomId The room ID to read from, may be null. * @param {String} roomId The room ID to read from, may be null.
* @returns {*} The setting value, or null if not found. * @returns {*} The setting value, or null if not found.
*/ */
public abstract getValue(settingName: string, roomId: string): any; public abstract getValue(settingName: string, roomId: string | null): any;
/** /**
* Sets the value for a particular setting at this level for a particular room. * Sets the value for a particular setting at this level for a particular room.
@ -45,7 +45,7 @@ export default abstract class SettingsHandler {
* @param {*} newValue The new value for the setting, may be null. * @param {*} newValue The new value for the setting, may be null.
* @returns {Promise} Resolves when the setting has been saved. * @returns {Promise} Resolves when the setting has been saved.
*/ */
public abstract setValue(settingName: string, roomId: string, newValue: any): Promise<void>; public abstract setValue(settingName: string, roomId: string | null, newValue: any): Promise<void>;
/** /**
* Determines if the current user is able to set the value of the given setting * Determines if the current user is able to set the value of the given setting
@ -54,7 +54,7 @@ export default abstract class SettingsHandler {
* @param {String} roomId The room ID to check in, may be null * @param {String} roomId The room ID to check in, may be null
* @returns {boolean} True if the setting can be set by the user, false otherwise. * @returns {boolean} True if the setting can be set by the user, false otherwise.
*/ */
public abstract canSetValue(settingName: string, roomId: string): boolean; public abstract canSetValue(settingName: string, roomId: string | null): boolean;
/** /**
* Determines if this level is supported on this device. * Determines if this level is supported on this device.

View file

@ -30,7 +30,7 @@ export class FontWatcher implements IWatcher {
// Externally we tell the user the font is size 15. Internally we use 10. // Externally we tell the user the font is size 15. Internally we use 10.
public static readonly SIZE_DIFF = 5; public static readonly SIZE_DIFF = 5;
private dispatcherRef: string; private dispatcherRef: string | null;
public constructor() { public constructor() {
this.dispatcherRef = null; this.dispatcherRef = null;
@ -42,6 +42,7 @@ export class FontWatcher implements IWatcher {
} }
public stop(): void { public stop(): void {
if (!this.dispatcherRef) return;
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
@ -77,7 +78,7 @@ export class FontWatcher implements IWatcher {
if (fontSize !== size) { if (fontSize !== size) {
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, fontSize); SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, fontSize);
} }
document.querySelector<HTMLElement>(":root").style.fontSize = toPx(fontSize); document.querySelector<HTMLElement>(":root")!.style.fontSize = toPx(fontSize);
}; };
private setSystemFont = ({ private setSystemFont = ({

View file

@ -26,9 +26,9 @@ import { ActionPayload } from "../../dispatcher/payloads";
import { SettingLevel } from "../SettingLevel"; import { SettingLevel } from "../SettingLevel";
export default class ThemeWatcher { export default class ThemeWatcher {
private themeWatchRef: string; private themeWatchRef: string | null;
private systemThemeWatchRef: string; private systemThemeWatchRef: string | null;
private dispatcherRef: string; private dispatcherRef: string | null;
private preferDark: MediaQueryList; private preferDark: MediaQueryList;
private preferLight: MediaQueryList; private preferLight: MediaQueryList;
@ -67,9 +67,9 @@ export default class ThemeWatcher {
this.preferLight.removeEventListener("change", this.onChange); this.preferLight.removeEventListener("change", this.onChange);
this.preferHighContrast.removeEventListener("change", this.onChange); this.preferHighContrast.removeEventListener("change", this.onChange);
} }
SettingsStore.unwatchSetting(this.systemThemeWatchRef); if (this.systemThemeWatchRef) SettingsStore.unwatchSetting(this.systemThemeWatchRef);
SettingsStore.unwatchSetting(this.themeWatchRef); if (this.themeWatchRef) SettingsStore.unwatchSetting(this.themeWatchRef);
dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
} }
private onChange = (): void => { private onChange = (): void => {

View file

@ -92,7 +92,7 @@ export abstract class AsyncStore<T extends Object> extends EventEmitter {
* @param {T|*} newState The new state of the store. * @param {T|*} newState The new state of the store.
* @param {boolean} quiet If true, the function will not raise an UPDATE_EVENT. * @param {boolean} quiet If true, the function will not raise an UPDATE_EVENT.
*/ */
protected async reset(newState: T | Object = null, quiet = false): Promise<void> { protected async reset(newState: T | Object | null = null, quiet = false): Promise<void> {
await this.lock.acquireAsync(); await this.lock.acquireAsync();
try { try {
this.storeState = Object.freeze(<T>(newState || {})); this.storeState = Object.freeze(<T>(newState || {}));

View file

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

View file

@ -22,7 +22,7 @@ import { ActionPayload } from "../dispatcher/payloads";
import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload"; import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
interface IState { interface IState {
deferredAction: ActionPayload; deferredAction: ActionPayload | null;
} }
const INITIAL_STATE: IState = { const INITIAL_STATE: IState = {
@ -83,8 +83,8 @@ class LifecycleStore extends Store<ActionPayload> {
} }
} }
let singletonLifecycleStore = null; let singletonLifecycleStore: LifecycleStore | null = null;
if (!singletonLifecycleStore) { if (!singletonLifecycleStore) {
singletonLifecycleStore = new LifecycleStore(); singletonLifecycleStore = new LifecycleStore();
} }
export default singletonLifecycleStore; export default singletonLifecycleStore!;

View file

@ -110,15 +110,15 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* ordered by creation time descending * ordered by creation time descending
*/ */
private liveBeaconIds: BeaconIdentifier[] = []; private liveBeaconIds: BeaconIdentifier[] = [];
private locationInterval: number; private locationInterval?: number;
private geolocationError: GeolocationError | undefined; private geolocationError?: GeolocationError;
private clearPositionWatch: ClearWatchCallback | undefined; private clearPositionWatch?: ClearWatchCallback;
/** /**
* Track when the last position was published * Track when the last position was published
* So we can manually get position on slow interval * So we can manually get position on slow interval
* when the target is stationary * when the target is stationary
*/ */
private lastPublishedPositionTimestamp: number | undefined; private lastPublishedPositionTimestamp?: number;
public constructor() { public constructor() {
super(defaultDispatcher); super(defaultDispatcher);
@ -231,7 +231,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
*/ */
private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => { private onNewBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return; return;
} }
this.addBeacon(beacon); this.addBeacon(beacon);
@ -242,7 +242,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
* This will be called when a beacon is replaced * This will be called when a beacon is replaced
*/ */
private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => { private onUpdateBeacon = (_event: MatrixEvent, beacon: Beacon): void => {
if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { if (!isOwnBeacon(beacon, this.matrixClient.getUserId()!)) {
return; return;
} }
@ -309,7 +309,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
} }
private initialiseBeaconState = (): void => { private initialiseBeaconState = (): void => {
const userId = this.matrixClient.getUserId(); const userId = this.matrixClient.getUserId()!;
const visibleRooms = this.matrixClient.getVisibleRooms(); const visibleRooms = this.matrixClient.getVisibleRooms();
visibleRooms.forEach((room) => { visibleRooms.forEach((room) => {
@ -329,7 +329,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.beaconsByRoomId.set(beacon.roomId, new Set<string>()); this.beaconsByRoomId.set(beacon.roomId, new Set<string>());
} }
this.beaconsByRoomId.get(beacon.roomId).add(beacon.identifier); this.beaconsByRoomId.get(beacon.roomId)!.add(beacon.identifier);
beacon.monitorLiveness(); beacon.monitorLiveness();
}; };
@ -343,7 +343,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
if (!this.beacons.has(beaconId)) { if (!this.beacons.has(beaconId)) {
return; return;
} }
this.beacons.get(beaconId).destroy(); this.beacons.get(beaconId)!.destroy();
this.beacons.delete(beaconId); this.beacons.delete(beaconId);
this.checkLiveness(); this.checkLiveness();

View file

@ -43,7 +43,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
return instance; return instance;
})(); })();
private monitoredUser: User; private monitoredUser: User | null;
private constructor() { private constructor() {
// seed from localstorage because otherwise we won't get these values until a whole network // seed from localstorage because otherwise we won't get these values until a whole network
@ -62,7 +62,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
/** /**
* Gets the display name for the user, or null if not present. * Gets the display name for the user, or null if not present.
*/ */
public get displayName(): string { public get displayName(): string | null {
if (!this.matrixClient) return this.state.displayName || null; if (!this.matrixClient) return this.state.displayName || null;
if (this.matrixClient.isGuest()) { if (this.matrixClient.isGuest()) {
@ -81,7 +81,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
/** /**
* Gets the MXC URI of the user's avatar, or null if not present. * Gets the MXC URI of the user's avatar, or null if not present.
*/ */
public get avatarMxc(): string { public get avatarMxc(): string | null {
return this.state.avatarUrl || null; return this.state.avatarUrl || null;
} }
@ -92,7 +92,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
* will be returned as an HTTP URL. * will be returned as an HTTP URL.
* @returns The HTTP URL of the user's avatar * @returns The HTTP URL of the user's avatar
*/ */
public getHttpAvatarUrl(size = 0): string { public getHttpAvatarUrl(size = 0): string | null {
if (!this.avatarMxc) return null; if (!this.avatarMxc) return null;
const media = mediaFromMxc(this.avatarMxc); const media = mediaFromMxc(this.avatarMxc);
if (!size || size <= 0) { if (!size || size <= 0) {
@ -112,7 +112,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
} }
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
const myUserId = this.matrixClient.getUserId(); const myUserId = this.matrixClient.getUserId()!;
this.monitoredUser = this.matrixClient.getUser(myUserId); this.monitoredUser = this.matrixClient.getUser(myUserId);
if (this.monitoredUser) { if (this.monitoredUser) {
this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate); this.monitoredUser.on(UserEvent.DisplayName, this.onProfileUpdate);
@ -134,7 +134,7 @@ export class OwnProfileStore extends AsyncStoreWithClient<IState> {
async (): Promise<void> => { async (): Promise<void> => {
// We specifically do not use the User object we stored for profile info as it // We specifically do not use the User object we stored for profile info as it
// could easily be wrong (such as per-room instead of global profile). // could easily be wrong (such as per-room instead of global profile).
const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()); const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()!);
if (profileInfo.displayname) { if (profileInfo.displayname) {
window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname); window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname);
} else { } else {

View file

@ -44,10 +44,12 @@ export enum Phase {
export class SetupEncryptionStore extends EventEmitter { export class SetupEncryptionStore extends EventEmitter {
private started: boolean; private started: boolean;
public phase: Phase; public phase: Phase;
public verificationRequest: VerificationRequest; public verificationRequest: VerificationRequest | null = null;
public backupInfo: IKeyBackupInfo; public backupInfo: IKeyBackupInfo | null = null;
public keyId: string; // ID of the key that the secrets we want are encrypted with
public keyInfo: ISecretStorageKeyInfo; public keyId: string | null = null;
// Descriptor of the key that the secrets we want are encrypted with
public keyInfo: ISecretStorageKeyInfo | null = null;
public hasDevicesToVerifyAgainst: boolean; public hasDevicesToVerifyAgainst: boolean;
public static sharedInstance(): SetupEncryptionStore { public static sharedInstance(): SetupEncryptionStore {
@ -61,19 +63,12 @@ export class SetupEncryptionStore extends EventEmitter {
} }
this.started = true; this.started = true;
this.phase = Phase.Loading; this.phase = Phase.Loading;
this.verificationRequest = null;
this.backupInfo = null;
// ID of the key that the secrets we want are encrypted with
this.keyId = null;
// Descriptor of the key that the secrets we want are encrypted with
this.keyInfo = null;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on(CryptoEvent.VerificationRequest, this.onVerificationRequest); cli.on(CryptoEvent.VerificationRequest, this.onVerificationRequest);
cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged); cli.on(CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId()); const requestsInProgress = cli.getVerificationRequestsToDeviceInProgress(cli.getUserId()!);
if (requestsInProgress.length) { if (requestsInProgress.length) {
// If there are multiple, we take the most recent. Equally if the user sends another request from // If there are multiple, we take the most recent. Equally if the user sends another request from
// another device after this screen has been shown, we'll switch to the new one, so this // another device after this screen has been shown, we'll switch to the new one, so this
@ -111,7 +106,7 @@ export class SetupEncryptionStore extends EventEmitter {
// do we have any other verified devices which are E2EE which we can verify against? // do we have any other verified devices which are E2EE which we can verify against?
const dehydratedDevice = await cli.getDehydratedDevice(); const dehydratedDevice = await cli.getDehydratedDevice();
const ownUserId = cli.getUserId(); const ownUserId = cli.getUserId()!;
const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId); const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId);
this.hasDevicesToVerifyAgainst = cli this.hasDevicesToVerifyAgainst = cli
.getStoredDevicesForUser(ownUserId) .getStoredDevicesForUser(ownUserId)
@ -119,7 +114,7 @@ export class SetupEncryptionStore extends EventEmitter {
(device) => (device) =>
device.getIdentityKey() && device.getIdentityKey() &&
(!dehydratedDevice || device.deviceId != dehydratedDevice.device_id) && (!dehydratedDevice || device.deviceId != dehydratedDevice.device_id) &&
crossSigningInfo.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(), crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true).isCrossSigningVerified(),
); );
this.phase = Phase.Intro; this.phase = Phase.Intro;
@ -183,11 +178,11 @@ export class SetupEncryptionStore extends EventEmitter {
}; };
public onVerificationRequestChange = (): void => { public onVerificationRequestChange = (): void => {
if (this.verificationRequest.cancelled) { if (this.verificationRequest?.cancelled) {
this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange); this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange);
this.verificationRequest = null; this.verificationRequest = null;
this.emit("update"); this.emit("update");
} else if (this.verificationRequest.phase === VERIF_PHASE_DONE) { } else if (this.verificationRequest?.phase === VERIF_PHASE_DONE) {
this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange); this.verificationRequest.off(VerificationRequestEvent.Change, this.onVerificationRequestChange);
this.verificationRequest = null; this.verificationRequest = null;
// At this point, the verification has finished, we just need to wait for // At this point, the verification has finished, we just need to wait for
@ -259,7 +254,7 @@ export class SetupEncryptionStore extends EventEmitter {
this.phase = Phase.Finished; this.phase = Phase.Finished;
this.emit("update"); this.emit("update");
// async - ask other clients for keys, if necessary // async - ask other clients for keys, if necessary
MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests(); MatrixClientPeg.get().crypto?.cancelAndResendAllOutgoingKeyRequests();
} }
private async setActiveVerificationRequest(request: VerificationRequest): Promise<void> { private async setActiveVerificationRequest(request: VerificationRequest): Promise<void> {

View file

@ -104,9 +104,9 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
private generateApps(room: Room): IApp[] { private generateApps(room: Room): IApp[] {
return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => {
return WidgetUtils.makeAppConfig( return WidgetUtils.makeAppConfig(
ev.getStateKey(), ev.getStateKey()!,
ev.getContent(), ev.getContent(),
ev.getSender(), ev.getSender()!,
ev.getRoomId(), ev.getRoomId(),
ev.getId(), ev.getId(),
); );
@ -172,7 +172,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
private onRoomStateEvents = (ev: MatrixEvent): void => { private onRoomStateEvents = (ev: MatrixEvent): void => {
if (ev.getType() !== "im.vector.modular.widgets") return; // TODO: Support m.widget too if (ev.getType() !== "im.vector.modular.widgets") return; // TODO: Support m.widget too
const roomId = ev.getRoomId(); const roomId = ev.getRoomId()!;
this.initRoom(roomId); this.initRoom(roomId);
this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); this.loadRoomWidgets(this.matrixClient.getRoom(roomId));
this.emit(UPDATE_EVENT, roomId); this.emit(UPDATE_EVENT, roomId);

View file

@ -37,7 +37,7 @@ export abstract class EchoContext extends Whenable<ContextTransactionState> impl
return this._state; return this._state;
} }
public get firstFailedTime(): Date { public get firstFailedTime(): Date | null {
const failedTxn = this.transactions.find((t) => t.didPreviouslyFail || t.status === TransactionStatus.Error); const failedTxn = this.transactions.find((t) => t.didPreviouslyFail || t.status === TransactionStatus.Error);
if (failedTxn) return failedTxn.startTime; if (failedTxn) return failedTxn.startTime;
return null; return null;

View file

@ -28,19 +28,19 @@ export const PROPERTY_UPDATED = "property_updated";
export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends EventEmitter { export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends EventEmitter {
private cache = new Map<K, { txn: EchoTransaction; val: V }>(); private cache = new Map<K, { txn: EchoTransaction; val: V }>();
protected matrixClient: MatrixClient; protected matrixClient: MatrixClient | null;
protected constructor(public readonly context: C, private lookupFn: (key: K) => V) { protected constructor(public readonly context: C, private lookupFn: (key: K) => V) {
super(); super();
} }
public setClient(client: MatrixClient): void { public setClient(client: MatrixClient | null): void {
const oldClient = this.matrixClient; const oldClient = this.matrixClient;
this.matrixClient = client; this.matrixClient = client;
this.onClientChanged(oldClient, client); this.onClientChanged(oldClient, client);
} }
protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void; protected abstract onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void;
/** /**
* Gets a value. If the key is in flight, the cached value will be returned. If * Gets a value. If the key is in flight, the cached value will be returned. If
@ -50,7 +50,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
* @returns The value for the key. * @returns The value for the key.
*/ */
public getValue(key: K): V { public getValue(key: K): V {
return this.cache.has(key) ? this.cache.get(key).val : this.lookupFn(key); return this.cache.has(key) ? this.cache.get(key)!.val : this.lookupFn(key);
} }
private cacheVal(key: K, val: V, txn: EchoTransaction): void { private cacheVal(key: K, val: V, txn: EchoTransaction): void {
@ -60,7 +60,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
private decacheKey(key: K): void { private decacheKey(key: K): void {
if (this.cache.has(key)) { if (this.cache.has(key)) {
this.context.disownTransaction(this.cache.get(key).txn); this.context.disownTransaction(this.cache.get(key)!.txn);
this.cache.delete(key); this.cache.delete(key);
this.emit(PROPERTY_UPDATED, key); this.emit(PROPERTY_UPDATED, key);
} }
@ -68,7 +68,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
protected markEchoReceived(key: K): void { protected markEchoReceived(key: K): void {
if (this.cache.has(key)) { if (this.cache.has(key)) {
const txn = this.cache.get(key).txn; const txn = this.cache.get(key)!.txn;
this.context.disownTransaction(txn); this.context.disownTransaction(txn);
txn.cancel(); txn.cancel();
} }
@ -78,7 +78,7 @@ export abstract class GenericEchoChamber<C extends EchoContext, K, V> extends Ev
public setValue(auditName: string, key: K, targetVal: V, runFn: RunFn, revertFn: RunFn): void { public setValue(auditName: string, key: K, targetVal: V, runFn: RunFn, revertFn: RunFn): void {
// Cancel any pending transactions for the same key // Cancel any pending transactions for the same key
if (this.cache.has(key)) { if (this.cache.has(key)) {
this.cache.get(key).txn.cancel(); this.cache.get(key)!.txn.cancel();
} }
const ctxn = this.context.beginTransaction(auditName, runFn); const ctxn = this.context.beginTransaction(auditName, runFn);

View file

@ -34,7 +34,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
super(context, (k) => this.properties.get(k)); super(context, (k) => this.properties.get(k));
} }
protected onClientChanged(oldClient: MatrixClient, newClient: MatrixClient): void { protected onClientChanged(oldClient: MatrixClient | null, newClient: MatrixClient | null): void {
this.properties.clear(); this.properties.clear();
oldClient?.removeListener(ClientEvent.AccountData, this.onAccountData); oldClient?.removeListener(ClientEvent.AccountData, this.onAccountData);
if (newClient) { if (newClient) {
@ -57,7 +57,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
}; };
private updateNotificationVolume(): void { private updateNotificationVolume(): void {
const state = getRoomNotifsState(this.matrixClient, this.context.room.roomId); const state = this.matrixClient ? getRoomNotifsState(this.matrixClient, this.context.room.roomId) : null;
if (state) this.properties.set(CachedRoomKey.NotificationVolume, state); if (state) this.properties.set(CachedRoomKey.NotificationVolume, state);
else this.properties.delete(CachedRoomKey.NotificationVolume); else this.properties.delete(CachedRoomKey.NotificationVolume);
this.markEchoReceived(CachedRoomKey.NotificationVolume); this.markEchoReceived(CachedRoomKey.NotificationVolume);

View file

@ -62,7 +62,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
*/ */
public getListState(tagId: TagID): ListNotificationState { public getListState(tagId: TagID): ListNotificationState {
if (this.listMap.has(tagId)) { if (this.listMap.has(tagId)) {
return this.listMap.get(tagId); return this.listMap.get(tagId)!;
} }
// TODO: Update if/when invites move out of the room list. // TODO: Update if/when invites move out of the room list.
@ -86,14 +86,14 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
if (!this.roomMap.has(room)) { if (!this.roomMap.has(room)) {
this.roomMap.set(room, new RoomNotificationState(room)); this.roomMap.set(room, new RoomNotificationState(room));
} }
return this.roomMap.get(room); return this.roomMap.get(room)!;
} }
public static get instance(): RoomNotificationStateStore { public static get instance(): RoomNotificationStateStore {
return RoomNotificationStateStore.internalInstance; return RoomNotificationStateStore.internalInstance;
} }
private onSync = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => { private onSync = (state: SyncState, prevState: SyncState | null, data?: ISyncStateData): void => {
// Only count visible rooms to not torment the user with notification counts in rooms they can't see. // Only count visible rooms to not torment the user with notification counts in rooms they can't see.
// This will include highlights from the previous version of the room internally // This will include highlights from the previous version of the room internally
const globalState = new SummarizedNotificationState(); const globalState = new SummarizedNotificationState();

View file

@ -58,20 +58,20 @@ export default class RightPanelStore extends ReadyWatchingStore {
* Resets the store. Intended for test usage only. * Resets the store. Intended for test usage only.
*/ */
public reset(): void { public reset(): void {
this.global = null; this.global = undefined;
this.byRoom = {}; this.byRoom = {};
this.viewedRoomId = null; this.viewedRoomId = null;
} }
protected async onReady(): Promise<any> { protected async onReady(): Promise<any> {
this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.matrixClient?.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
this.loadCacheFromSettings(); this.loadCacheFromSettings();
this.emitAndUpdateSettings(); this.emitAndUpdateSettings();
} }
protected async onNotReady(): Promise<any> { protected async onNotReady(): Promise<any> {
this.matrixClient.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate); this.matrixClient?.off(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
} }
protected onDispatcherAction(payload: ActionPayload): void { protected onDispatcherAction(payload: ActionPayload): void {
@ -376,7 +376,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
// the room member list. // the room member list.
if (SettingsStore.getValue("feature_right_panel_default_open") && !this.byRoom[this.viewedRoomId]?.isOpen) { if (SettingsStore.getValue("feature_right_panel_default_open") && !this.byRoom[this.viewedRoomId]?.isOpen) {
const history = [{ phase: RightPanelPhases.RoomMemberList }]; const history = [{ phase: RightPanelPhases.RoomMemberList }];
const room = this.viewedRoomId && this.mxClient?.getRoom(this.viewedRoomId); const room = this.viewedRoomId ? this.mxClient?.getRoom(this.viewedRoomId) : undefined;
if (!room?.isSpaceRoom()) { if (!room?.isSpaceRoom()) {
history.unshift({ phase: RightPanelPhases.RoomSummary }); history.unshift({ phase: RightPanelPhases.RoomSummary });
} }

View file

@ -123,7 +123,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
* @param inTagId The tag ID in which the room resides * @param inTagId The tag ID in which the room resides
* @returns The preview, or null if none present. * @returns The preview, or null if none present.
*/ */
public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string> { public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string | null> {
if (!room) return null; // invalid room, just return nothing if (!room) return null; // invalid room, just return nothing
if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId); if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId);
@ -132,14 +132,14 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
if (!previews) return null; if (!previews) return null;
if (!previews.has(inTagId)) { if (!previews.has(inTagId)) {
return previews.get(TAG_ANY); return previews.get(TAG_ANY)!;
} }
return previews.get(inTagId); return previews.get(inTagId) ?? null;
} }
public generatePreviewForEvent(event: MatrixEvent): string { public generatePreviewForEvent(event: MatrixEvent): string {
const previewDef = PREVIEWS[event.getType()]; const previewDef = PREVIEWS[event.getType()];
return previewDef?.previewer.getTextFor(event, null, true) ?? ""; return previewDef?.previewer.getTextFor(event, undefined, true) ?? "";
} }
private async generatePreview(room: Room, tagId?: TagID): Promise<void> { private async generatePreview(room: Room, tagId?: TagID): Promise<void> {
@ -171,7 +171,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
if (!previewDef) continue; if (!previewDef) continue;
if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue; if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue;
const anyPreview = previewDef.previewer.getTextFor(event, null); const anyPreview = previewDef.previewer.getTextFor(event);
if (!anyPreview) continue; // not previewable for some reason if (!anyPreview) continue; // not previewable for some reason
changed = changed || anyPreview !== map.get(TAG_ANY); changed = changed || anyPreview !== map.get(TAG_ANY);
@ -179,7 +179,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
const tagsToGenerate = Array.from(map.keys()).filter((t) => t !== TAG_ANY); // we did the any tag above const tagsToGenerate = Array.from(map.keys()).filter((t) => t !== TAG_ANY); // we did the any tag above
for (const genTagId of tagsToGenerate) { for (const genTagId of tagsToGenerate) {
const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId; const realTagId = genTagId === TAG_ANY ? undefined : genTagId;
const preview = previewDef.previewer.getTextFor(event, realTagId); const preview = previewDef.previewer.getTextFor(event, realTagId);
if (preview === anyPreview) { if (preview === anyPreview) {
changed = changed || anyPreview !== map.get(genTagId); changed = changed || anyPreview !== map.get(genTagId);

View file

@ -116,7 +116,7 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter. * Awaitable version of the sticky room setter.
* @param val The new room to sticky. * @param val The new room to sticky.
*/ */
public setStickyRoom(val: Room): void { public setStickyRoom(val: Room | null): void {
try { try {
this.updateStickyRoom(val); this.updateStickyRoom(val);
} catch (e) { } catch (e) {

View file

@ -29,7 +29,7 @@ export class PollStartEventPreview implements IPreview {
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>; public context!: React.ContextType<typeof MatrixClientContext>;
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
let eventContent = event.getContent(); let eventContent = event.getContent();
if (event.isRelation("m.replace")) { if (event.isRelation("m.replace")) {
@ -51,7 +51,7 @@ export class PollStartEventPreview implements IPreview {
let question = poll.question.text.trim(); let question = poll.question.text.trim();
question = sanitizeForTranslation(question); question = sanitizeForTranslation(question);
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) {
return question; return question;
} else { } else {
return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: question }); return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: question });

View file

@ -22,11 +22,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
export class StickerEventPreview implements IPreview { export class StickerEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
const stickerName = event.getContent()["body"]; const stickerName = event.getContent()["body"];
if (!stickerName) return null; if (!stickerName) return null;
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) {
return stickerName; return stickerName;
} else { } else {
return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName }); return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName });

View file

@ -78,7 +78,7 @@ const getSpaceContextKey = (space: SpaceKey): string => `mx_space_context_${spac
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => {
// [spaces, rooms] // [spaces, rooms]
return arr.reduce( return arr.reduce<[Room[], Room[]]>(
(result, room: Room) => { (result, room: Room) => {
result[room.isSpaceRoom() ? 0 : 1].push(room); result[room.isSpaceRoom() ? 0 : 1].push(room);
return result; return result;
@ -165,7 +165,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this.rootSpaces; return this.rootSpaces;
} }
public get activeSpace(): SpaceKey { public get activeSpace(): SpaceKey | undefined {
return this._activeSpace; return this._activeSpace;
} }
@ -228,7 +228,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
public setActiveSpace(space: SpaceKey, contextSwitch = true): void { public setActiveSpace(space: SpaceKey, contextSwitch = true): void {
if (!space || !this.matrixClient || space === this.activeSpace) return; if (!space || !this.matrixClient || space === this.activeSpace) return;
let cliSpace: Room; let cliSpace: Room | null = null;
if (!isMetaSpace(space)) { if (!isMetaSpace(space)) {
cliSpace = this.matrixClient.getRoom(space); cliSpace = this.matrixClient.getRoom(space);
if (!cliSpace?.isSpaceRoom()) return; if (!cliSpace?.isSpaceRoom()) return;
@ -246,6 +246,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// else if the last viewed room in this space is joined then view that // else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on // else view space home or home depending on what is being clicked on
if ( if (
roomId &&
cliSpace?.getMyMembership() !== "invite" && cliSpace?.getMyMembership() !== "invite" &&
this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" && this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" &&
this.isRoomInSpace(space, roomId) this.isRoomInSpace(space, roomId)
@ -348,10 +349,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
.filter((ev) => ev.getContent()?.via); .filter((ev) => ev.getContent()?.via);
return ( return (
sortBy(childEvents, (ev) => { sortBy(childEvents, (ev) => {
return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()); return getChildOrder(ev.getContent().order, ev.getTs(), ev.getStateKey()!);
}) })
.map((ev) => { .map((ev) => {
const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey(), true); const history = this.matrixClient.getRoomUpgradeHistory(ev.getStateKey()!, true);
return history[history.length - 1]; return history[history.length - 1];
}) })
.filter((room) => { .filter((room) => {
@ -373,7 +374,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const userId = this.matrixClient?.getUserId(); const userId = this.matrixClient?.getUserId();
const room = this.matrixClient?.getRoom(roomId); const room = this.matrixClient?.getRoom(roomId);
return ( return (
room?.currentState (room?.currentState
.getStateEvents(EventType.SpaceParent) .getStateEvents(EventType.SpaceParent)
.map((ev) => { .map((ev) => {
const content = ev.getContent(); const content = ev.getContent();
@ -396,7 +397,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return parent; return parent;
}) })
.filter(Boolean) || [] .filter(Boolean) as Room[]) || []
); );
} }
@ -467,7 +468,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
space: SpaceKey, space: SpaceKey,
includeDescendantSpaces = true, includeDescendantSpaces = true,
useCache = true, useCache = true,
): Set<string> => { ): Set<string> | undefined => {
if (space === MetaSpace.Home && this.allRoomsInHome) { if (space === MetaSpace.Home && this.allRoomsInHome) {
return undefined; return undefined;
} }
@ -490,7 +491,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private markTreeChildren = (rootSpace: Room, unseen: Set<Room>): void => { private markTreeChildren = (rootSpace: Room, unseen: Set<Room>): void => {
const stack = [rootSpace]; const stack = [rootSpace];
while (stack.length) { while (stack.length) {
const space = stack.pop(); const space = stack.pop()!;
unseen.delete(space); unseen.delete(space);
this.getChildSpaces(space.roomId).forEach((space) => { this.getChildSpaces(space.roomId).forEach((space) => {
if (unseen.has(space)) { if (unseen.has(space)) {
@ -646,7 +647,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
const visibleRooms = this.matrixClient.getVisibleRooms(); const visibleRooms = this.matrixClient.getVisibleRooms();
let dmBadgeSpace: MetaSpace; let dmBadgeSpace: MetaSpace | undefined;
// only show badges on dms on the most relevant space if such exists // only show badges on dms on the most relevant space if such exists
if (enabledMetaSpaces.has(MetaSpace.People)) { if (enabledMetaSpaces.has(MetaSpace.People)) {
dmBadgeSpace = MetaSpace.People; dmBadgeSpace = MetaSpace.People;
@ -702,8 +703,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
); // put all invites in the Home Space ); // put all invites in the Home Space
}; };
private static isInSpace(member: RoomMember): boolean { private static isInSpace(member?: RoomMember | null): boolean {
return member.membership === "join" || member.membership === "invite"; return member?.membership === "join" || member?.membership === "invite";
} }
// Method for resolving the impact of a single user's membership change in the given Space and its hierarchy // Method for resolving the impact of a single user's membership change in the given Space and its hierarchy
@ -755,11 +756,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.rootSpaces.forEach((s) => { this.rootSpaces.forEach((s) => {
// traverse each space tree in DFS to build up the supersets as you go up, // traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees. // reusing results from like subtrees.
const traverseSpace = (spaceId: string, parentPath: Set<string>): [Set<string>, Set<string>] => { const traverseSpace = (
spaceId: string,
parentPath: Set<string>,
): [Set<string>, Set<string>] | undefined => {
if (parentPath.has(spaceId)) return; // prevent cycles if (parentPath.has(spaceId)) return; // prevent cycles
// reuse existing results if multiple similar branches exist // reuse existing results if multiple similar branches exist
if (this.roomIdsBySpace.has(spaceId) && this.userIdsBySpace.has(spaceId)) { if (this.roomIdsBySpace.has(spaceId) && this.userIdsBySpace.has(spaceId)) {
return [this.roomIdsBySpace.get(spaceId), this.userIdsBySpace.get(spaceId)]; return [this.roomIdsBySpace.get(spaceId)!, this.userIdsBySpace.get(spaceId)!];
} }
const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId)); const [childSpaces, childRooms] = partitionSpacesAndRooms(this.getChildren(spaceId));
@ -865,7 +869,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (this.suggestedRooms.find((r) => r.room_id === roomId)) return; if (this.suggestedRooms.find((r) => r.room_id === roomId)) return;
// try to find the canonical parent first // try to find the canonical parent first
let parent: SpaceKey = this.getCanonicalParent(roomId)?.roomId; let parent: SpaceKey | undefined = this.getCanonicalParent(roomId)?.roomId;
// otherwise, try to find a root space which contains this room // otherwise, try to find a root space which contains this room
if (!parent) { if (!parent) {

View file

@ -54,7 +54,7 @@ export interface ISuggestedRoom extends IHierarchyRoom {
viaServers: string[]; viaServers: string[];
} }
export function isMetaSpace(spaceKey: SpaceKey): boolean { export function isMetaSpace(spaceKey?: SpaceKey): boolean {
return ( return (
spaceKey === MetaSpace.Home || spaceKey === MetaSpace.Home ||
spaceKey === MetaSpace.Favourites || spaceKey === MetaSpace.Favourites ||

View file

@ -33,6 +33,7 @@ import {
WidgetApiFromWidgetAction, WidgetApiFromWidgetAction,
WidgetKind, WidgetKind,
} from "matrix-widget-api"; } from "matrix-widget-api";
import { Optional } from "matrix-events-sdk";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
@ -156,7 +157,7 @@ export class ElementWidget extends Widget {
export class StopGapWidget extends EventEmitter { export class StopGapWidget extends EventEmitter {
private client: MatrixClient; private client: MatrixClient;
private messaging: ClientWidgetApi; private messaging: ClientWidgetApi | null;
private mockWidget: ElementWidget; private mockWidget: ElementWidget;
private scalarToken: string; private scalarToken: string;
private roomId?: string; private roomId?: string;
@ -172,7 +173,7 @@ export class StopGapWidget extends EventEmitter {
// Backwards compatibility: not all old widgets have a creatorUserId // Backwards compatibility: not all old widgets have a creatorUserId
if (!app.creatorUserId) { if (!app.creatorUserId) {
app = objectShallowClone(app); // clone to prevent accidental mutation app = objectShallowClone(app); // clone to prevent accidental mutation
app.creatorUserId = this.client.getUserId(); app.creatorUserId = this.client.getUserId()!;
} }
this.mockWidget = new ElementWidget(app); this.mockWidget = new ElementWidget(app);
@ -181,7 +182,7 @@ export class StopGapWidget extends EventEmitter {
this.virtual = app.eventId === undefined; this.virtual = app.eventId === undefined;
} }
private get eventListenerRoomId(): string { private get eventListenerRoomId(): Optional<string> {
// When widgets are listening to events, we need to make sure they're only // When widgets are listening to events, we need to make sure they're only
// receiving events for the right room. In particular, room widgets get locked // receiving events for the right room. In particular, room widgets get locked
// to the room they were added in while account widgets listen to the currently // to the room they were added in while account widgets listen to the currently
@ -192,7 +193,7 @@ export class StopGapWidget extends EventEmitter {
return SdkContextClass.instance.roomViewStore.getRoomId(); return SdkContextClass.instance.roomViewStore.getRoomId();
} }
public get widgetApi(): ClientWidgetApi { public get widgetApi(): ClientWidgetApi | null {
return this.messaging; return this.messaging;
} }
@ -214,7 +215,7 @@ export class StopGapWidget extends EventEmitter {
const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {}; const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {};
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,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(),
clientId: ELEMENT_CLIENT_ID, clientId: ELEMENT_CLIENT_ID,
@ -256,9 +257,9 @@ export class StopGapWidget extends EventEmitter {
ev.preventDefault(); ev.preventDefault();
if (ModalWidgetStore.instance.canOpenModalWidget()) { if (ModalWidgetStore.instance.canOpenModalWidget()) {
ModalWidgetStore.instance.openModalWidget(ev.detail.data, this.mockWidget, this.roomId); ModalWidgetStore.instance.openModalWidget(ev.detail.data, this.mockWidget, this.roomId);
this.messaging.transport.reply(ev.detail, {}); // ack this.messaging?.transport.reply(ev.detail, {}); // ack
} else { } else {
this.messaging.transport.reply(ev.detail, { this.messaging?.transport.reply(ev.detail, {
error: { error: {
message: "Unable to open modal at this time", message: "Unable to open modal at this time",
}, },
@ -301,14 +302,14 @@ export class StopGapWidget extends EventEmitter {
// Check up front if this is even a valid request // Check up front if this is even a valid request
const targetRoomId = (ev.detail.data || {}).room_id; const targetRoomId = (ev.detail.data || {}).room_id;
if (!targetRoomId) { if (!targetRoomId) {
return this.messaging.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{ return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
error: { message: "Room ID not supplied." }, error: { message: "Room ID not supplied." },
}); });
} }
// Check the widget's permission // Check the widget's permission
if (!this.messaging.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) { if (!this.messaging?.hasCapability(ElementWidgetCapabilities.CanChangeViewedRoom)) {
return this.messaging.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{ return this.messaging?.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
error: { message: "This widget does not have permission for this action (denied)." }, error: { message: "This widget does not have permission for this action (denied)." },
}); });
} }
@ -332,7 +333,7 @@ export class StopGapWidget extends EventEmitter {
const events = room.getLiveTimeline()?.getEvents() || []; const events = room.getLiveTimeline()?.getEvents() || [];
const roomEvent = events[events.length - 1]; const roomEvent = events[events.length - 1];
if (!roomEvent) continue; // force later code to think the room is fresh if (!roomEvent) continue; // force later code to think the room is fresh
this.readUpToMap[room.roomId] = roomEvent.getId(); this.readUpToMap[room.roomId] = roomEvent.getId()!;
} }
// Attach listeners for feeding events - the underlying widget classes handle permissions for us // Attach listeners for feeding events - the underlying widget classes handle permissions for us
@ -343,7 +344,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on( this.messaging.on(
`action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`, `action:${WidgetApiFromWidgetAction.UpdateAlwaysOnScreen}`,
(ev: CustomEvent<IStickyActionRequest>) => { (ev: CustomEvent<IStickyActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.AlwaysOnScreen)) { if (this.messaging?.hasCapability(MatrixCapabilities.AlwaysOnScreen)) {
ActiveWidgetStore.instance.setWidgetPersistence( ActiveWidgetStore.instance.setWidgetPersistence(
this.mockWidget.id, this.mockWidget.id,
this.roomId, this.roomId,
@ -360,7 +361,7 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on( this.messaging.on(
`action:${WidgetApiFromWidgetAction.SendSticker}`, `action:${WidgetApiFromWidgetAction.SendSticker}`,
(ev: CustomEvent<IStickerActionRequest>) => { (ev: CustomEvent<IStickerActionRequest>) => {
if (this.messaging.hasCapability(MatrixCapabilities.StickerSending)) { if (this.messaging?.hasCapability(MatrixCapabilities.StickerSending)) {
// Acknowledge first // Acknowledge first
ev.preventDefault(); ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
@ -381,7 +382,7 @@ export class StopGapWidget extends EventEmitter {
(ev: CustomEvent<IWidgetApiRequest>) => { (ev: CustomEvent<IWidgetApiRequest>) => {
// Acknowledge first // Acknowledge first
ev.preventDefault(); ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
// First close the stickerpicker // First close the stickerpicker
defaultDispatcher.dispatch({ action: "stickerpicker_close" }); defaultDispatcher.dispatch({ action: "stickerpicker_close" });
@ -415,7 +416,7 @@ export class StopGapWidget extends EventEmitter {
}), }),
}); });
} }
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); this.messaging?.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
}); });
} }
} }
@ -478,7 +479,7 @@ export class StopGapWidget extends EventEmitter {
private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => { private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
await this.client.decryptEventIfNeeded(ev); await this.client.decryptEventIfNeeded(ev);
if (ev.isDecryptionFailure()) return; if (ev.isDecryptionFailure()) return;
await this.messaging.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted()); await this.messaging?.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted());
}; };
private feedEvent(ev: MatrixEvent): void { private feedEvent(ev: MatrixEvent): void {
@ -490,7 +491,7 @@ export class StopGapWidget extends EventEmitter {
// //
// This approach of "read up to" prevents widgets receiving decryption spam from startup or // This approach of "read up to" prevents widgets receiving decryption spam from startup or
// receiving out-of-order events from backfill and such. // receiving out-of-order events from backfill and such.
const upToEventId = this.readUpToMap[ev.getRoomId()]; const upToEventId = this.readUpToMap[ev.getRoomId()!];
if (upToEventId) { if (upToEventId) {
// Small optimization for exact match (prevent search) // Small optimization for exact match (prevent search)
if (upToEventId === ev.getId()) { if (upToEventId === ev.getId()) {
@ -501,7 +502,7 @@ export class StopGapWidget extends EventEmitter {
// Timelines are most recent last, so reverse the order and limit ourselves to 100 events // Timelines are most recent last, so reverse the order and limit ourselves to 100 events
// to avoid overusing the CPU. // to avoid overusing the CPU.
const timeline = this.client.getRoom(ev.getRoomId()).getLiveTimeline(); const timeline = this.client.getRoom(ev.getRoomId()!).getLiveTimeline();
const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100); const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100);
for (const timelineEvent of events) { for (const timelineEvent of events) {

View file

@ -131,7 +131,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
protected async onReady(): Promise<void> { protected async onReady(): Promise<void> {
this.updateAllRooms(); this.updateAllRooms();
this.matrixClient.on(RoomStateEvent.Events, this.updateRoomFromState); this.matrixClient?.on(RoomStateEvent.Events, this.updateRoomFromState);
this.pinnedRef = SettingsStore.watchSetting("Widgets.pinned", null, this.updateFromSettings); this.pinnedRef = SettingsStore.watchSetting("Widgets.pinned", null, this.updateFromSettings);
this.layoutRef = SettingsStore.watchSetting("Widgets.layout", null, this.updateFromSettings); this.layoutRef = SettingsStore.watchSetting("Widgets.layout", null, this.updateFromSettings);
WidgetStore.instance.on(UPDATE_EVENT, this.updateFromWidgetStore); WidgetStore.instance.on(UPDATE_EVENT, this.updateFromWidgetStore);
@ -155,7 +155,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
private updateFromWidgetStore = (roomId?: string): void => { private updateFromWidgetStore = (roomId?: string): void => {
if (roomId) { if (roomId) {
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient?.getRoom(roomId);
if (room) this.recalculateRoom(room); if (room) this.recalculateRoom(room);
} else { } else {
this.updateAllRooms(); this.updateAllRooms();
@ -164,13 +164,13 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
private updateRoomFromState = (ev: MatrixEvent): void => { private updateRoomFromState = (ev: MatrixEvent): void => {
if (ev.getType() !== WIDGET_LAYOUT_EVENT_TYPE) return; if (ev.getType() !== WIDGET_LAYOUT_EVENT_TYPE) return;
const room = this.matrixClient.getRoom(ev.getRoomId()); const room = this.matrixClient?.getRoom(ev.getRoomId());
if (room) this.recalculateRoom(room); if (room) this.recalculateRoom(room);
}; };
private updateFromSettings = (settingName: string, roomId: string /* and other stuff */): void => { private updateFromSettings = (settingName: string, roomId: string /* and other stuff */): void => {
if (roomId) { if (roomId) {
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient?.getRoom(roomId);
if (room) this.recalculateRoom(room); if (room) this.recalculateRoom(room);
} else { } else {
this.updateAllRooms(); this.updateAllRooms();
@ -189,7 +189,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, ""); const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, "");
const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId); const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId);
let userLayout = SettingsStore.getValue<ILayoutSettings>("Widgets.layout", room.roomId); let userLayout = SettingsStore.getValue<ILayoutSettings | null>("Widgets.layout", room.roomId);
if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) { if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) {
// For some other layout that we don't really care about. The user can reset this // For some other layout that we don't really care about. The user can reset this
@ -197,7 +197,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
userLayout = null; userLayout = null;
} }
const roomLayout: ILayoutStateEvent = layoutEv ? layoutEv.getContent() : null; const roomLayout = layoutEv?.getContent<ILayoutStateEvent>() ?? null;
// We filter for the center container first. // We filter for the center container first.
// (An error is raised, if there are multiple widgets marked for the center container) // (An error is raised, if there are multiple widgets marked for the center container)
// For the right and top container multiple widgets are allowed. // For the right and top container multiple widgets are allowed.
@ -218,9 +218,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
// The widget won't need to be put in any other container. // The widget won't need to be put in any other container.
continue; continue;
} }
let targetContainer = defaultContainer; let targetContainer: Container = defaultContainer;
if (!!manualContainer || !!stateContainer) { if (!!manualContainer || !!stateContainer) {
targetContainer = manualContainer ? manualContainer : stateContainer; targetContainer = manualContainer ?? stateContainer!;
} else if (isLegacyPinned && !stateContainer) { } else if (isLegacyPinned && !stateContainer) {
// Special legacy case // Special legacy case
targetContainer = Container.Top; targetContainer = Container.Top;
@ -259,7 +259,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
// Determine width distribution and height of the top container now (the only relevant one) // Determine width distribution and height of the top container now (the only relevant one)
const widths: number[] = []; const widths: number[] = [];
let maxHeight = null; // null == default let maxHeight: number | null = null; // null == default
let doAutobalance = true; let doAutobalance = true;
for (let i = 0; i < topWidgets.length; i++) { for (let i = 0; i < topWidgets.length; i++) {
const widget = topWidgets[i]; const widget = topWidgets[i];
@ -487,7 +487,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
public canCopyLayoutToRoom(room: Room): boolean { public canCopyLayoutToRoom(room: Room): boolean {
if (!this.matrixClient) return false; // not ready yet if (!this.matrixClient) return false; // not ready yet
return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()); return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()!);
} }
public copyLayoutToRoom(room: Room): void { public copyLayoutToRoom(room: Room): void {
@ -508,7 +508,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
}; };
} }
} }
this.matrixClient.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, ""); this.matrixClient?.sendStateEvent(room.roomId, WIDGET_LAYOUT_EVENT_TYPE, evContent, "");
} }
private getAllWidgets(room: Room): [IApp, Container][] { private getAllWidgets(room: Room): [IApp, Container][] {
@ -516,7 +516,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
if (!containers) return []; if (!containers) return [];
const ret: [IApp, Container][] = []; const ret: [IApp, Container][] = [];
for (const container of Object.keys(containers)) { for (const container in containers) {
const widgets = containers[container as Container].ordered; const widgets = containers[container as Container].ordered;
for (const widget of widgets) { for (const widget of widgets) {
ret.push([widget, container as Container]); ret.push([widget, container as Container]);

View file

@ -32,7 +32,7 @@ export class WidgetPermissionStore {
// TODO (all functions here): Merge widgetKind with the widget definition // TODO (all functions here): Merge widgetKind with the widget definition
private packSettingKey(widget: Widget, kind: WidgetKind, roomId?: string): string { private packSettingKey(widget: Widget, kind: WidgetKind, roomId?: string): string {
let location = roomId; let location: string | null | undefined = roomId;
if (kind !== WidgetKind.Room) { if (kind !== WidgetKind.Room) {
location = this.context.client?.getUserId(); location = this.context.client?.getUserId();
} }

View file

@ -133,7 +133,7 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string {
.map((face) => { .map((face) => {
const src = face.src const src = face.src
?.map((srcElement) => { ?.map((srcElement) => {
let format: string; let format = "";
if (srcElement.format) { if (srcElement.format) {
format = `format("${srcElement.format}")`; format = `format("${srcElement.format}")`;
} }

View file

@ -66,7 +66,7 @@ interface Props {
export function IncomingCallToast({ callEvent }: Props): JSX.Element { export function IncomingCallToast({ callEvent }: Props): JSX.Element {
const roomId = callEvent.getRoomId()!; const roomId = callEvent.getRoomId()!;
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId) ?? undefined;
const call = useCall(roomId); const call = useCall(roomId);
const dismissToast = useCallback((): void => { const dismissToast = useCallback((): void => {
@ -107,7 +107,7 @@ export function IncomingCallToast({ callEvent }: Props): JSX.Element {
defaultDispatcher.dispatch<ViewRoomPayload>({ defaultDispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
room_id: room.roomId, room_id: room?.roomId,
view_call: true, view_call: true,
metricsTrigger: undefined, metricsTrigger: undefined,
}); });

View file

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

View file

@ -37,7 +37,7 @@ export function presentableTextForFile(
shortened = false, shortened = false,
): string { ): string {
let text = fallbackText; let text = fallbackText;
if (content.body?.length > 0) { if (content.body?.length) {
// The content body should be the name of the file including a // The content body should be the name of the file including a
// file extension. // file extension.
text = content.body; text = content.body;

View file

@ -69,7 +69,7 @@ async function isColrFontSupported(): Promise<boolean> {
try { try {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const context = canvas.getContext("2d"); const context = canvas.getContext("2d")!;
const img = new Image(); const img = new Image();
// eslint-disable-next-line // eslint-disable-next-line
const fontCOLR = const fontCOLR =

View file

@ -26,8 +26,8 @@ import { useEventEmitterState } from "../../hooks/useEventEmitter";
export const useLiveBeacons = (roomId: Room["roomId"], matrixClient: MatrixClient): Beacon[] => { export const useLiveBeacons = (roomId: Room["roomId"], matrixClient: MatrixClient): Beacon[] => {
const room = matrixClient.getRoom(roomId); const room = matrixClient.getRoom(roomId);
const liveBeacons = useEventEmitterState(room.currentState, RoomStateEvent.BeaconLiveness, () => const liveBeacons = useEventEmitterState(room?.currentState, RoomStateEvent.BeaconLiveness, () =>
room.currentState?.liveBeaconIds.map((beaconIdentifier) => room.currentState.beacons.get(beaconIdentifier)), room?.currentState?.liveBeaconIds.map((beaconIdentifier) => room.currentState.beacons.get(beaconIdentifier)),
); );
return liveBeacons; return liveBeacons;

View file

@ -102,7 +102,7 @@ export abstract class Member {
* Gets the MXC URL of this Member's avatar. For users this should be their profile's * Gets the MXC URL of this Member's avatar. For users this should be their profile's
* avatar MXC URL or null if none set. For 3PIDs this should always be null. * avatar MXC URL or null if none set. For 3PIDs this should always be null.
*/ */
public abstract getMxcAvatarUrl(): string; public abstract getMxcAvatarUrl(): string | null;
} }
export class DirectoryMember extends Member { export class DirectoryMember extends Member {
@ -127,8 +127,8 @@ export class DirectoryMember extends Member {
return this._userId; return this._userId;
} }
public getMxcAvatarUrl(): string { public getMxcAvatarUrl(): string | null {
return this.avatarUrl; return this.avatarUrl ?? null;
} }
} }
@ -156,7 +156,7 @@ export class ThreepidMember extends Member {
return this.id; return this.id;
} }
public getMxcAvatarUrl(): string { public getMxcAvatarUrl(): string | null {
return null; return null;
} }
} }

View file

@ -276,14 +276,14 @@ export default abstract class Exporter {
protected isReply(event: MatrixEvent): boolean { protected isReply(event: MatrixEvent): boolean {
const isEncrypted = event.isEncrypted(); const isEncrypted = event.isEncrypted();
// If encrypted, in_reply_to lies in event.event.content // If encrypted, in_reply_to lies in event.event.content
const content = isEncrypted ? event.event.content : event.getContent(); const content = isEncrypted ? event.event.content! : event.getContent();
const relatesTo = content["m.relates_to"]; const relatesTo = content["m.relates_to"];
return !!(relatesTo && relatesTo["m.in_reply_to"]); return !!(relatesTo && relatesTo["m.in_reply_to"]);
} }
protected isAttachment(mxEv: MatrixEvent): boolean { protected isAttachment(mxEv: MatrixEvent): boolean {
const attachmentTypes = ["m.sticker", "m.image", "m.file", "m.video", "m.audio"]; const attachmentTypes = ["m.sticker", "m.image", "m.file", "m.video", "m.audio"];
return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype); return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype!);
} }
public abstract export(): Promise<void>; public abstract export(): Promise<void>;

View file

@ -217,10 +217,10 @@ export default class HTMLExporter extends Exporter {
</html>`; </html>`;
} }
protected getAvatarURL(event: MatrixEvent): string | undefined { protected getAvatarURL(event: MatrixEvent): string | null {
const member = event.sender; const member = event.sender;
const avatarUrl = member?.getMxcAvatarUrl(); const avatarUrl = member?.getMxcAvatarUrl();
return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : undefined; return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : null;
} }
protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> { protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> {
@ -386,7 +386,7 @@ export default class HTMLExporter extends Exporter {
protected async createHTML(events: MatrixEvent[], start: number): Promise<string> { protected async createHTML(events: MatrixEvent[], start: number): Promise<string> {
let content = ""; let content = "";
let prevEvent = null; let prevEvent: MatrixEvent | null = null;
for (let i = start; i < Math.min(start + 1000, events.length); i++) { for (let i = start; i < Math.min(start + 1000, events.length); i++) {
const event = events[i]; const event = events[i];
this.updateProgress( this.updateProgress(

View file

@ -47,7 +47,7 @@ export default class JSONExporter extends Exporter {
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator; const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator;
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || ""; const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
const exporter = this.client.getUserId(); const exporter = this.client.getUserId()!;
const exporterName = this.room?.getMember(exporter)?.rawDisplayName || exporter; const exporterName = this.room?.getMember(exporter)?.rawDisplayName || exporter;
const jsonObject = { const jsonObject = {
room_name: this.room.name, room_name: this.room.name,

View file

@ -45,7 +45,7 @@ async function getRulesFromCssFile(path: string): Promise<CSSStyleSheet> {
// the style will only be parsed once it is added to a document // the style will only be parsed once it is added to a document
doc.body.appendChild(styleElement); doc.body.appendChild(styleElement);
return styleElement.sheet; return styleElement.sheet!;
} }
// naively culls unused css rules based on which classes are present in the html, // naively culls unused css rules based on which classes are present in the html,

View file

@ -19,6 +19,8 @@ import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement; type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
interface IThumbnail { interface IThumbnail {
info: { info: {
thumbnail_info?: { thumbnail_info?: {
@ -29,15 +31,13 @@ interface IThumbnail {
}; };
w: number; w: number;
h: number; h: number;
[BLURHASH_FIELD]: string; [BLURHASH_FIELD]?: string;
thumbnail_url?: string; thumbnail_url?: string;
thumbnail_file?: IEncryptedFile; thumbnail_file?: IEncryptedFile;
}; };
thumbnail: Blob; thumbnail: Blob;
} }
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
const MAX_WIDTH = 800; const MAX_WIDTH = 800;
const MAX_HEIGHT = 600; const MAX_HEIGHT = 600;
@ -88,7 +88,7 @@ export async function createThumbnail(
canvas = document.createElement("canvas"); canvas = document.createElement("canvas");
canvas.width = targetWidth; canvas.width = targetWidth;
canvas.height = targetHeight; canvas.height = targetHeight;
context = canvas.getContext("2d"); context = canvas.getContext("2d")!;
} }
context.drawImage(element, 0, 0, targetWidth, targetHeight); context.drawImage(element, 0, 0, targetWidth, targetHeight);
@ -97,7 +97,9 @@ export async function createThumbnail(
if (window.OffscreenCanvas && canvas instanceof OffscreenCanvas) { if (window.OffscreenCanvas && canvas instanceof OffscreenCanvas) {
thumbnailPromise = canvas.convertToBlob({ type: mimeType }); thumbnailPromise = canvas.convertToBlob({ type: mimeType });
} else { } else {
thumbnailPromise = new Promise<Blob>((resolve) => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType)); thumbnailPromise = new Promise<Blob>((resolve) =>
(canvas as HTMLCanvasElement).toBlob(resolve as BlobCallback, mimeType),
);
} }
const imageData = context.getImageData(0, 0, targetWidth, targetHeight); const imageData = context.getImageData(0, 0, targetWidth, targetHeight);

View file

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

View file

@ -33,21 +33,23 @@ describe("SlashCommands", () => {
let localRoom: LocalRoom; let localRoom: LocalRoom;
let command: Command; let command: Command;
const findCommand = (cmd: string): Command => { const findCommand = (cmd: string): Command | undefined => {
return Commands.find((command: Command) => command.command === cmd); return Commands.find((command: Command) => command.command === cmd);
}; };
const setCurrentRoom = (): void => { const setCurrentRoom = (): void => {
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId); mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => { mocked(client.getRoom).mockImplementation((rId: string): Room | null => {
if (rId === roomId) return room; if (rId === roomId) return room;
return null;
}); });
}; };
const setCurrentLocalRoon = (): void => { const setCurrentLocalRoon = (): void => {
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId); mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId);
mocked(client.getRoom).mockImplementation((rId: string): Room => { mocked(client.getRoom).mockImplementation((rId: string): Room | null => {
if (rId === localRoomId) return localRoom; if (rId === localRoomId) return localRoom;
return null;
}); });
}; };
@ -57,8 +59,8 @@ describe("SlashCommands", () => {
client = createTestClient(); client = createTestClient();
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client);
room = new Room(roomId, client, client.getUserId()); room = new Room(roomId, client, client.getUserId()!);
localRoom = new LocalRoom(localRoomId, client, client.getUserId()); localRoom = new LocalRoom(localRoomId, client, client.getUserId()!);
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId"); jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
}); });
@ -68,7 +70,7 @@ describe("SlashCommands", () => {
const command = getCommand("/topic pizza"); const command = getCommand("/topic pizza");
expect(command.cmd).toBeDefined(); expect(command.cmd).toBeDefined();
expect(command.args).toBeDefined(); expect(command.args).toBeDefined();
await command.cmd.run("room-id", null, command.args); await command.cmd!.run("room-id", null, command.args);
expect(client.setRoomTopic).toHaveBeenCalledWith("room-id", "pizza", undefined); expect(client.setRoomTopic).toHaveBeenCalledWith("room-id", "pizza", undefined);
}); });
}); });
@ -96,7 +98,7 @@ describe("SlashCommands", () => {
["converttoroom"], ["converttoroom"],
])("/%s", (commandName: string) => { ])("/%s", (commandName: string) => {
beforeEach(() => { beforeEach(() => {
command = findCommand(commandName); command = findCommand(commandName)!;
}); });
describe("isEnabled", () => { describe("isEnabled", () => {
@ -114,7 +116,7 @@ describe("SlashCommands", () => {
describe("/tovirtual", () => { describe("/tovirtual", () => {
beforeEach(() => { beforeEach(() => {
command = findCommand("tovirtual"); command = findCommand("tovirtual")!;
}); });
describe("isEnabled", () => { describe("isEnabled", () => {
@ -154,7 +156,7 @@ describe("SlashCommands", () => {
describe("/remakeolm", () => { describe("/remakeolm", () => {
beforeEach(() => { beforeEach(() => {
command = findCommand("remakeolm"); command = findCommand("remakeolm")!;
}); });
describe("isEnabled", () => { describe("isEnabled", () => {
@ -198,39 +200,39 @@ describe("SlashCommands", () => {
describe("/part", () => { describe("/part", () => {
it("should part room matching alias if found", async () => { it("should part room matching alias if found", async () => {
const room1 = new Room("room-id", client, client.getUserId()); const room1 = new Room("room-id", client, client.getUserId()!);
room1.getCanonicalAlias = jest.fn().mockReturnValue("#foo:bar"); room1.getCanonicalAlias = jest.fn().mockReturnValue("#foo:bar");
const room2 = new Room("other-room", client, client.getUserId()); const room2 = new Room("other-room", client, client.getUserId()!);
room2.getCanonicalAlias = jest.fn().mockReturnValue("#baz:bar"); room2.getCanonicalAlias = jest.fn().mockReturnValue("#baz:bar");
mocked(client.getRooms).mockReturnValue([room1, room2]); mocked(client.getRooms).mockReturnValue([room1, room2]);
const command = getCommand("/part #foo:bar"); const command = getCommand("/part #foo:bar");
expect(command.cmd).toBeDefined(); expect(command.cmd).toBeDefined();
expect(command.args).toBeDefined(); expect(command.args).toBeDefined();
await command.cmd.run("room-id", null, command.args); await command.cmd!.run("room-id", null, command.args);
expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything()); expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything());
}); });
it("should part room matching alt alias if found", async () => { it("should part room matching alt alias if found", async () => {
const room1 = new Room("room-id", client, client.getUserId()); const room1 = new Room("room-id", client, client.getUserId()!);
room1.getAltAliases = jest.fn().mockReturnValue(["#foo:bar"]); room1.getAltAliases = jest.fn().mockReturnValue(["#foo:bar"]);
const room2 = new Room("other-room", client, client.getUserId()); const room2 = new Room("other-room", client, client.getUserId()!);
room2.getAltAliases = jest.fn().mockReturnValue(["#baz:bar"]); room2.getAltAliases = jest.fn().mockReturnValue(["#baz:bar"]);
mocked(client.getRooms).mockReturnValue([room1, room2]); mocked(client.getRooms).mockReturnValue([room1, room2]);
const command = getCommand("/part #foo:bar"); const command = getCommand("/part #foo:bar");
expect(command.cmd).toBeDefined(); expect(command.cmd).toBeDefined();
expect(command.args).toBeDefined(); expect(command.args).toBeDefined();
await command.cmd.run("room-id", null, command.args); await command.cmd!.run("room-id", null, command.args);
expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything()); expect(client.leaveRoomChain).toHaveBeenCalledWith("room-id", expect.anything());
}); });
}); });
describe.each(["rainbow", "rainbowme"])("/%s", (commandName: string) => { describe.each(["rainbow", "rainbowme"])("/%s", (commandName: string) => {
const command = findCommand(commandName); const command = findCommand(commandName)!;
it("should return usage if no args", () => { it("should return usage if no args", () => {
expect(command.run(roomId, null, null).error).toBe(command.getUsage()); expect(command.run(roomId, null).error).toBe(command.getUsage());
}); });
it("should make things rainbowy", () => { it("should make things rainbowy", () => {

View file

@ -50,19 +50,19 @@ describe("SlidingSyncManager", () => {
}); });
it("adds a custom subscription for a lazy-loadable room", async () => { it("adds a custom subscription for a lazy-loadable room", async () => {
const roomId = "!lazy:id"; const roomId = "!lazy:id";
const room = new Room(roomId, client, client.getUserId()); const room = new Room(roomId, client, client.getUserId()!);
room.getLiveTimeline().initialiseState([ room.getLiveTimeline().initialiseState([
new MatrixEvent({ new MatrixEvent({
type: "m.room.create", type: "m.room.create",
state_key: "", state_key: "",
event_id: "$abc123", event_id: "$abc123",
sender: client.getUserId(), sender: client.getUserId()!,
content: { content: {
creator: client.getUserId(), creator: client.getUserId(),
}, },
}), }),
]); ]);
mocked(client.getRoom).mockImplementation((r: string): Room => { mocked(client.getRoom).mockImplementation((r: string): Room | null => {
if (roomId === r) { if (roomId === r) {
return room; return room;
} }

View file

@ -73,16 +73,16 @@ describe("EmojiProvider", function () {
add("😘"); //kissing_heart add("😘"); //kissing_heart
add("😘"); add("😘");
add("😚"); //kissing_closed_eyes add("😚"); //kissing_closed_eyes
const emojiProvider = new EmojiProvider(null); const emojiProvider = new EmojiProvider(null!);
let completionsList = await emojiProvider.getCompletions(":kis", { beginning: true, end: 3, start: 3 }); let completionsList = await emojiProvider.getCompletions(":kis", { beginning: true, end: 3, start: 3 });
expect(completionsList[0].component.props.title).toEqual(":kissing_heart:"); expect(completionsList[0].component!.props.title).toEqual(":kissing_heart:");
expect(completionsList[1].component.props.title).toEqual(":kissing_closed_eyes:"); expect(completionsList[1].component!.props.title).toEqual(":kissing_closed_eyes:");
completionsList = await emojiProvider.getCompletions(":kissing_c", { beginning: true, end: 3, start: 3 }); completionsList = await emojiProvider.getCompletions(":kissing_c", { beginning: true, end: 3, start: 3 });
expect(completionsList[0].component.props.title).toEqual(":kissing_closed_eyes:"); expect(completionsList[0].component!.props.title).toEqual(":kissing_closed_eyes:");
completionsList = await emojiProvider.getCompletions(":so", { beginning: true, end: 2, start: 2 }); completionsList = await emojiProvider.getCompletions(":so", { beginning: true, end: 2, start: 2 });
expect(completionsList[0].component.props.title).toEqual(":sob:"); expect(completionsList[0].component!.props.title).toEqual(":sob:");
}); });
}); });

View file

@ -46,7 +46,7 @@ jest.mock("../../../src/utils/beacon", () => ({
const roomId = "!roomId:server_name"; const roomId = "!roomId:server_name";
describe("MessagePanel", function () { describe("MessagePanel", function () {
let clock: FakeTimers.InstalledClock | null = null; let clock: FakeTimers.InstalledClock;
const realSetTimeout = window.setTimeout; const realSetTimeout = window.setTimeout;
const events = mkEvents(); const events = mkEvents();
const userId = "@me:here"; const userId = "@me:here";
@ -117,14 +117,11 @@ describe("MessagePanel", function () {
}); });
afterEach(function () { afterEach(function () {
if (clock) { clock?.uninstall();
clock.uninstall();
clock = null;
}
}); });
function mkEvents() { function mkEvents() {
const events = []; const events: MatrixEvent[] = [];
const ts0 = Date.now(); const ts0 = Date.now();
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
events.push( events.push(
@ -141,7 +138,7 @@ describe("MessagePanel", function () {
// Just to avoid breaking Dateseparator tests that might run at 00hrs // Just to avoid breaking Dateseparator tests that might run at 00hrs
function mkOneDayEvents() { function mkOneDayEvents() {
const events = []; const events: MatrixEvent[] = [];
const ts0 = Date.parse("09 May 2004 00:12:00 GMT"); const ts0 = Date.parse("09 May 2004 00:12:00 GMT");
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
events.push( events.push(
@ -158,7 +155,7 @@ describe("MessagePanel", function () {
// make a collection of events with some member events that should be collapsed with an EventListSummary // make a collection of events with some member events that should be collapsed with an EventListSummary
function mkMelsEvents() { function mkMelsEvents() {
const events = []; const events: MatrixEvent[] = [];
const ts0 = Date.now(); const ts0 = Date.now();
let i = 0; let i = 0;
@ -200,7 +197,7 @@ describe("MessagePanel", function () {
// A list of membership events only with nothing else // A list of membership events only with nothing else
function mkMelsEventsOnly() { function mkMelsEventsOnly() {
const events = []; const events: MatrixEvent[] = [];
const ts0 = Date.now(); const ts0 = Date.now();
let i = 0; let i = 0;
@ -323,7 +320,7 @@ describe("MessagePanel", function () {
} }
function isReadMarkerVisible(rmContainer?: Element) { function isReadMarkerVisible(rmContainer?: Element) {
return rmContainer?.children.length > 0; return !!rmContainer?.children.length;
} }
it("should show the events", function () { it("should show the events", function () {
@ -466,8 +463,8 @@ describe("MessagePanel", function () {
it("should collapse creation events", function () { it("should collapse creation events", function () {
const events = mkCreationEvents(); const events = mkCreationEvents();
const createEvent = events.find((event) => event.getType() === "m.room.create"); const createEvent = events.find((event) => event.getType() === "m.room.create")!;
const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption"); const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption")!;
client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null));
TestUtilsMatrix.upsertRoomStateEvents(room, events); TestUtilsMatrix.upsertRoomStateEvents(room, events);
@ -493,8 +490,8 @@ describe("MessagePanel", function () {
it("should not collapse beacons as part of creation events", function () { it("should not collapse beacons as part of creation events", function () {
const events = mkCreationEvents(); const events = mkCreationEvents();
const creationEvent = events.find((event) => event.getType() === "m.room.create"); const creationEvent = events.find((event) => event.getType() === "m.room.create")!;
const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender(), creationEvent.getRoomId(), { const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender()!, creationEvent.getRoomId()!, {
isLive: true, isLive: true,
}); });
const combinedEvents = [...events, beaconInfoEvent]; const combinedEvents = [...events, beaconInfoEvent];
@ -550,7 +547,7 @@ describe("MessagePanel", function () {
let els = container.getElementsByClassName("mx_GenericEventListSummary"); let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10);
const updatedEvents = [ const updatedEvents = [
...events, ...events,
@ -570,7 +567,7 @@ describe("MessagePanel", function () {
els = container.getElementsByClassName("mx_GenericEventListSummary"); els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(11);
}); });
it("prepends events into summaries during backward pagination without changing key", () => { it("prepends events into summaries during backward pagination without changing key", () => {
@ -580,7 +577,7 @@ describe("MessagePanel", function () {
let els = container.getElementsByClassName("mx_GenericEventListSummary"); let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10);
const updatedEvents = [ const updatedEvents = [
TestUtilsMatrix.mkMembership({ TestUtilsMatrix.mkMembership({
@ -600,7 +597,7 @@ describe("MessagePanel", function () {
els = container.getElementsByClassName("mx_GenericEventListSummary"); els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(11);
}); });
it("assigns different keys to summaries that get split up", () => { it("assigns different keys to summaries that get split up", () => {
@ -610,7 +607,7 @@ describe("MessagePanel", function () {
let els = container.getElementsByClassName("mx_GenericEventListSummary"); let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`); expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(10);
const updatedEvents = [ const updatedEvents = [
...events.slice(0, 5), ...events.slice(0, 5),
@ -628,10 +625,10 @@ describe("MessagePanel", function () {
els = container.getElementsByClassName("mx_GenericEventListSummary"); els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(2); expect(els.length).toEqual(2);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`); expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(5); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(5);
expect(els[1].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[5].getId()}`); expect(els[1].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[5].getId()}`);
expect(els[1].getAttribute("data-scroll-tokens").split(",").length).toEqual(5); expect(els[1].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(5);
}); });
// We test this because setting lookups can be *slow*, and we don't want // We test this because setting lookups can be *slow*, and we don't want
@ -681,7 +678,7 @@ describe("MessagePanel", function () {
const els = container.getElementsByClassName("mx_GenericEventListSummary"); const els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1); expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(3); expect(els[0].getAttribute("data-scroll-tokens")?.split(",")).toHaveLength(3);
}); });
}); });

View file

@ -68,7 +68,7 @@ describe("ThreadPanel", () => {
const found = container.querySelector(".mx_ThreadPanel_dropdown"); const found = container.querySelector(".mx_ThreadPanel_dropdown");
expect(found).toBeTruthy(); expect(found).toBeTruthy();
expect(screen.queryByRole("menu")).toBeFalsy(); expect(screen.queryByRole("menu")).toBeFalsy();
fireEvent.click(found); fireEvent.click(found!);
expect(screen.queryByRole("menu")).toBeTruthy(); expect(screen.queryByRole("menu")).toBeTruthy();
}); });
@ -80,11 +80,13 @@ describe("ThreadPanel", () => {
setFilterOption={() => undefined} setFilterOption={() => undefined}
/>, />,
); );
fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown")); fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown")!);
const found = screen.queryAllByRole("menuitemradio"); const found = screen.queryAllByRole("menuitemradio");
expect(found).toHaveLength(2); expect(found).toHaveLength(2);
const foundButton = screen.queryByRole("menuitemradio", { checked: true }); const foundButton = screen.queryByRole("menuitemradio", { checked: true });
expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t("Shows all threads from current room")}`); expect(foundButton?.textContent).toEqual(
`${_t("All threads")}${_t("Shows all threads from current room")}`,
);
expect(foundButton).toMatchSnapshot(); expect(foundButton).toMatchSnapshot();
}); });
}); });

View file

@ -111,7 +111,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
const { container } = getComponent(); const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]")); fireEvent.click(container.querySelector("[role=button]")!);
expect(dispatchSpy).toHaveBeenCalledWith({ expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom, action: Action.ViewRoom,
@ -144,7 +144,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
const { container } = getComponent(); const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]")); fireEvent.click(container.querySelector("[role=button]")!);
expect(dispatchSpy).toHaveBeenCalledWith({ expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom, action: Action.ViewRoom,
@ -163,7 +163,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
]); ]);
const { container, rerender } = getComponent(); const { container, rerender } = getComponent();
// error mode // error mode
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual( expect(container.querySelector(".mx_LeftPanelLiveShareWarning")?.textContent).toEqual(
"An error occurred whilst sharing your live location", "An error occurred whilst sharing your live location",
); );
@ -175,7 +175,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
rerender(<LeftPanelLiveShareWarning />); rerender(<LeftPanelLiveShareWarning />);
// default mode // default mode
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual( expect(container.querySelector(".mx_LeftPanelLiveShareWarning")?.textContent).toEqual(
"You are sharing your live location", "You are sharing your live location",
); );
}); });
@ -252,7 +252,7 @@ describe("<LeftPanelLiveShareWarning />", () => {
const { container } = getComponent(); const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]")); fireEvent.click(container.querySelector("[role=button]")!);
expect(dispatchSpy).toHaveBeenCalledWith({ expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom, action: Action.ViewRoom,

View file

@ -74,13 +74,13 @@ describe("ForwardDialog", () => {
const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => { const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => {
mockClient.getVisibleRooms.mockReturnValue(rooms); mockClient.getVisibleRooms.mockReturnValue(rooms);
mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId)); mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
const wrapper: RenderResult = render( const wrapper: RenderResult = render(
<ForwardDialog <ForwardDialog
matrixClient={mockClient} matrixClient={mockClient}
event={message} event={message}
permalinkCreator={new RoomPermalinkCreator(undefined, sourceRoom)} permalinkCreator={new RoomPermalinkCreator(undefined!, sourceRoom)}
onFinished={jest.fn()} onFinished={jest.fn()}
/>, />,
); );
@ -135,8 +135,8 @@ describe("ForwardDialog", () => {
}), }),
); );
let firstButton: Element; let firstButton!: Element;
let secondButton: Element; let secondButton!: Element;
const update = () => { const update = () => {
[firstButton, secondButton] = container.querySelectorAll(".mx_ForwardList_sendButton"); [firstButton, secondButton] = container.querySelectorAll(".mx_ForwardList_sendButton");
}; };
@ -253,7 +253,6 @@ describe("ForwardDialog", () => {
[M_ASSET.name]: { type: LocationAssetType.Pin }, [M_ASSET.name]: { type: LocationAssetType.Pin },
[M_LOCATION.name]: { [M_LOCATION.name]: {
uri: geoUri, uri: geoUri,
description: undefined as string,
}, },
}; };
expect(mockClient.sendEvent).toHaveBeenCalledWith( expect(mockClient.sendEvent).toHaveBeenCalledWith(
@ -269,7 +268,7 @@ describe("ForwardDialog", () => {
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy(); expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
sendToFirstRoom(container); sendToFirstRoom(container);
const timestamp = M_TIMESTAMP.findIn<number>(modernLocationEvent.getContent()); const timestamp = M_TIMESTAMP.findIn<number>(modernLocationEvent.getContent())!;
// text and description from original event are removed // text and description from original event are removed
// text gets new default message from event values // text gets new default message from event values
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`; const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
@ -280,7 +279,6 @@ describe("ForwardDialog", () => {
[M_ASSET.name]: { type: LocationAssetType.Pin }, [M_ASSET.name]: { type: LocationAssetType.Pin },
[M_LOCATION.name]: { [M_LOCATION.name]: {
uri: geoUri, uri: geoUri,
description: undefined as string,
}, },
}; };
expect(mockClient.sendEvent).toHaveBeenCalledWith( expect(mockClient.sendEvent).toHaveBeenCalledWith(
@ -301,7 +299,6 @@ describe("ForwardDialog", () => {
[M_ASSET.name]: { type: LocationAssetType.Pin }, [M_ASSET.name]: { type: LocationAssetType.Pin },
[M_LOCATION.name]: { [M_LOCATION.name]: {
uri: geoUri, uri: geoUri,
description: undefined as string,
}, },
geo_uri: geoUri, geo_uri: geoUri,
[M_TIMESTAMP.name]: timestamp, [M_TIMESTAMP.name]: timestamp,

View file

@ -92,7 +92,7 @@ function mockClient({
(it) => (it) =>
!searchTerm || !searchTerm ||
it.user_id.toLowerCase().includes(searchTerm) || it.user_id.toLowerCase().includes(searchTerm) ||
it.display_name.toLowerCase().includes(searchTerm), it.display_name?.toLowerCase().includes(searchTerm),
); );
return Promise.resolve({ return Promise.resolve({
results: results.slice(0, limit ?? +Infinity), results: results.slice(0, limit ?? +Infinity),
@ -138,7 +138,7 @@ describe("Spotlight Dialog", () => {
mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] }); mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] });
testRoom = mkRoom(mockedClient, "!test23:example.com"); testRoom = mkRoom(mockedClient, "!test23:example.com");
mocked(testRoom.getMyMembership).mockReturnValue("join"); mocked(testRoom.getMyMembership).mockReturnValue("join");
testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId()); testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId()!);
testLocalRoom.updateMyMembership("join"); testLocalRoom.updateMyMembership("join");
mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]); mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]);
@ -149,7 +149,7 @@ describe("Spotlight Dialog", () => {
describe("should apply filters supplied via props", () => { describe("should apply filters supplied via props", () => {
it("without filter", async () => { it("without filter", async () => {
const wrapper = mount(<SpotlightDialog initialFilter={null} onFinished={() => null} />); const wrapper = mount(<SpotlightDialog onFinished={() => null} />);
await act(async () => { await act(async () => {
await sleep(200); await sleep(200);
}); });

View file

@ -61,7 +61,7 @@ describe("<StyledRadioGroup />", () => {
value: optionC.value, value: optionC.value,
}); });
expect(getCheckedInput(component).value).toEqual(optionC.value); expect(getCheckedInput(component)?.value).toEqual(optionC.value);
}); });
it("selects correct buttons when definitions have checked prop", () => { it("selects correct buttons when definitions have checked prop", () => {
@ -99,7 +99,7 @@ describe("<StyledRadioGroup />", () => {
onChange, onChange,
}); });
fireEvent.click(getInputByValue(component, optionB.value)); fireEvent.click(getInputByValue(component, optionB.value)!);
expect(onChange).toHaveBeenCalledWith(optionB.value); expect(onChange).toHaveBeenCalledWith(optionB.value);
}); });

View file

@ -62,7 +62,7 @@ describe("<TooltipTarget />", () => {
const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o)); const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o));
it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => { it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => {
const wrapper = getComponent({ alignment: Alignment[alignment] }); const wrapper = getComponent({ alignment: Alignment[alignment] })!;
act(() => { act(() => {
Simulate.mouseOver(wrapper); Simulate.mouseOver(wrapper);
}); });
@ -70,7 +70,7 @@ describe("<TooltipTarget />", () => {
}); });
it("hides tooltip on mouseleave", () => { it("hides tooltip on mouseleave", () => {
const wrapper = getComponent(); const wrapper = getComponent()!;
act(() => { act(() => {
Simulate.mouseOver(wrapper); Simulate.mouseOver(wrapper);
}); });
@ -82,7 +82,7 @@ describe("<TooltipTarget />", () => {
}); });
it("displays tooltip on focus", () => { it("displays tooltip on focus", () => {
const wrapper = getComponent(); const wrapper = getComponent()!;
act(() => { act(() => {
Simulate.focus(wrapper); Simulate.focus(wrapper);
}); });
@ -90,7 +90,7 @@ describe("<TooltipTarget />", () => {
}); });
it("hides tooltip on blur", async () => { it("hides tooltip on blur", async () => {
const wrapper = getComponent(); const wrapper = getComponent()!;
act(() => { act(() => {
Simulate.focus(wrapper); Simulate.focus(wrapper);
}); });

View file

@ -53,7 +53,7 @@ describe("shareLocation", () => {
}, },
); );
shareLocationFn = shareLocation(client, roomId, shareType, null, () => {}); shareLocationFn = shareLocation(client, roomId, shareType, undefined, () => {});
}); });
it("should forward the call to doMaybeLocalRoomAction", () => { it("should forward the call to doMaybeLocalRoomAction", () => {

View file

@ -52,7 +52,7 @@ describe("EncryptionEvent", () => {
event = mkMessage({ event = mkMessage({
event: true, event: true,
room: roomId, room: roomId,
user: client.getUserId(), user: client.getUserId()!,
}); });
jest.spyOn(DMRoomMap, "shared").mockReturnValue({ jest.spyOn(DMRoomMap, "shared").mockReturnValue({
getUserIdForRoomId: jest.fn(), getUserIdForRoomId: jest.fn(),
@ -61,9 +61,9 @@ describe("EncryptionEvent", () => {
describe("for an encrypted room", () => { describe("for an encrypted room", () => {
beforeEach(() => { beforeEach(() => {
event.event.content.algorithm = algorithm; event.event.content!.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true); mocked(client.isRoomEncrypted).mockReturnValue(true);
const room = new Room(roomId, client, client.getUserId()); const room = new Room(roomId, client, client.getUserId()!);
mocked(client.getRoom).mockReturnValue(room); mocked(client.getRoom).mockReturnValue(room);
}); });
@ -91,7 +91,7 @@ describe("EncryptionEvent", () => {
describe("with unknown algorithm", () => { describe("with unknown algorithm", () => {
beforeEach(() => { beforeEach(() => {
event.event.content.algorithm = "unknown"; event.event.content!.algorithm = "unknown";
}); });
it("should show the expected texts", () => { it("should show the expected texts", () => {
@ -115,9 +115,9 @@ describe("EncryptionEvent", () => {
describe("for an encrypted local room", () => { describe("for an encrypted local room", () => {
beforeEach(() => { beforeEach(() => {
event.event.content.algorithm = algorithm; event.event.content!.algorithm = algorithm;
mocked(client.isRoomEncrypted).mockReturnValue(true); mocked(client.isRoomEncrypted).mockReturnValue(true);
const localRoom = new LocalRoom(roomId, client, client.getUserId()); const localRoom = new LocalRoom(roomId, client, client.getUserId()!);
mocked(client.getRoom).mockReturnValue(localRoom); mocked(client.getRoom).mockReturnValue(localRoom);
renderEncryptionEvent(client, event); renderEncryptionEvent(client, event);
}); });

View file

@ -236,9 +236,7 @@ describe("<MessageActionBar />", () => {
it("opens message context menu on click", () => { it("opens message context menu on click", () => {
const { getByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { getByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => { fireEvent.click(queryByLabelText("Options")!);
fireEvent.click(queryByLabelText("Options"));
});
expect(getByTestId("mx_MessageContextMenu")).toBeTruthy(); expect(getByTestId("mx_MessageContextMenu")).toBeTruthy();
}); });
}); });
@ -269,9 +267,7 @@ describe("<MessageActionBar />", () => {
it("dispatches reply event on click", () => { it("dispatches reply event on click", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => { fireEvent.click(queryByLabelText("Reply")!);
fireEvent.click(queryByLabelText("Reply"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({ expect(dispatcher.dispatch).toHaveBeenCalledWith({
action: "reply_to_event", action: "reply_to_event",
@ -306,9 +302,7 @@ describe("<MessageActionBar />", () => {
it("opens reaction picker on click", () => { it("opens reaction picker on click", () => {
const { queryByLabelText, getByTestId } = getComponent({ mxEvent: alicesMessageEvent }); const { queryByLabelText, getByTestId } = getComponent({ mxEvent: alicesMessageEvent });
act(() => { fireEvent.click(queryByLabelText("React")!);
fireEvent.click(queryByLabelText("React"));
});
expect(getByTestId("mx_EmojiPicker")).toBeTruthy(); expect(getByTestId("mx_EmojiPicker")).toBeTruthy();
}); });
}); });
@ -421,9 +415,7 @@ describe("<MessageActionBar />", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(getByLabelText("Reply in thread")); fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({ expect(dispatcher.dispatch).toHaveBeenCalledWith({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
@ -453,9 +445,7 @@ describe("<MessageActionBar />", () => {
it("opens thread on click", () => { it("opens thread on click", () => {
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(getByLabelText("Reply in thread")); fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({ expect(dispatcher.dispatch).toHaveBeenCalledWith({
action: Action.ShowThread, action: Action.ShowThread,
@ -482,9 +472,7 @@ describe("<MessageActionBar />", () => {
} as unknown as Thread); } as unknown as Thread);
const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent }); const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent });
act(() => {
fireEvent.click(getByLabelText("Reply in thread")); fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({ expect(dispatcher.dispatch).toHaveBeenCalledWith({
action: Action.ShowThread, action: Action.ShowThread,
@ -501,7 +489,7 @@ describe("<MessageActionBar />", () => {
describe("favourite button", () => { describe("favourite button", () => {
//for multiple event usecase //for multiple event usecase
const favButton = (evt: MatrixEvent) => { const favButton = (evt: MatrixEvent) => {
return getComponent({ mxEvent: evt }).getByTestId(evt.getId()); return getComponent({ mxEvent: evt }).getByTestId(evt.getId()!);
}; };
describe("when favourite_messages feature is enabled", () => { describe("when favourite_messages feature is enabled", () => {
@ -538,9 +526,7 @@ describe("<MessageActionBar />", () => {
expect(localStorageMock.getItem("io_element_favouriteMessages")).toBeNull(); expect(localStorageMock.getItem("io_element_favouriteMessages")).toBeNull();
//if only alice's event is fired //if only alice's event is fired
act(() => {
fireEvent.click(alicesAction); fireEvent.click(alicesAction);
});
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
@ -550,9 +536,7 @@ describe("<MessageActionBar />", () => {
); );
//when bob's event is fired,both should be styled and stored in localStorage //when bob's event is fired,both should be styled and stored in localStorage
act(() => {
fireEvent.click(bobsAction); fireEvent.click(bobsAction);
});
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(bobsAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(bobsAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
@ -567,9 +551,7 @@ describe("<MessageActionBar />", () => {
); );
//if decided to unfavourite bob's event by clicking again //if decided to unfavourite bob's event by clicking again
act(() => {
fireEvent.click(bobsAction); fireEvent.click(bobsAction);
});
expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar"); expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(localStorageMock.getItem("io_element_favouriteMessages")).toEqual('["$alices_message"]'); expect(localStorageMock.getItem("io_element_favouriteMessages")).toEqual('["$alices_message"]');
@ -599,9 +581,7 @@ describe("<MessageActionBar />", () => {
event.preventDefault = jest.fn(); event.preventDefault = jest.fn();
const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => { fireEvent(queryByLabelText(buttonLabel)!, event);
fireEvent(queryByLabelText(buttonLabel), event);
});
expect(event.stopPropagation).toHaveBeenCalled(); expect(event.stopPropagation).toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled(); expect(event.preventDefault).toHaveBeenCalled();
expect(queryByTestId("mx_MessageContextMenu")).toBeFalsy(); expect(queryByTestId("mx_MessageContextMenu")).toBeFalsy();
@ -610,9 +590,7 @@ describe("<MessageActionBar />", () => {
it("does shows context menu when right-clicking options", () => { it("does shows context menu when right-clicking options", () => {
const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => { fireEvent.contextMenu(queryByLabelText("Options")!);
fireEvent.contextMenu(queryByLabelText("Options"));
});
expect(queryByTestId("mx_MessageContextMenu")).toBeTruthy(); expect(queryByTestId("mx_MessageContextMenu")).toBeTruthy();
}); });
}); });

View file

@ -44,11 +44,11 @@ describe("MemberList", () => {
return room; return room;
} }
let parentDiv: HTMLDivElement = null; let parentDiv: HTMLDivElement;
let client: MatrixClient = null; let client: MatrixClient;
let root: Component = null; let root: Component;
let memberListRoom: Room; let memberListRoom: Room;
let memberList: MemberList = null; let memberList: MemberList;
let adminUsers: RoomMember[] = []; let adminUsers: RoomMember[] = [];
let moderatorUsers: RoomMember[] = []; let moderatorUsers: RoomMember[] = [];
@ -140,14 +140,13 @@ describe("MemberList", () => {
if (parentDiv) { if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv); ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove(); parentDiv.remove();
parentDiv = null;
} }
done(); done();
}); });
function expectOrderedByPresenceAndPowerLevel(memberTiles: MemberTile[], isPresenceEnabled: boolean) { function expectOrderedByPresenceAndPowerLevel(memberTiles: MemberTile[], isPresenceEnabled: boolean) {
let prevMember = null; let prevMember: RoomMember | undefined;
for (const tile of memberTiles) { for (const tile of memberTiles) {
const memberA = prevMember; const memberA = prevMember;
const memberB = tile.props.member; const memberB = tile.props.member;
@ -160,8 +159,8 @@ describe("MemberList", () => {
console.log(memberList.memberString(memberA)); console.log(memberList.memberString(memberA));
console.log(memberList.memberString(memberB)); console.log(memberList.memberString(memberB));
const userA = memberA.user; const userA = memberA.user!;
const userB = memberB.user; const userB = memberB.user!;
let groupChange = false; let groupChange = false;
@ -229,16 +228,16 @@ describe("MemberList", () => {
const onlineUsers = [adminUsers[0]]; const onlineUsers = [adminUsers[0]];
const offlineUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)]; const offlineUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)];
activeUsers.forEach((u) => { activeUsers.forEach((u) => {
u.user.currentlyActive = true; u.user!.currentlyActive = true;
u.user.presence = "online"; u.user!.presence = "online";
}); });
onlineUsers.forEach((u) => { onlineUsers.forEach((u) => {
u.user.currentlyActive = false; u.user!.currentlyActive = false;
u.user.presence = "online"; u.user!.presence = "online";
}); });
offlineUsers.forEach((u) => { offlineUsers.forEach((u) => {
u.user.currentlyActive = false; u.user!.currentlyActive = false;
u.user.presence = "offline"; u.user!.presence = "offline";
}); });
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
@ -268,18 +267,18 @@ describe("MemberList", () => {
const inactiveUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)]; const inactiveUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)];
activeUsers.forEach((u) => { activeUsers.forEach((u) => {
u.powerLevel = 100; // set everyone to the same PL to avoid running that check u.powerLevel = 100; // set everyone to the same PL to avoid running that check
u.user.lastPresenceTs = 1000; u.user!.lastPresenceTs = 1000;
u.user.lastActiveAgo = 0; u.user!.lastActiveAgo = 0;
}); });
semiActiveUsers.forEach((u) => { semiActiveUsers.forEach((u) => {
u.powerLevel = 100; u.powerLevel = 100;
u.user.lastPresenceTs = 1000; u.user!.lastPresenceTs = 1000;
u.user.lastActiveAgo = 50; u.user!.lastActiveAgo = 50;
}); });
inactiveUsers.forEach((u) => { inactiveUsers.forEach((u) => {
u.powerLevel = 100; u.powerLevel = 100;
u.user.lastPresenceTs = 1000; u.user!.lastPresenceTs = 1000;
u.user.lastActiveAgo = 100; u.user!.lastActiveAgo = 100;
}); });
// Bypass all the event listeners and skip to the good part // Bypass all the event listeners and skip to the good part
@ -294,10 +293,10 @@ describe("MemberList", () => {
// Intentionally put everyone on the same level to force a name comparison // Intentionally put everyone on the same level to force a name comparison
const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers]; const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers];
allUsers.forEach((u) => { allUsers.forEach((u) => {
u.user.currentlyActive = true; u.user!.currentlyActive = true;
u.user.presence = "online"; u.user!.presence = "online";
u.user.lastPresenceTs = 1000; u.user!.lastPresenceTs = 1000;
u.user.lastActiveAgo = 0; u.user!.lastActiveAgo = 0;
u.powerLevel = 100; u.powerLevel = 100;
}); });

View file

@ -37,13 +37,13 @@ describe("NotificationBadge", () => {
/>, />,
); );
fireEvent.click(container.firstChild); fireEvent.click(container.firstChild!);
expect(cb).toHaveBeenCalledTimes(1); expect(cb).toHaveBeenCalledTimes(1);
fireEvent.mouseEnter(container.firstChild); fireEvent.mouseEnter(container.firstChild!);
expect(cb).toHaveBeenCalledTimes(2); expect(cb).toHaveBeenCalledTimes(2);
fireEvent.mouseLeave(container.firstChild); fireEvent.mouseLeave(container.firstChild!);
expect(cb).toHaveBeenCalledTimes(3); expect(cb).toHaveBeenCalledTimes(3);
}); });

View file

@ -217,7 +217,7 @@ function createRoom(info: IRoomCreationInfo) {
const client: MatrixClient = MatrixClientPeg.get(); const client: MatrixClient = MatrixClientPeg.get();
const roomId = "!1234567890:domain"; const roomId = "!1234567890:domain";
const userId = client.getUserId(); const userId = client.getUserId()!;
if (info.isDm) { if (info.isDm) {
client.getAccountData = (eventType) => { client.getAccountData = (eventType) => {
expect(eventType).toEqual("m.direct"); expect(eventType).toEqual("m.direct");
@ -231,7 +231,7 @@ function createRoom(info: IRoomCreationInfo) {
pendingEventOrdering: PendingEventOrdering.Detached, pendingEventOrdering: PendingEventOrdering.Detached,
}); });
const otherJoinEvents = []; const otherJoinEvents: MatrixEvent[] = [];
for (const otherUserId of info.userIds) { for (const otherUserId of info.userIds) {
otherJoinEvents.push(mkJoinEvent(roomId, otherUserId)); otherJoinEvents.push(mkJoinEvent(roomId, otherUserId));
} }

View file

@ -88,7 +88,7 @@ describe("<SendMessageComposer/>", () => {
const documentOffset = new DocumentOffset(11, true); const documentOffset = new DocumentOffset(11, true);
model.update("hello world", "insertText", documentOffset); model.update("hello world", "insertText", documentOffset);
const content = createMessageContent(model, null, undefined, permalinkCreator); const content = createMessageContent(model, undefined, undefined, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "hello world", body: "hello world",
@ -101,7 +101,7 @@ describe("<SendMessageComposer/>", () => {
const documentOffset = new DocumentOffset(13, true); const documentOffset = new DocumentOffset(13, true);
model.update("hello *world*", "insertText", documentOffset); model.update("hello *world*", "insertText", documentOffset);
const content = createMessageContent(model, null, undefined, permalinkCreator); const content = createMessageContent(model, undefined, undefined, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "hello *world*", body: "hello *world*",
@ -116,7 +116,7 @@ describe("<SendMessageComposer/>", () => {
const documentOffset = new DocumentOffset(22, true); const documentOffset = new DocumentOffset(22, true);
model.update("/me blinks __quickly__", "insertText", documentOffset); model.update("/me blinks __quickly__", "insertText", documentOffset);
const content = createMessageContent(model, null, undefined, permalinkCreator); const content = createMessageContent(model, undefined, undefined, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "blinks __quickly__", body: "blinks __quickly__",
@ -132,7 +132,7 @@ describe("<SendMessageComposer/>", () => {
model.update("/me ✨sparkles✨", "insertText", documentOffset); model.update("/me ✨sparkles✨", "insertText", documentOffset);
expect(model.parts.length).toEqual(4); // Emoji count as non-text expect(model.parts.length).toEqual(4); // Emoji count as non-text
const content = createMessageContent(model, null, undefined, permalinkCreator); const content = createMessageContent(model, undefined, undefined, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "✨sparkles✨", body: "✨sparkles✨",
@ -146,7 +146,7 @@ describe("<SendMessageComposer/>", () => {
model.update("//dev/null is my favourite place", "insertText", documentOffset); model.update("//dev/null is my favourite place", "insertText", documentOffset);
const content = createMessageContent(model, null, undefined, permalinkCreator); const content = createMessageContent(model, undefined, undefined, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "/dev/null is my favourite place", body: "/dev/null is my favourite place",
@ -216,7 +216,7 @@ describe("<SendMessageComposer/>", () => {
// ensure the right state was persisted to localStorage // ensure the right state was persisted to localStorage
unmount(); unmount();
expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({ expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
parts: [{ type: "plain", text: "Test Text" }], parts: [{ type: "plain", text: "Test Text" }],
replyEventId: mockEvent.getId(), replyEventId: mockEvent.getId(),
}); });
@ -249,7 +249,7 @@ describe("<SendMessageComposer/>", () => {
// ensure the right state was persisted to localStorage // ensure the right state was persisted to localStorage
window.dispatchEvent(new Event("beforeunload")); window.dispatchEvent(new Event("beforeunload"));
expect(JSON.parse(localStorage.getItem(key))).toStrictEqual({ expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({
parts: [{ type: "plain", text: "Hello World" }], parts: [{ type: "plain", text: "Hello World" }],
}); });
}); });
@ -260,7 +260,7 @@ describe("<SendMessageComposer/>", () => {
const { container } = getComponent({ replyToEvent: mockEvent }); const { container } = getComponent({ replyToEvent: mockEvent });
addTextToComposer(container, "This is a message"); addTextToComposer(container, "This is a message");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" }); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
await waitFor(() => { await waitFor(() => {
expect(spyDispatcher).toHaveBeenCalledWith({ expect(spyDispatcher).toHaveBeenCalledWith({
@ -271,7 +271,7 @@ describe("<SendMessageComposer/>", () => {
}); });
expect(container.textContent).toBe(""); expect(container.textContent).toBe("");
const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`); const str = sessionStorage.getItem(`mx_cider_history_${mockRoom.roomId}[0]`)!;
expect(JSON.parse(str)).toStrictEqual({ expect(JSON.parse(str)).toStrictEqual({
parts: [{ type: "plain", text: "This is a message" }], parts: [{ type: "plain", text: "This is a message" }],
replyEventId: mockEvent.getId(), replyEventId: mockEvent.getId(),
@ -289,7 +289,7 @@ describe("<SendMessageComposer/>", () => {
const { container } = getComponent(); const { container } = getComponent();
addTextToComposer(container, "test message"); addTextToComposer(container, "test message");
fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer"), { key: "Enter" }); fireEvent.keyDown(container.querySelector(".mx_SendMessageComposer")!, { key: "Enter" });
expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, { expect(mockClient.sendMessage).toHaveBeenCalledWith("myfakeroom", null, {
body: "test message", body: "test message",

View file

@ -310,11 +310,8 @@ describe("<Notifications />", () => {
it("enables email notification when toggling on", async () => { it("enables email notification when toggling on", async () => {
await getComponentAndWait(); await getComponentAndWait();
const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!;
await act(async () => {
fireEvent.click(emailToggle); fireEvent.click(emailToggle);
});
expect(mockClient.setPusher).toHaveBeenCalledWith( expect(mockClient.setPusher).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@ -332,11 +329,8 @@ describe("<Notifications />", () => {
mockClient.setPusher.mockRejectedValue({}); mockClient.setPusher.mockRejectedValue({});
await getComponentAndWait(); await getComponentAndWait();
const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!;
await act(async () => {
fireEvent.click(emailToggle); fireEvent.click(emailToggle);
});
// force render // force render
await flushPromises(); await flushPromises();
@ -349,11 +343,8 @@ describe("<Notifications />", () => {
mockClient.getPushers.mockResolvedValue({ pushers: [testPusher] }); mockClient.getPushers.mockResolvedValue({ pushers: [testPusher] });
await getComponentAndWait(); await getComponentAndWait();
const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]'); const emailToggle = screen.getByTestId("notif-email-switch").querySelector('div[role="switch"]')!;
await act(async () => {
fireEvent.click(emailToggle); fireEvent.click(emailToggle);
});
expect(mockClient.setPusher).toHaveBeenCalledWith({ expect(mockClient.setPusher).toHaveBeenCalledWith({
...testPusher, ...testPusher,
@ -364,21 +355,19 @@ describe("<Notifications />", () => {
it("toggles and sets settings correctly", async () => { it("toggles and sets settings correctly", async () => {
await getComponentAndWait(); await getComponentAndWait();
let audioNotifsToggle: HTMLDivElement; let audioNotifsToggle!: HTMLDivElement;
const update = () => { const update = () => {
audioNotifsToggle = screen audioNotifsToggle = screen
.getByTestId("notif-setting-audioNotificationsEnabled") .getByTestId("notif-setting-audioNotificationsEnabled")
.querySelector('div[role="switch"]'); .querySelector('div[role="switch"]')!;
}; };
update(); update();
expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("true"); expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("true");
expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(true); expect(SettingsStore.getValue("audioNotificationsEnabled")).toEqual(true);
act(() => {
fireEvent.click(audioNotifsToggle); fireEvent.click(audioNotifsToggle);
});
update(); update();
expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("false"); expect(audioNotifsToggle.getAttribute("aria-checked")).toEqual("false");
@ -422,7 +411,7 @@ describe("<Notifications />", () => {
const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id); const oneToOneRuleElement = screen.getByTestId(section + oneToOneRule.rule_id);
await act(async () => { await act(async () => {
const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]'); const offToggle = oneToOneRuleElement.querySelector('input[type="radio"]')!;
fireEvent.click(offToggle); fireEvent.click(offToggle);
}); });

View file

@ -55,7 +55,7 @@ describe("RolesRoomSettingsTab", () => {
}; };
const getElementCallSwitch = (tab: RenderResult): HTMLElement => { const getElementCallSwitch = (tab: RenderResult): HTMLElement => {
return tab.container.querySelector("[data-testid='element-call-switch']"); return tab.container.querySelector("[data-testid='element-call-switch']")!;
}; };
describe("correct state", () => { describe("correct state", () => {
@ -87,7 +87,7 @@ describe("RolesRoomSettingsTab", () => {
const tab = renderTab(); const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!);
await waitFor(() => await waitFor(() =>
expect(cli.sendStateEvent).toHaveBeenCalledWith( expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId, room.roomId,
@ -107,7 +107,7 @@ describe("RolesRoomSettingsTab", () => {
const tab = renderTab(); const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!);
await waitFor(() => await waitFor(() =>
expect(cli.sendStateEvent).toHaveBeenCalledWith( expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId, room.roomId,
@ -128,7 +128,7 @@ describe("RolesRoomSettingsTab", () => {
const tab = renderTab(); const tab = renderTab();
fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")); fireEvent.click(getElementCallSwitch(tab).querySelector(".mx_ToggleSwitch")!);
await waitFor(() => await waitFor(() =>
expect(cli.sendStateEvent).toHaveBeenCalledWith( expect(cli.sendStateEvent).toHaveBeenCalledWith(
room.roomId, room.roomId,

View file

@ -94,13 +94,13 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const getByTestId = (container: Element, id: string) => container.querySelector(`[data-test-id=${id}]`); const getByTestId = (container: Element, id: string) => container.querySelector(`[data-test-id=${id}]`);
const toggleGuestAccessSection = async (component: Element) => { const toggleGuestAccessSection = async (component: Element) => {
const toggleButton = getByTestId(component, "toggle-guest-access-btn"); const toggleButton = getByTestId(component, "toggle-guest-access-btn")!;
await act(async () => { await act(async () => {
Simulate.click(toggleButton); Simulate.click(toggleButton);
}); });
}; };
const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"'); const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"]');
const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"'); const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"]');
const getErrorMessage = (component: Element) => getByTestId(component, "space-settings-error")?.textContent; const getErrorMessage = (component: Element) => getByTestId(component, "space-settings-error")?.textContent;
beforeEach(() => { beforeEach(() => {
@ -150,10 +150,10 @@ describe("<SpaceSettingsVisibilityTab />", () => {
await toggleGuestAccessSection(component); await toggleGuestAccessSection(component);
const guestAccessInput = getGuestAccessToggle(component); const guestAccessInput = getGuestAccessToggle(component);
expect(guestAccessInput.getAttribute("aria-checked")).toEqual("true"); expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("true");
await act(async () => { await act(async () => {
Simulate.click(guestAccessInput); Simulate.click(guestAccessInput!);
}); });
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
@ -165,7 +165,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
); );
// toggled off // toggled off
expect(guestAccessInput.getAttribute("aria-checked")).toEqual("false"); expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("false");
}); });
it("renders error message when update fails", async () => { it("renders error message when update fails", async () => {
@ -174,7 +174,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const component = getComponent({ space }); const component = getComponent({ space });
await toggleGuestAccessSection(component); await toggleGuestAccessSection(component);
await act(async () => { await act(async () => {
Simulate.click(getGuestAccessToggle(component)); Simulate.click(getGuestAccessToggle(component)!);
}); });
expect(getErrorMessage(component)).toEqual("Failed to update the guest access of this space"); expect(getErrorMessage(component)).toEqual("Failed to update the guest access of this space");
@ -187,7 +187,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
await toggleGuestAccessSection(component); await toggleGuestAccessSection(component);
expect(getGuestAccessToggle(component).getAttribute("aria-disabled")).toEqual("true"); expect(getGuestAccessToggle(component)?.getAttribute("aria-disabled")).toEqual("true");
}); });
}); });
@ -197,7 +197,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const component = getComponent({ space }); const component = getComponent({ space });
// toggle off because space settings is != WorldReadable // toggle off because space settings is != WorldReadable
expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("false"); expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false");
}); });
it("updates history visibility on toggle", async () => { it("updates history visibility on toggle", async () => {
@ -205,10 +205,10 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const component = getComponent({ space }); const component = getComponent({ space });
// toggle off because space settings is != WorldReadable // toggle off because space settings is != WorldReadable
expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("false"); expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false");
await act(async () => { await act(async () => {
Simulate.click(getHistoryVisibilityToggle(component)); Simulate.click(getHistoryVisibilityToggle(component)!);
}); });
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
@ -218,7 +218,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
"", "",
); );
expect(getHistoryVisibilityToggle(component).getAttribute("aria-checked")).toEqual("true"); expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("true");
}); });
it("renders error message when history update fails", async () => { it("renders error message when history update fails", async () => {
@ -227,7 +227,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const component = getComponent({ space }); const component = getComponent({ space });
await act(async () => { await act(async () => {
Simulate.click(getHistoryVisibilityToggle(component)); Simulate.click(getHistoryVisibilityToggle(component)!);
}); });
expect(getErrorMessage(component)).toEqual("Failed to update the history visibility of this space"); expect(getErrorMessage(component)).toEqual("Failed to update the history visibility of this space");
@ -237,7 +237,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
const space = makeMockSpace(mockMatrixClient, joinRule, guestRule, historyRule); const space = makeMockSpace(mockMatrixClient, joinRule, guestRule, historyRule);
(space.currentState.maySendStateEvent as jest.Mock).mockReturnValue(false); (space.currentState.maySendStateEvent as jest.Mock).mockReturnValue(false);
const component = getComponent({ space }); const component = getComponent({ space });
expect(getHistoryVisibilityToggle(component).getAttribute("aria-disabled")).toEqual("true"); expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-disabled")).toEqual("true");
}); });
}); });

View file

@ -34,7 +34,7 @@ describe("languageHandler", function () {
const plurals = "and %(count)s others..."; const plurals = "and %(count)s others...";
const variableSub = "You are now ignoring %(userId)s"; const variableSub = "You are now ignoring %(userId)s";
type TestCase = [string, string, Record<string, unknown>, Record<string, unknown>, TranslatedString]; type TestCase = [string, string, Record<string, unknown>, Record<string, unknown> | undefined, TranslatedString];
const testCasesEn: TestCase[] = [ const testCasesEn: TestCase[] = [
// description of the test case, translationString, variables, tags, expected result // description of the test case, translationString, variables, tags, expected result
["translates a basic string", basicString, {}, undefined, "Rooms"], ["translates a basic string", basicString, {}, undefined, "Rooms"],
@ -82,7 +82,7 @@ describe("languageHandler", function () {
], ],
]; ];
let oldNodeEnv: string; let oldNodeEnv: string | undefined;
beforeAll(() => { beforeAll(() => {
oldNodeEnv = process.env.NODE_ENV; oldNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "test"; process.env.NODE_ENV = "test";
@ -110,7 +110,7 @@ describe("languageHandler", function () {
}); });
it.each(testCasesEn)("%s", (_d, translationString, variables, tags, result) => { it.each(testCasesEn)("%s", (_d, translationString, variables, tags, result) => {
expect(_t(translationString, variables, tags)).toEqual(result); expect(_t(translationString, variables, tags!)).toEqual(result);
}); });
it("replacements in the wrong order", function () { it("replacements in the wrong order", function () {
@ -168,25 +168,25 @@ describe("languageHandler", function () {
describe("_t", () => { describe("_t", () => {
it("translated correctly when plural string exists for count", () => { it("translated correctly when plural string exists for count", () => {
expect(_t(lvExistingPlural, { count: 1, filename: "test.txt" }, undefined)).toEqual( expect(_t(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual(
"Качване на test.txt и 1 друг", "Качване на test.txt и 1 друг",
); );
}); });
it.each(pluralCases)("%s", (_d, translationString, variables, tags, result) => { it.each(pluralCases)("%s", (_d, translationString, variables, tags, result) => {
expect(_t(translationString, variables, tags)).toEqual(result); expect(_t(translationString, variables, tags!)).toEqual(result);
}); });
}); });
describe("_tDom()", () => { describe("_tDom()", () => {
it("translated correctly when plural string exists for count", () => { it("translated correctly when plural string exists for count", () => {
expect(_tDom(lvExistingPlural, { count: 1, filename: "test.txt" }, undefined)).toEqual( expect(_tDom(lvExistingPlural, { count: 1, filename: "test.txt" })).toEqual(
"Качване на test.txt и 1 друг", "Качване на test.txt и 1 друг",
); );
}); });
it.each(pluralCases)( it.each(pluralCases)(
"%s and translates with fallback locale, attributes fallback locale", "%s and translates with fallback locale, attributes fallback locale",
(_d, translationString, variables, tags, result) => { (_d, translationString, variables, tags, result) => {
expect(_tDom(translationString, variables, tags)).toEqual(<span lang="en">{result}</span>); expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
}, },
); );
}); });
@ -197,7 +197,7 @@ describe("languageHandler", function () {
it.each(testCasesEn)( it.each(testCasesEn)(
"%s and translates with fallback locale", "%s and translates with fallback locale",
(_d, translationString, variables, tags, result) => { (_d, translationString, variables, tags, result) => {
expect(_t(translationString, variables, tags)).toEqual(result); expect(_t(translationString, variables, tags!)).toEqual(result);
}, },
); );
}); });
@ -206,7 +206,7 @@ describe("languageHandler", function () {
it.each(testCasesEn)( it.each(testCasesEn)(
"%s and translates with fallback locale, attributes fallback locale", "%s and translates with fallback locale, attributes fallback locale",
(_d, translationString, variables, tags, result) => { (_d, translationString, variables, tags, result) => {
expect(_tDom(translationString, variables, tags)).toEqual(<span lang="en">{result}</span>); expect(_tDom(translationString, variables, tags!)).toEqual(<span lang="en">{result}</span>);
}, },
); );
}); });
@ -216,12 +216,12 @@ describe("languageHandler", function () {
describe("when languages dont load", () => { describe("when languages dont load", () => {
it("_t", () => { it("_t", () => {
const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary"; const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary";
expect(_t(STRING_NOT_IN_THE_DICTIONARY, {}, undefined)).toEqual(STRING_NOT_IN_THE_DICTIONARY); expect(_t(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual(STRING_NOT_IN_THE_DICTIONARY);
}); });
it("_tDom", () => { it("_tDom", () => {
const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary"; const STRING_NOT_IN_THE_DICTIONARY = "a string that isn't in the translations dictionary";
expect(_tDom(STRING_NOT_IN_THE_DICTIONARY, {}, undefined)).toEqual( expect(_tDom(STRING_NOT_IN_THE_DICTIONARY, {})).toEqual(
<span lang="en">{STRING_NOT_IN_THE_DICTIONARY}</span>, <span lang="en">{STRING_NOT_IN_THE_DICTIONARY}</span>,
); );
}); });

View file

@ -383,7 +383,7 @@ describe("JitsiCall", () => {
await waitFor( await waitFor(
() => () =>
expect( expect(
room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(),
).toEqual({ ).toEqual({
devices: [client.getDeviceId()], devices: [client.getDeviceId()],
expires_ts: now1 + call.STUCK_DEVICE_TIMEOUT_MS, expires_ts: now1 + call.STUCK_DEVICE_TIMEOUT_MS,
@ -396,7 +396,7 @@ describe("JitsiCall", () => {
await waitFor( await waitFor(
() => () =>
expect( expect(
room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(),
).toEqual({ ).toEqual({
devices: [], devices: [],
expires_ts: now2 + call.STUCK_DEVICE_TIMEOUT_MS, expires_ts: now2 + call.STUCK_DEVICE_TIMEOUT_MS,
@ -495,7 +495,7 @@ describe("JitsiCall", () => {
}); });
const expectDevices = (devices: IMyDevice[]) => const expectDevices = (devices: IMyDevice[]) =>
expect( expect(
room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId).getContent(), room.currentState.getStateEvents(JitsiCall.MEMBER_EVENT_TYPE, alice.userId)?.getContent(),
).toEqual({ ).toEqual({
expires_ts: expect.any(Number), expires_ts: expect.any(Number),
devices: devices.map((d) => d.device_id), devices: devices.map((d) => d.device_id),

View file

@ -37,12 +37,12 @@ function makeMatchMedia(values: any) {
} }
return function matchMedia(query: string) { return function matchMedia(query: string) {
return new FakeMediaQueryList(query); return new FakeMediaQueryList(query) as unknown as MediaQueryList;
}; };
} }
function makeGetValue(values: any) { function makeGetValue(values: any) {
return function getValue<T = any>(settingName: string, _roomId: string = null, _excludeDefault = false): T { return function getValue<T = any>(settingName: string, _roomId: string | null = null, _excludeDefault = false): T {
return values[settingName]; return values[settingName];
}; };
} }
@ -51,7 +51,7 @@ function makeGetValueAt(values: any) {
return function getValueAt( return function getValueAt(
_level: SettingLevel, _level: SettingLevel,
settingName: string, settingName: string,
_roomId: string = null, _roomId: string | null = null,
_explicit = false, _explicit = false,
_excludeDefault = false, _excludeDefault = false,
): any { ): any {