Make more of the codebase conform to strict types (#10857)

This commit is contained in:
Michael Telatynski 2023-05-16 14:25:43 +01:00 committed by GitHub
parent 7f017a84c2
commit 6a3f59cc76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 127 additions and 121 deletions

View file

@ -56,7 +56,7 @@ export default class AddThreepid {
private sessionId: string; private sessionId: string;
private submitUrl?: string; private submitUrl?: string;
private clientSecret: string; private clientSecret: string;
private bind: boolean; private bind = false;
public constructor() { public constructor() {
this.clientSecret = MatrixClientPeg.get().generateClientSecret(); this.clientSecret = MatrixClientPeg.get().generateClientSecret();

View file

@ -580,13 +580,13 @@ export default class ContentMessages {
} catch (error) { } catch (error) {
// 413: File was too big or upset the server in some way: // 413: File was too big or upset the server in some way:
// clear the media size limit so we fetch it again next time we try to upload // clear the media size limit so we fetch it again next time we try to upload
if (error?.httpStatus === 413) { if (error instanceof HTTPError && error.httpStatus === 413) {
this.mediaConfig = null; this.mediaConfig = null;
} }
if (!upload.cancelled) { if (!upload.cancelled) {
let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName }); let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName });
if (error.httpStatus === 413) { if (error instanceof HTTPError && error.httpStatus === 413) {
desc = _t("The file '%(fileName)s' exceeds this homeserver's size limit for uploads", { desc = _t("The file '%(fileName)s' exceeds this homeserver's size limit for uploads", {
fileName: upload.fileName, fileName: upload.fileName,
}); });

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react"; import React from "react";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { createClient, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from "./MatrixClientPeg"; import { MatrixClientPeg } from "./MatrixClientPeg";
@ -123,7 +123,7 @@ export default class IdentityAuthClient {
try { try {
await this.matrixClient.getIdentityAccount(token); await this.matrixClient.getIdentityAccount(token);
} catch (e) { } catch (e) {
if (e.errcode === "M_TERMS_NOT_SIGNED") { if (e instanceof MatrixError && e.errcode === "M_TERMS_NOT_SIGNED") {
logger.log("Identity server requires new terms to be agreed to"); logger.log("Identity server requires new terms to be agreed to");
await startTermsFlow([new Service(SERVICE_TYPES.IS, identityServerUrl, token)]); await startTermsFlow([new Service(SERVICE_TYPES.IS, identityServerUrl, token)]);
return; return;

View file

@ -132,7 +132,7 @@ class NotifierClass {
let msg = this.notificationMessageForEvent(ev); let msg = this.notificationMessageForEvent(ev);
if (!msg) return; if (!msg) return;
let title; let title: string | undefined;
if (!ev.sender || room.name === ev.sender.name) { if (!ev.sender || room.name === ev.sender.name) {
title = room.name; title = room.name;
// notificationMessageForEvent includes sender, but we already have the sender here // notificationMessageForEvent includes sender, but we already have the sender here
@ -153,6 +153,8 @@ class NotifierClass {
} }
} }
if (!title) return;
if (!this.isBodyEnabled()) { if (!this.isBodyEnabled()) {
msg = ""; msg = "";
} }

View file

@ -118,7 +118,7 @@ export class SlidingSyncManager {
private static readonly internalInstance = new SlidingSyncManager(); private static readonly internalInstance = new SlidingSyncManager();
public slidingSync: SlidingSync; public slidingSync: SlidingSync;
private client: MatrixClient; private client?: MatrixClient;
private configureDefer: IDeferred<void>; private configureDefer: IDeferred<void>;
@ -242,8 +242,8 @@ export class SlidingSyncManager {
} else { } else {
subscriptions.delete(roomId); subscriptions.delete(roomId);
} }
const room = this.client.getRoom(roomId); const room = this.client?.getRoom(roomId);
let shouldLazyLoad = !this.client.isRoomEncrypted(roomId); let shouldLazyLoad = !this.client?.isRoomEncrypted(roomId);
if (!room) { if (!room) {
// default to safety: request all state if we can't work it out. This can happen if you // default to safety: request all state if we can't work it out. This can happen if you
// refresh the app whilst viewing a room: we call setRoomVisible before we know anything // refresh the app whilst viewing a room: we call setRoomVisible before we know anything

View file

@ -95,8 +95,8 @@ const getUIOnlyShortcuts = (): IKeyboardShortcuts => {
export const getKeyboardShortcuts = (): IKeyboardShortcuts => { export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
const overrideBrowserShortcuts = PlatformPeg.get()?.overrideBrowserShortcuts(); const overrideBrowserShortcuts = PlatformPeg.get()?.overrideBrowserShortcuts();
return Object.keys(KEYBOARD_SHORTCUTS) return (Object.keys(KEYBOARD_SHORTCUTS) as KeyBindingAction[])
.filter((k: KeyBindingAction) => { .filter((k) => {
if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false; if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
if (MAC_ONLY_SHORTCUTS.includes(k) && !IS_MAC) return false; if (MAC_ONLY_SHORTCUTS.includes(k) && !IS_MAC) return false;
if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false; if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;

View file

@ -20,7 +20,7 @@ import FileSaver from "file-saver";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup"; import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix"; import { CrossSigningKeys, MatrixError, UIAFlow } from "matrix-js-sdk/src/matrix";
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api"; import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import classNames from "classnames"; import classNames from "classnames";
@ -103,7 +103,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
forceReset: false, forceReset: false,
}; };
private recoveryKey: IRecoveryKey; private recoveryKey: IRecoveryKey;
private backupKey: Uint8Array; private backupKey?: Uint8Array;
private recoveryKeyNode = createRef<HTMLElement>(); private recoveryKeyNode = createRef<HTMLElement>();
private passphraseField = createRef<Field>(); private passphraseField = createRef<Field>();
@ -208,7 +208,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
// no keys which would be a no-op. // no keys which would be a no-op.
logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
} catch (error) { } catch (error) {
if (!error.data || !error.data.flows) { if (!(error instanceof MatrixError) || !error.data || !error.data.flows) {
logger.log("uploadDeviceSigningKeys advertised no flows!"); logger.log("uploadDeviceSigningKeys advertised no flows!");
return; return;
} }
@ -372,7 +372,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
phase: Phase.Stored, phase: Phase.Stored,
}); });
} catch (e) { } catch (e) {
if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { if (
this.state.canUploadKeysWithPasswordOnly &&
e instanceof MatrixError &&
e.httpStatus === 401 &&
e.data.flows
) {
this.setState({ this.setState({
accountPassword: "", accountPassword: "",
accountPasswordCorrect: false, accountPasswordCorrect: false,

View file

@ -16,6 +16,7 @@ limitations under the License.
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import IdentityAuthClient from "./IdentityAuthClient"; import IdentityAuthClient from "./IdentityAuthClient";
@ -57,7 +58,7 @@ export async function getThreepidsWithBindStatus(
} }
} catch (e) { } catch (e) {
// Ignore terms errors here and assume other flows handle this // Ignore terms errors here and assume other flows handle this
if (e.errcode !== "M_TERMS_NOT_SIGNED") { if (!(e instanceof MatrixError) || e.errcode !== "M_TERMS_NOT_SIGNED") {
throw e; throw e;
} }
} }

View file

@ -56,7 +56,7 @@ class FilePanel extends React.Component<IProps, IState> {
// This is used to track if a decrypted event was a live event and should be // This is used to track if a decrypted event was a live event and should be
// added to the timeline. // added to the timeline.
private decryptingEvents = new Set<string>(); private decryptingEvents = new Set<string>();
public noRoom: boolean; public noRoom = false;
private card = createRef<HTMLDivElement>(); private card = createRef<HTMLDivElement>();
public state: IState = { public state: IState = {

View file

@ -125,7 +125,7 @@ export function GenericDropdownMenu<T>({
}: IProps<T>): JSX.Element { }: IProps<T>): JSX.Element {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu<HTMLElement>();
const selected: GenericDropdownMenuItem<T> | null = options const selected: GenericDropdownMenuItem<T> | undefined = options
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it])) .flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value)); .find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
let contextMenuOptions: JSX.Element; let contextMenuOptions: JSX.Element;

View file

@ -181,19 +181,19 @@ export default class ScrollPanel extends React.Component<IProps> {
private unmounted = false; private unmounted = false;
private scrollTimeout?: Timer; private scrollTimeout?: Timer;
// Are we currently trying to backfill? // Are we currently trying to backfill?
private isFilling: boolean; private isFilling = false;
// Is the current fill request caused by a props update? // Is the current fill request caused by a props update?
private isFillingDueToPropsUpdate = false; private isFillingDueToPropsUpdate = false;
// Did another request to check the fill state arrive while we were trying to backfill? // Did another request to check the fill state arrive while we were trying to backfill?
private fillRequestWhileRunning: boolean; private fillRequestWhileRunning = false;
// Is that next fill request scheduled because of a props update? // Is that next fill request scheduled because of a props update?
private pendingFillDueToPropsUpdate: boolean; private pendingFillDueToPropsUpdate = false;
private scrollState: IScrollState; private scrollState!: IScrollState;
private preventShrinkingState: IPreventShrinkingState | null = null; private preventShrinkingState: IPreventShrinkingState | null = null;
private unfillDebouncer: number | null = null; private unfillDebouncer: number | null = null;
private bottomGrowth: number; private bottomGrowth!: number;
private minListHeight: number; private minListHeight!: number;
private heightUpdateInProgress: boolean; private heightUpdateInProgress = false;
private divScroll: HTMLDivElement | null = null; private divScroll: HTMLDivElement | null = null;
public constructor(props: IProps) { public constructor(props: IProps) {

View file

@ -28,7 +28,7 @@ interface IProps {
} }
interface IState { interface IState {
phase: Phase; phase?: Phase;
lostKeys: boolean; lostKeys: boolean;
} }

View file

@ -19,7 +19,7 @@ import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
import { _t, _td } from "../../../languageHandler"; import { _t, _td, UserFriendlyError } from "../../../languageHandler";
import Login from "../../../Login"; import Login from "../../../Login";
import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils"; import { messageForConnectionError, messageForLoginError } from "../../../utils/ErrorUtils";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
@ -110,7 +110,7 @@ type OnPasswordLogin = {
*/ */
export default class LoginComponent extends React.PureComponent<IProps, IState> { export default class LoginComponent extends React.PureComponent<IProps, IState> {
private unmounted = false; private unmounted = false;
private loginLogic: Login; private loginLogic!: Login;
private readonly stepRendererMap: Record<string, () => ReactNode>; private readonly stepRendererMap: Record<string, () => ReactNode>;
@ -265,7 +265,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
logger.error("Problem parsing URL or unhandled error doing .well-known discovery:", e); logger.error("Problem parsing URL or unhandled error doing .well-known discovery:", e);
let message = _t("Failed to perform homeserver discovery"); let message = _t("Failed to perform homeserver discovery");
if (e.translatedMessage) { if (e instanceof UserFriendlyError && e.translatedMessage) {
message = e.translatedMessage; message = e.translatedMessage;
} }

View file

@ -38,7 +38,7 @@ interface IProps {
} }
interface IState { interface IState {
phase: Phase; phase?: Phase;
verificationRequest: VerificationRequest | null; verificationRequest: VerificationRequest | null;
backupInfo: IKeyBackupInfo | null; backupInfo: IKeyBackupInfo | null;
lostKeys: boolean; lostKeys: boolean;

View file

@ -18,6 +18,7 @@ import React, { ChangeEvent, SyntheticEvent } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth"; import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@ -164,7 +165,11 @@ export default class SoftLogout extends React.Component<IProps, IState> {
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams); credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
} catch (e) { } catch (e) {
let errorText = _t("Failed to re-authenticate due to a homeserver problem"); let errorText = _t("Failed to re-authenticate due to a homeserver problem");
if (e.errcode === "M_FORBIDDEN" && (e.httpStatus === 401 || e.httpStatus === 403)) { if (
e instanceof MatrixError &&
e.errcode === "M_FORBIDDEN" &&
(e.httpStatus === 401 || e.httpStatus === 403)
) {
errorText = _t("Incorrect password"); errorText = _t("Incorrect password");
} }

View file

@ -214,7 +214,7 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
let errorText = this.props.errorText; let errorText = this.props.errorText;
let sitePublicKey; let sitePublicKey: string | undefined;
if (!this.props.stageParams || !this.props.stageParams.public_key) { if (!this.props.stageParams || !this.props.stageParams.public_key) {
errorText = _t( errorText = _t(
"Missing captcha public key in homeserver configuration. Please report " + "Missing captcha public key in homeserver configuration. Please report " +
@ -224,7 +224,7 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
sitePublicKey = this.props.stageParams.public_key; sitePublicKey = this.props.stageParams.public_key;
} }
let errorSection; let errorSection: JSX.Element | undefined;
if (errorText) { if (errorText) {
errorSection = ( errorSection = (
<div className="error" role="alert"> <div className="error" role="alert">
@ -235,7 +235,9 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
return ( return (
<div> <div>
<CaptchaForm sitePublicKey={sitePublicKey} onCaptchaResponse={this.onCaptchaResponse} /> {sitePublicKey && (
<CaptchaForm sitePublicKey={sitePublicKey} onCaptchaResponse={this.onCaptchaResponse} />
)}
{errorSection} {errorSection}
</div> </div>
); );

View file

@ -28,7 +28,7 @@ interface IProps extends Omit<BaseProps, "matrixClient" | "children" | "onFinish
specificLabel: string; specificLabel: string;
noneLabel?: string; noneLabel?: string;
warningMessage?: string; warningMessage?: string;
onFinished(success: boolean, reason?: string, rooms?: Room[]): void; onFinished(success?: boolean, reason?: string, rooms?: Room[]): void;
spaceChildFilter?(child: Room): boolean; spaceChildFilter?(child: Room): boolean;
} }
@ -61,7 +61,7 @@ const ConfirmSpaceUserActionDialog: React.FC<IProps> = ({
return ( return (
<ConfirmUserActionDialog <ConfirmUserActionDialog
{...props} {...props}
onFinished={(success: boolean, reason?: string) => { onFinished={(success?: boolean, reason?: string) => {
onFinished(success, reason, roomsToLeave); onFinished(success, reason, roomsToLeave);
}} }}
className="mx_ConfirmSpaceUserActionDialog" className="mx_ConfirmSpaceUserActionDialog"

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ReactNode, KeyboardEvent } from "react"; import React, { ReactNode } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -30,7 +30,7 @@ interface IProps {
button?: boolean | string; button?: boolean | string;
hasCloseButton?: boolean; hasCloseButton?: boolean;
fixedWidth?: boolean; fixedWidth?: boolean;
onKeyDown?(event: KeyboardEvent): void; onKeyDown?(event: KeyboardEvent | React.KeyboardEvent): void;
onFinished(): void; onFinished(): void;
} }

View file

@ -76,7 +76,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
result = await client.relations(roomId, eventId, RelationType.Replace, EventType.RoomMessage, opts); result = await client.relations(roomId, eventId, RelationType.Replace, EventType.RoomMessage, opts);
} catch (error) { } catch (error) {
// log if the server returned an error // log if the server returned an error
if (error.errcode) { if (error instanceof MatrixError && error.errcode) {
logger.error("fetching /relations failed with error", error); logger.error("fetching /relations failed with error", error);
} }
this.setState({ error: error as MatrixError }, () => reject(error)); this.setState({ error: error as MatrixError }, () => reject(error));

View file

@ -20,7 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler"; import { _t, UserFriendlyError } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import Field from "../elements/Field"; import Field from "../elements/Field";
@ -113,7 +113,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e); const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
if (stateForError.serverErrorIsFatal) { if (stateForError.serverErrorIsFatal) {
let error = _t("Unable to validate homeserver"); let error = _t("Unable to validate homeserver");
if (e.translatedMessage) { if (e instanceof UserFriendlyError && e.translatedMessage) {
error = e.translatedMessage; error = e.translatedMessage;
} }
return { error }; return { error };

View file

@ -54,12 +54,13 @@ export const stateKeyField = (defaultValue?: string): IFieldDef => ({
}); });
const validateEventContent = withValidation<any, Error | undefined>({ const validateEventContent = withValidation<any, Error | undefined>({
deriveData({ value }) { async deriveData({ value }) {
try { try {
JSON.parse(value!); JSON.parse(value!);
} catch (e) { } catch (e) {
return e; return e as Error;
} }
return undefined;
}, },
rules: [ rules: [
{ {

View file

@ -21,7 +21,7 @@ import BaseDialog from "../BaseDialog";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import { SetupEncryptionStore, Phase } from "../../../../stores/SetupEncryptionStore"; import { SetupEncryptionStore, Phase } from "../../../../stores/SetupEncryptionStore";
function iconFromPhase(phase: Phase): string { function iconFromPhase(phase?: Phase): string {
if (phase === Phase.Done) { if (phase === Phase.Done) {
return require("../../../../../res/img/e2e/verified-deprecated.svg").default; return require("../../../../../res/img/e2e/verified-deprecated.svg").default;
} else { } else {

View file

@ -21,17 +21,7 @@ import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix"; import { IPublicRoomsChunkRoom, MatrixClient, RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { normalize } from "matrix-js-sdk/src/utils"; import { normalize } from "matrix-js-sdk/src/utils";
import React, { import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
ChangeEvent,
KeyboardEvent,
RefObject,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import sanitizeHtml from "sanitize-html"; import sanitizeHtml from "sanitize-html";
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
@ -1067,7 +1057,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
); );
} }
const onDialogKeyDown = (ev: KeyboardEvent): void => { const onDialogKeyDown = (ev: KeyboardEvent | React.KeyboardEvent): void => {
const navigationAction = getKeyBindingsManager().getNavigationAction(ev); const navigationAction = getKeyBindingsManager().getNavigationAction(ev);
switch (navigationAction) { switch (navigationAction) {
case KeyBindingAction.FilterRooms: case KeyBindingAction.FilterRooms:
@ -1139,7 +1129,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
} }
}; };
const onKeyDown = (ev: KeyboardEvent): void => { const onKeyDown = (ev: React.KeyboardEvent): void => {
const action = getKeyBindingsManager().getAccessibilityAction(ev); const action = getKeyBindingsManager().getAccessibilityAction(ev);
switch (action) { switch (action) {

View file

@ -38,7 +38,7 @@ interface IProps {
} }
interface IState { interface IState {
roomMember: RoomMember; roomMember: RoomMember | null;
isWrapped: boolean; isWrapped: boolean;
widgetDomain: string | null; widgetDomain: string | null;
} }
@ -56,7 +56,7 @@ export default class AppPermission extends React.Component<IProps, IState> {
// The second step is to find the user's profile so we can show it on the prompt // The second step is to find the user's profile so we can show it on the prompt
const room = MatrixClientPeg.get().getRoom(this.props.roomId); const room = MatrixClientPeg.get().getRoom(this.props.roomId);
let roomMember; let roomMember: RoomMember | null = null;
if (room) roomMember = room.getMember(this.props.creatorUserId); if (room) roomMember = room.getMember(this.props.creatorUserId);
// Set all this into the initial state // Set all this into the initial state

View file

@ -126,7 +126,7 @@ export default class AppTile extends React.Component<IProps, IState> {
private persistKey: string; private persistKey: string;
private sgWidget: StopGapWidget | null; private sgWidget: StopGapWidget | null;
private dispatcherRef?: string; private dispatcherRef?: string;
private unmounted: boolean; private unmounted = false;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject } from "react"; import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, RefObject, createRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { debounce } from "lodash"; import { debounce } from "lodash";
@ -118,7 +118,7 @@ interface IState {
export default class Field extends React.PureComponent<PropShapes, IState> { export default class Field extends React.PureComponent<PropShapes, IState> {
private readonly id: string; private readonly id: string;
private inputRef: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>; private readonly _inputRef = createRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>();
public static readonly defaultProps = { public static readonly defaultProps = {
element: "input", element: "input",
@ -228,6 +228,10 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
return valid; return valid;
} }
private get inputRef(): RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement> {
return this.props.inputRef ?? this._inputRef;
}
public render(): React.ReactNode { public render(): React.ReactNode {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { const {
@ -249,8 +253,6 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
...inputProps ...inputProps
} = this.props; } = this.props;
this.inputRef = inputRef || React.createRef();
// Handle displaying feedback on validity // Handle displaying feedback on validity
let fieldTooltip: JSX.Element | undefined; let fieldTooltip: JSX.Element | undefined;
if (tooltipContent || this.state.feedback) { if (tooltipContent || this.state.feedback) {

View file

@ -302,7 +302,7 @@ interface IState {
* tooltip along one edge of the target. * tooltip along one edge of the target.
*/ */
export default class InteractiveTooltip extends React.Component<IProps, IState> { export default class InteractiveTooltip extends React.Component<IProps, IState> {
private target: HTMLElement; private target?: HTMLElement;
public static defaultProps = { public static defaultProps = {
side: Direction.Top, side: Direction.Top,
@ -345,6 +345,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
private onLeftOfTarget(): boolean { private onLeftOfTarget(): boolean {
const { contentRect } = this.state; const { contentRect } = this.state;
if (!this.target) return false;
const targetRect = this.target.getBoundingClientRect(); const targetRect = this.target.getBoundingClientRect();
if (this.props.direction === Direction.Left) { if (this.props.direction === Direction.Left) {
@ -359,6 +360,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
private aboveTarget(): boolean { private aboveTarget(): boolean {
const { contentRect } = this.state; const { contentRect } = this.state;
if (!this.target) return false;
const targetRect = this.target.getBoundingClientRect(); const targetRect = this.target.getBoundingClientRect();
if (this.props.direction === Direction.Top) { if (this.props.direction === Direction.Top) {
@ -378,7 +380,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
private onMouseMove = (ev: MouseEvent): void => { private onMouseMove = (ev: MouseEvent): void => {
const { clientX: x, clientY: y } = ev; const { clientX: x, clientY: y } = ev;
const { contentRect } = this.state; const { contentRect } = this.state;
if (!contentRect) return; if (!contentRect || !this.target) return;
const targetRect = this.target.getBoundingClientRect(); const targetRect = this.target.getBoundingClientRect();
let direction: Direction; let direction: Direction;
@ -423,6 +425,8 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
return null; return null;
} }
if (!this.target) return null;
const targetRect = this.target.getBoundingClientRect(); const targetRect = this.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page // The window X and Y offsets are to adjust position when zoomed in to page

View file

@ -107,7 +107,7 @@ interface IProps {
initialCaret?: DocumentOffset; initialCaret?: DocumentOffset;
disabled?: boolean; disabled?: boolean;
onChange?(selection: Caret, inputType?: string, diff?: IDiff): void; onChange?(selection?: Caret, inputType?: string, diff?: IDiff): void;
onPaste?(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean; onPaste?(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean;
} }
@ -130,7 +130,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private isIMEComposing = false; private isIMEComposing = false;
private hasTextSelected = false; private hasTextSelected = false;
private _isCaretAtEnd: boolean; private _isCaretAtEnd = false;
private lastCaret: DocumentOffset; private lastCaret: DocumentOffset;
private lastSelection: ReturnType<typeof cloneSelection> | null = null; private lastSelection: ReturnType<typeof cloneSelection> | null = null;
@ -230,7 +230,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
} }
private updateEditorState = (selection: Caret, inputType?: string, diff?: IDiff): void => { private updateEditorState = (selection?: Caret, inputType?: string, diff?: IDiff): void => {
if (!this.editorRef.current) return; if (!this.editorRef.current) return;
renderModel(this.editorRef.current, this.props.model); renderModel(this.editorRef.current, this.props.model);
if (selection) { if (selection) {

View file

@ -39,6 +39,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds"; import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
import { EmojiButton } from "./EmojiButton"; import { EmojiButton } from "./EmojiButton";
import { filterBoolean } from "../../../utils/arrays";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
@ -118,8 +119,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
]; ];
} }
mainButtons = mainButtons.filter((x: ReactElement) => x); mainButtons = filterBoolean(mainButtons);
moreButtons = moreButtons.filter((x: ReactElement) => x); moreButtons = filterBoolean(moreButtons);
const moreOptionsClasses = classNames({ const moreOptionsClasses = classNames({
mx_MessageComposer_button: true, mx_MessageComposer_button: true,

View file

@ -313,7 +313,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
private onResize = ( private onResize = (
e: MouseEvent | TouchEvent, e: MouseEvent | TouchEvent,
travelDirection: Direction, travelDirection: Direction,
refToElement: HTMLDivElement, refToElement: HTMLElement,
delta: ResizeDelta, delta: ResizeDelta,
): void => { ): void => {
const newHeight = this.heightAtStart + delta.height; const newHeight = this.heightAtStart + delta.height;
@ -329,7 +329,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
private onResizeStop = ( private onResizeStop = (
e: MouseEvent | TouchEvent, e: MouseEvent | TouchEvent,
travelDirection: Direction, travelDirection: Direction,
refToElement: HTMLDivElement, refToElement: HTMLElement,
delta: ResizeDelta, delta: ResizeDelta,
): void => { ): void => {
const newHeight = this.heightAtStart + delta.height; const newHeight = this.heightAtStart + delta.height;

View file

@ -27,7 +27,7 @@ import SearchWarning, { WarningKind } from "../elements/SearchWarning";
interface IProps { interface IProps {
onCancelClick: () => void; onCancelClick: () => void;
onSearch: (query: string, scope: string) => void; onSearch: (query: string, scope: SearchScope) => void;
searchInProgress?: boolean; searchInProgress?: boolean;
isRoomEncrypted?: boolean; isRoomEncrypted?: boolean;
} }

View file

@ -686,7 +686,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
return false; return false;
}; };
private onChange = (selection: Caret, inputType?: string, diff?: IDiff): void => { private onChange = (selection?: Caret, inputType?: string, diff?: IDiff): void => {
// We call this in here rather than onKeyDown as that would trip it on global shortcuts e.g. Ctrl-k also // We call this in here rather than onKeyDown as that would trip it on global shortcuts e.g. Ctrl-k also
if (!!diff) { if (!!diff) {
this.prepareToEncrypt?.(); this.prepareToEncrypt?.();

View file

@ -31,7 +31,7 @@ interface IKeyboardShortcutRowProps {
} }
// Filter out the labs section if labs aren't enabled. // Filter out the labs section if labs aren't enabled.
const visibleCategories = Object.entries(CATEGORIES).filter( const visibleCategories = (Object.entries(CATEGORIES) as [CategoryName, ICategory][]).filter(
([categoryName]) => categoryName !== CategoryName.LABS || SdkConfig.get("show_labs_settings"), ([categoryName]) => categoryName !== CategoryName.LABS || SdkConfig.get("show_labs_settings"),
); );
@ -73,7 +73,7 @@ const KeyboardUserSettingsTab: React.FC = () => {
return ( return (
<div className="mx_SettingsTab mx_KeyboardUserSettingsTab"> <div className="mx_SettingsTab mx_KeyboardUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Keyboard")}</div> <div className="mx_SettingsTab_heading">{_t("Keyboard")}</div>
{visibleCategories.map(([categoryName, category]: [CategoryName, ICategory]) => { {visibleCategories.map(([categoryName, category]) => {
return <KeyboardShortcutSection key={categoryName} categoryName={categoryName} category={category} />; return <KeyboardShortcutSection key={categoryName} categoryName={categoryName} category={category} />;
})} })}
</div> </div>

View file

@ -147,7 +147,7 @@ function getTextAndOffsetToNode(
let foundNode = false; let foundNode = false;
let text = ""; let text = "";
function enterNodeCallback(node: HTMLElement): boolean { function enterNodeCallback(node: Node): boolean {
if (!foundNode) { if (!foundNode) {
if (node === selectionNode) { if (node === selectionNode) {
foundNode = true; foundNode = true;
@ -157,7 +157,7 @@ function getTextAndOffsetToNode(
// but for example while pasting in some browsers, they are still // but for example while pasting in some browsers, they are still
// converted to BRs, so also take these into account when they // converted to BRs, so also take these into account when they
// are not the last element in the DIV. // are not the last element in the DIV.
if (node.tagName === "BR" && node.nextSibling) { if (node instanceof HTMLElement && node.tagName === "BR" && node.nextSibling) {
if (!foundNode) { if (!foundNode) {
offsetToNode += 1; offsetToNode += 1;
} }
@ -173,12 +173,16 @@ function getTextAndOffsetToNode(
return true; return true;
} }
function leaveNodeCallback(node: HTMLElement): void { function leaveNodeCallback(node: Node): void {
// if this is not the last DIV (which are only used as line containers atm) // if this is not the last DIV (which are only used as line containers atm)
// we don't just check if there is a nextSibling because sometimes the caret ends up // we don't just check if there is a nextSibling because sometimes the caret ends up
// after the last DIV and it creates a newline if you type then, // after the last DIV and it creates a newline if you type then,
// whereas you just want it to be appended to the current line // whereas you just want it to be appended to the current line
if (node.tagName === "DIV" && (<HTMLElement>node.nextSibling)?.tagName === "DIV") { if (
node instanceof HTMLElement &&
node.tagName === "DIV" &&
(<HTMLElement>node.nextSibling)?.tagName === "DIV"
) {
text += "\n"; text += "\n";
if (!foundNode) { if (!foundNode) {
offsetToNode += 1; offsetToNode += 1;

View file

@ -21,7 +21,7 @@ import { Caret } from "./caret";
export interface IHistory { export interface IHistory {
parts: SerializedPart[]; parts: SerializedPart[];
caret: Caret; caret?: Caret;
} }
export const MAX_STEP_LENGTH = 10; export const MAX_STEP_LENGTH = 10;
@ -31,7 +31,7 @@ export default class HistoryManager {
private newlyTypedCharCount = 0; private newlyTypedCharCount = 0;
private currentIndex = -1; private currentIndex = -1;
private changedSinceLastPush = false; private changedSinceLastPush = false;
private lastCaret: Caret | null = null; private lastCaret?: Caret;
private nonWordBoundarySinceLastPush = false; private nonWordBoundarySinceLastPush = false;
private addedSinceLastPush = false; private addedSinceLastPush = false;
private removedSinceLastPush = false; private removedSinceLastPush = false;
@ -41,7 +41,7 @@ export default class HistoryManager {
this.newlyTypedCharCount = 0; this.newlyTypedCharCount = 0;
this.currentIndex = -1; this.currentIndex = -1;
this.changedSinceLastPush = false; this.changedSinceLastPush = false;
this.lastCaret = null; this.lastCaret = undefined;
this.nonWordBoundarySinceLastPush = false; this.nonWordBoundarySinceLastPush = false;
this.addedSinceLastPush = false; this.addedSinceLastPush = false;
this.removedSinceLastPush = false; this.removedSinceLastPush = false;
@ -85,7 +85,7 @@ export default class HistoryManager {
} }
} }
private pushState(model: EditorModel, caret: Caret): void { private pushState(model: EditorModel, caret?: Caret): void {
// remove all steps after current step // remove all steps after current step
while (this.currentIndex < this.stack.length - 1) { while (this.currentIndex < this.stack.length - 1) {
this.stack.pop(); this.stack.pop();
@ -93,7 +93,7 @@ export default class HistoryManager {
const parts = model.serializeParts(); const parts = model.serializeParts();
this.stack.push({ parts, caret }); this.stack.push({ parts, caret });
this.currentIndex = this.stack.length - 1; this.currentIndex = this.stack.length - 1;
this.lastCaret = null; this.lastCaret = undefined;
this.changedSinceLastPush = false; this.changedSinceLastPush = false;
this.newlyTypedCharCount = 0; this.newlyTypedCharCount = 0;
this.nonWordBoundarySinceLastPush = false; this.nonWordBoundarySinceLastPush = false;
@ -102,7 +102,7 @@ export default class HistoryManager {
} }
// needs to persist parts and caret position // needs to persist parts and caret position
public tryPush(model: EditorModel, caret: Caret, inputType?: string, diff?: IDiff): boolean { public tryPush(model: EditorModel, caret?: Caret, inputType?: string, diff?: IDiff): boolean {
// ignore state restoration echos. // ignore state restoration echos.
// these respect the inputType values of the input event, // these respect the inputType values of the input event,
// but are actually passed in from MessageEditor calling model.reset() // but are actually passed in from MessageEditor calling model.reset()

View file

@ -49,7 +49,7 @@ export const usePublicRoomDirectory = (): {
publicRooms: IPublicRoomsChunkRoom[]; publicRooms: IPublicRoomsChunkRoom[];
protocols: Protocols | null; protocols: Protocols | null;
config?: IPublicRoomDirectoryConfig | null; config?: IPublicRoomDirectoryConfig | null;
setConfig(config: IPublicRoomDirectoryConfig): void; setConfig(config: IPublicRoomDirectoryConfig | null): void;
search(opts: IPublicRoomsOpts): Promise<boolean>; search(opts: IPublicRoomsOpts): Promise<boolean>;
} => { } => {
const [publicRooms, setPublicRooms] = useState<IPublicRoomsChunkRoom[]>([]); const [publicRooms, setPublicRooms] = useState<IPublicRoomsChunkRoom[]>([]);

View file

@ -28,6 +28,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
import { HTTPError } from "matrix-js-sdk/src/http-api";
import PlatformPeg from "../PlatformPeg"; import PlatformPeg from "../PlatformPeg";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
@ -471,7 +472,7 @@ export default class EventIndex extends EventEmitter {
checkpoint.direction, checkpoint.direction,
); );
} catch (e) { } catch (e) {
if (e.httpStatus === 403) { if (e instanceof HTTPError && e.httpStatus === 403) {
logger.log( logger.log(
"EventIndex: Removing checkpoint as we don't have ", "EventIndex: Removing checkpoint as we don't have ",
"permissions to fetch messages from this room.", "permissions to fetch messages from this room.",
@ -564,7 +565,7 @@ export default class EventIndex extends EventEmitter {
return object; return object;
}); });
let newCheckpoint; let newCheckpoint: ICrawlerCheckpoint | null = null;
// The token can be null for some reason. Don't create a checkpoint // The token can be null for some reason. Don't create a checkpoint
// in that case since adding it to the db will fail. // in that case since adding it to the db will fail.

View file

@ -45,7 +45,7 @@ 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 | null = null; public verificationRequest: VerificationRequest | null = null;
public backupInfo: IKeyBackupInfo | null = null; public backupInfo: IKeyBackupInfo | null = null;
// ID of the key that the secrets we want are encrypted with // ID of the key that the secrets we want are encrypted with

View file

@ -145,8 +145,8 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string {
return ""; return "";
}) })
.join(", "); .join(", ");
const props = Object.keys(face).filter((prop: (typeof allowedFontFaceProps)[number]) => const props = Object.keys(face).filter((prop) =>
allowedFontFaceProps.includes(prop), allowedFontFaceProps.includes(prop as (typeof allowedFontFaceProps)[number]),
) as Array<(typeof allowedFontFaceProps)[number]>; ) as Array<(typeof allowedFontFaceProps)[number]>;
const body = props const body = props
.map((prop) => { .map((prop) => {

View file

@ -16,6 +16,7 @@ limitations under the License.
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types"; import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { HTTPError } from "matrix-js-sdk/src/http-api";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
@ -39,7 +40,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise<bool
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
} catch (e) { } catch (e) {
logger.error(e); logger.error(e);
if (e.cors === "rejected" || e.httpStatus === 404) { if (e.cors === "rejected" || (e instanceof HTTPError && e.httpStatus === 404)) {
terms = null; terms = null;
} else { } else {
throw e; throw e;

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IDeferred, defer } from "matrix-js-sdk/src/utils";
/** /**
A countdown timer, exposing a promise api. A countdown timer, exposing a promise api.
A timer starts in a non-started state, A timer starts in a non-started state,
@ -28,9 +30,7 @@ a new one through `clone()` or `cloneIfRun()`.
export default class Timer { export default class Timer {
private timerHandle?: number; private timerHandle?: number;
private startTs?: number; private startTs?: number;
private promise: Promise<void>; private deferred!: IDeferred<void>;
private resolve: () => void;
private reject: (err: Error) => void;
public constructor(private timeout: number) { public constructor(private timeout: number) {
this.setNotStarted(); this.setNotStarted();
@ -39,10 +39,8 @@ export default class Timer {
private setNotStarted(): void { private setNotStarted(): void {
this.timerHandle = undefined; this.timerHandle = undefined;
this.startTs = undefined; this.startTs = undefined;
this.promise = new Promise<void>((resolve, reject) => { this.deferred = defer();
this.resolve = resolve; this.deferred.promise = this.deferred.promise.finally(() => {
this.reject = reject;
}).finally(() => {
this.timerHandle = undefined; this.timerHandle = undefined;
}); });
} }
@ -51,7 +49,7 @@ export default class Timer {
const now = Date.now(); const now = Date.now();
const elapsed = now - this.startTs!; const elapsed = now - this.startTs!;
if (elapsed >= this.timeout) { if (elapsed >= this.timeout) {
this.resolve(); this.deferred.resolve();
this.setNotStarted(); this.setNotStarted();
} else { } else {
const delta = this.timeout - elapsed; const delta = this.timeout - elapsed;
@ -108,7 +106,7 @@ export default class Timer {
public abort(): Timer { public abort(): Timer {
if (this.isRunning()) { if (this.isRunning()) {
clearTimeout(this.timerHandle); clearTimeout(this.timerHandle);
this.reject(new Error("Timer was aborted.")); this.deferred.reject(new Error("Timer was aborted."));
this.setNotStarted(); this.setNotStarted();
} }
return this; return this;
@ -120,7 +118,7 @@ export default class Timer {
*@return {Promise} *@return {Promise}
*/ */
public finished(): Promise<void> { public finished(): Promise<void> {
return this.promise; return this.deferred.promise;
} }
public isRunning(): boolean { public isRunning(): boolean {

View file

@ -65,7 +65,7 @@ export const useOwnLiveBeacons = (liveBeaconIds: BeaconIdentifier[]): LiveBeacon
// select the beacon with latest expiry to display expiry time // select the beacon with latest expiry to display expiry time
const beacon = liveBeaconIds const beacon = liveBeaconIds
.map((beaconId) => OwnBeaconStore.instance.getBeaconById(beaconId)) .map((beaconId) => OwnBeaconStore.instance.getBeaconById(beaconId)!)
.sort(sortBeaconsByLatestExpiry) .sort(sortBeaconsByLatestExpiry)
.shift(); .shift();

View file

@ -82,12 +82,12 @@ export function createPartCreator(completions: PillPart[] = []) {
} }
export function createRenderer() { export function createRenderer() {
const render = (c: Caret) => { const render = (c?: Caret) => {
render.caret = c; render.caret = c;
render.count += 1; render.count += 1;
}; };
render.count = 0; render.count = 0;
render.caret = null as unknown as Caret; render.caret = null as unknown as Caret | undefined;
return render; return render;
} }

View file

@ -15,18 +15,7 @@ limitations under the License.
*/ */
import EditorModel from "../../src/editor/model"; import EditorModel from "../../src/editor/model";
import { createPartCreator } from "./mock"; import { createPartCreator, createRenderer } from "./mock";
import { Caret } from "../../src/editor/caret";
function createRenderer() {
const render = (c: Caret) => {
render.caret = c;
render.count += 1;
};
render.count = 0;
render.caret = null;
return render;
}
describe("editor/position", function () { describe("editor/position", function () {
it("move first position backward in empty model", function () { it("move first position backward in empty model", function () {

View file

@ -48,8 +48,8 @@ describe("SetupEncryptionStore", () => {
client.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => { client.bootstrapCrossSigning.mockImplementation(async (opts: IBootstrapCrossSigningOpts) => {
await opts?.authUploadDeviceSigningKeys?.(makeRequest); await opts?.authUploadDeviceSigningKeys?.(makeRequest);
}); });
mocked(accessSecretStorage).mockImplementation(async (func: () => Promise<void>) => { mocked(accessSecretStorage).mockImplementation(async (func?: () => Promise<void>) => {
await func(); await func!();
}); });
await setupEncryptionStore.resetConfirm(); await setupEncryptionStore.resetConfirm();