Conform more code to strict null checking (#10169)

* Conform more code to strict null checking

* delint

* Iterate

* delint

* Fix bad test
This commit is contained in:
Michael Telatynski 2023-02-16 09:38:44 +00:00 committed by GitHub
parent 5123d7e641
commit e8b92b308b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 283 additions and 287 deletions

View file

@ -68,7 +68,7 @@ interface IState {
export default class LeftPanel extends React.Component<IProps, IState> { export default class LeftPanel extends React.Component<IProps, IState> {
private listContainerRef = createRef<HTMLDivElement>(); private listContainerRef = createRef<HTMLDivElement>();
private roomListRef = createRef<RoomList>(); private roomListRef = createRef<RoomList>();
private focusedElement: Element = null; private focusedElement: Element | null = null;
private isDoingStickyHeaders = false; private isDoingStickyHeaders = false;
public constructor(props: IProps) { public constructor(props: IProps) {

View file

@ -41,7 +41,7 @@ const KEY_FILE_MAX_SIZE = 128;
const VALIDATION_THROTTLE_MS = 200; const VALIDATION_THROTTLE_MS = 200;
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
keyInfo: ISecretStorageKeyInfo; keyInfo?: ISecretStorageKeyInfo;
checkPrivateKey: (k: { passphrase?: string; recoveryKey?: string }) => boolean; checkPrivateKey: (k: { passphrase?: string; recoveryKey?: string }) => boolean;
} }
@ -137,7 +137,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
}; };
private onRecoveryKeyFileChange = async (ev: ChangeEvent<HTMLInputElement>): Promise<void> => { private onRecoveryKeyFileChange = async (ev: ChangeEvent<HTMLInputElement>): Promise<void> => {
if (ev.target.files.length === 0) return; if (!ev.target.files?.length) return;
const f = ev.target.files[0]; const f = ev.target.files[0];
@ -170,7 +170,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
}; };
private onRecoveryKeyFileUploadClick = (): void => { private onRecoveryKeyFileUploadClick = (): void => {
this.fileUpload.current.click(); this.fileUpload.current?.click();
}; };
private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent): Promise<void> => { private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement> | React.MouseEvent): Promise<void> => {
@ -269,15 +269,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
} }
public render(): React.ReactNode { public render(): React.ReactNode {
const hasPassphrase = const hasPassphrase = this.props.keyInfo?.passphrase?.salt && this.props.keyInfo?.passphrase?.iterations;
this.props.keyInfo &&
this.props.keyInfo.passphrase &&
this.props.keyInfo.passphrase.salt &&
this.props.keyInfo.passphrase.iterations;
const resetButton = ( const resetButton = (
<div className="mx_AccessSecretStorageDialog_reset"> <div className="mx_AccessSecretStorageDialog_reset">
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, { {_t("Forgotten or lost all recovery methods? <a>Reset all</a>", undefined, {
a: (sub) => ( a: (sub) => (
<AccessibleButton <AccessibleButton
kind="link_inline" kind="link_inline"
@ -405,7 +401,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
value={this.state.recoveryKey} value={this.state.recoveryKey}
onChange={this.onRecoveryKeyChange} onChange={this.onRecoveryKeyChange}
autoFocus={true} autoFocus={true}
forceValidity={this.state.recoveryKeyCorrect} forceValidity={this.state.recoveryKeyCorrect ?? undefined}
autoComplete="off" autoComplete="off"
/> />
</div> </div>

View file

@ -112,8 +112,8 @@ export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes<H
type PropShapes = IInputProps | ISelectProps | ITextareaProps | INativeOnChangeInputProps; type PropShapes = IInputProps | ISelectProps | ITextareaProps | INativeOnChangeInputProps;
interface IState { interface IState {
valid: boolean; valid?: boolean;
feedback: React.ReactNode; feedback?: React.ReactNode;
feedbackVisible: boolean; feedbackVisible: boolean;
focused: boolean; focused: boolean;
} }
@ -148,8 +148,6 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
public constructor(props: PropShapes) { public constructor(props: PropShapes) {
super(props); super(props);
this.state = { this.state = {
valid: undefined,
feedback: undefined,
feedbackVisible: false, feedbackVisible: false,
focused: false, focused: false,
}; };
@ -199,7 +197,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
this.props.onBlur?.(ev); this.props.onBlur?.(ev);
}; };
public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise<boolean> { public async validate({ focused, allowEmpty = true }: IValidateOpts): Promise<boolean | undefined> {
if (!this.props.onValidate) { if (!this.props.onValidate) {
return; return;
} }
@ -268,11 +266,11 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
const fieldInput = React.createElement(this.props.element, inputProps_, children); const fieldInput = React.createElement(this.props.element, inputProps_, children);
let prefixContainer = null; let prefixContainer: JSX.Element | undefined;
if (prefixComponent) { if (prefixComponent) {
prefixContainer = <span className="mx_Field_prefix">{prefixComponent}</span>; prefixContainer = <span className="mx_Field_prefix">{prefixComponent}</span>;
} }
let postfixContainer = null; let postfixContainer: JSX.Element | undefined;
if (postfixComponent) { if (postfixComponent) {
postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>; postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>;
} }

View file

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { ReactNode } from "react";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
@ -41,12 +41,12 @@ export default class UrlPreviewSettings extends React.Component<IProps> {
dis.fire(Action.ViewUserSettings); dis.fire(Action.ViewUserSettings);
}; };
public render(): React.ReactNode { public render(): ReactNode {
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
let previewsForAccount = null; let previewsForAccount: ReactNode | undefined;
let previewsForRoom = null; let previewsForRoom: ReactNode | undefined;
if (!isEncrypted) { if (!isEncrypted) {
// Only show account setting state and room state setting state in non-e2ee rooms where they apply // Only show account setting state and room state setting state in non-e2ee rooms where they apply

View file

@ -28,10 +28,10 @@ import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps { interface IProps {
userId?: string; userId?: string;
displayName: string; displayName?: string;
avatarUrl: string; avatarUrl?: string;
messagePreviewText: string; messagePreviewText: string;
onLayoutChanged?: (layout: Layout) => void; onLayoutChanged: (layout: Layout) => void;
} }
interface IState { interface IState {

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 from "react"; import React, { ReactNode } from "react";
import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules"; import { IAnnotatedPushRule, IPusher, PushRuleAction, PushRuleKind, RuleId } from "matrix-js-sdk/src/@types/PushRules";
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -152,7 +152,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
// the master rule is *enabled* it means all other rules are *disabled* (or // the master rule is *enabled* it means all other rules are *disabled* (or
// inhibited). Conversely, when the master rule is *disabled* then all other rules // inhibited). Conversely, when the master rule is *disabled* then all other rules
// are *enabled* (or operate fine). // are *enabled* (or operate fine).
return this.state.masterPushRule?.enabled; return !!this.state.masterPushRule?.enabled;
} }
public componentDidMount(): void { public componentDidMount(): void {
@ -622,12 +622,12 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
); );
} }
private renderCategory(category: RuleClass): JSX.Element { private renderCategory(category: RuleClass): ReactNode {
if (category !== RuleClass.VectorOther && this.isInhibited) { if (category !== RuleClass.VectorOther && this.isInhibited) {
return null; // nothing to show for the section return null; // nothing to show for the section
} }
let clearNotifsButton: JSX.Element; let clearNotifsButton: JSX.Element | undefined;
if ( if (
category === RuleClass.VectorOther && category === RuleClass.VectorOther &&
MatrixClientPeg.get() MatrixClientPeg.get()
@ -660,7 +660,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
return null; return null;
} }
let keywordComposer: JSX.Element; let keywordComposer: JSX.Element | undefined;
if (category === RuleClass.VectorMentions) { if (category === RuleClass.VectorMentions) {
keywordComposer = ( keywordComposer = (
<TagComposer <TagComposer
@ -691,7 +691,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
/> />
); );
const fieldsetRows = this.state.vectorPushRules[category].map((r) => ( const fieldsetRows = this.state.vectorPushRules[category]?.map((r) => (
<fieldset <fieldset
key={category + r.ruleId} key={category + r.ruleId}
data-testid={category + r.ruleId} data-testid={category + r.ruleId}
@ -736,10 +736,10 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
); );
} }
private renderTargets(): JSX.Element { private renderTargets(): ReactNode {
if (this.isInhibited) return null; // no targets if there's no notifications if (this.isInhibited) return null; // no targets if there's no notifications
const rows = this.state.pushers.map((p) => ( const rows = this.state.pushers?.map((p) => (
<tr key={p.kind + p.pushkey}> <tr key={p.kind + p.pushkey}>
<td>{p.app_display_name}</td> <td>{p.app_display_name}</td>
<td>{p.device_display_name}</td> <td>{p.device_display_name}</td>

View file

@ -34,11 +34,11 @@ import PosthogTrackers from "../../../PosthogTrackers";
interface IState { interface IState {
userId?: string; userId?: string;
originalDisplayName?: string; originalDisplayName: string;
displayName?: string; displayName: string;
originalAvatarUrl?: string; originalAvatarUrl: string | null;
avatarUrl?: string | ArrayBuffer; avatarUrl?: string | ArrayBuffer;
avatarFile?: File; avatarFile?: File | null;
enableProfileSave?: boolean; enableProfileSave?: boolean;
} }
@ -52,25 +52,25 @@ export default class ProfileSettings extends React.Component<{}, IState> {
let avatarUrl = OwnProfileStore.instance.avatarMxc; let avatarUrl = OwnProfileStore.instance.avatarMxc;
if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96); if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
this.state = { this.state = {
userId: client.getUserId(), userId: client.getUserId()!,
originalDisplayName: OwnProfileStore.instance.displayName, originalDisplayName: OwnProfileStore.instance.displayName ?? "",
displayName: OwnProfileStore.instance.displayName, displayName: OwnProfileStore.instance.displayName ?? "",
originalAvatarUrl: avatarUrl, originalAvatarUrl: avatarUrl,
avatarUrl: avatarUrl, avatarUrl: avatarUrl ?? undefined,
avatarFile: null, avatarFile: null,
enableProfileSave: false, enableProfileSave: false,
}; };
} }
private uploadAvatar = (): void => { private uploadAvatar = (): void => {
this.avatarUpload.current.click(); this.avatarUpload.current?.click();
}; };
private removeAvatar = (): void => { private removeAvatar = (): void => {
// clear file upload field so same file can be selected // clear file upload field so same file can be selected
this.avatarUpload.current.value = ""; this.avatarUpload.current.value = "";
this.setState({ this.setState({
avatarUrl: null, avatarUrl: undefined,
avatarFile: null, avatarFile: null,
enableProfileSave: true, enableProfileSave: true,
}); });
@ -84,7 +84,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
this.setState({ this.setState({
enableProfileSave: false, enableProfileSave: false,
displayName: this.state.originalDisplayName, displayName: this.state.originalDisplayName,
avatarUrl: this.state.originalAvatarUrl, avatarUrl: this.state.originalAvatarUrl ?? undefined,
avatarFile: null, avatarFile: null,
}); });
}; };
@ -97,7 +97,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
this.setState({ enableProfileSave: false }); this.setState({ enableProfileSave: false });
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const newState: IState = {}; const newState: Partial<IState> = {};
const displayName = this.state.displayName.trim(); const displayName = this.state.displayName.trim();
try { try {
@ -114,7 +114,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
); );
const { content_uri: uri } = await client.uploadContent(this.state.avatarFile); const { content_uri: uri } = await client.uploadContent(this.state.avatarFile);
await client.setAvatarUrl(uri); await client.setAvatarUrl(uri);
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96); newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96) ?? undefined;
newState.originalAvatarUrl = newState.avatarUrl; newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null; newState.avatarFile = null;
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) { } else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
@ -128,7 +128,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
}); });
} }
this.setState(newState); this.setState<any>(newState);
}; };
private onDisplayNameChanged = (e: React.ChangeEvent<HTMLInputElement>): void => { private onDisplayNameChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
@ -141,7 +141,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
private onAvatarChanged = (e: React.ChangeEvent<HTMLInputElement>): void => { private onAvatarChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
if (!e.target.files || !e.target.files.length) { if (!e.target.files || !e.target.files.length) {
this.setState({ this.setState({
avatarUrl: this.state.originalAvatarUrl, avatarUrl: this.state.originalAvatarUrl ?? undefined,
avatarFile: null, avatarFile: null,
enableProfileSave: false, enableProfileSave: false,
}); });
@ -152,7 +152,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (ev) => { reader.onload = (ev) => {
this.setState({ this.setState({
avatarUrl: ev.target.result, avatarUrl: ev.target?.result,
avatarFile: file, avatarFile: file,
enableProfileSave: true, enableProfileSave: true,
}); });
@ -162,7 +162,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
public render(): React.ReactNode { public render(): React.ReactNode {
const hostingSignupLink = getHostingLink("user-settings"); const hostingSignupLink = getHostingLink("user-settings");
let hostingSignup = null; let hostingSignup: JSX.Element | undefined;
if (hostingSignupLink) { if (hostingSignupLink) {
hostingSignup = ( hostingSignup = (
<span> <span>

View file

@ -27,7 +27,7 @@ import ToggleSwitch from "../elements/ToggleSwitch";
interface IProps {} interface IProps {}
interface IState { interface IState {
currentManager: IntegrationManagerInstance; currentManager: IntegrationManagerInstance | null;
provisioningEnabled: boolean; provisioningEnabled: boolean;
} }

View file

@ -162,7 +162,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
this.setState({ customThemeUrl: e.target.value }); this.setState({ customThemeUrl: e.target.value });
}; };
private renderHighContrastCheckbox(): React.ReactElement<HTMLDivElement> { private renderHighContrastCheckbox(): React.ReactElement<HTMLDivElement> | undefined {
if ( if (
!this.state.useSystemTheme && !this.state.useSystemTheme &&
(findHighContrastTheme(this.state.theme) || isHighContrastTheme(this.state.theme)) (findHighContrastTheme(this.state.theme) || isHighContrastTheme(this.state.theme))
@ -181,7 +181,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
} }
private highContrastThemeChanged(checked: boolean): void { private highContrastThemeChanged(checked: boolean): void {
let newTheme: string; let newTheme: string | undefined;
if (checked) { if (checked) {
newTheme = findHighContrastTheme(this.state.theme); newTheme = findHighContrastTheme(this.state.theme);
} else { } else {
@ -194,7 +194,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
public render(): React.ReactElement<HTMLDivElement> { public render(): React.ReactElement<HTMLDivElement> {
const themeWatcher = new ThemeWatcher(); const themeWatcher = new ThemeWatcher();
let systemThemeSection: JSX.Element; let systemThemeSection: JSX.Element | undefined;
if (themeWatcher.isSystemThemeSupported()) { if (themeWatcher.isSystemThemeSupported()) {
systemThemeSection = ( systemThemeSection = (
<div> <div>
@ -208,9 +208,9 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
); );
} }
let customThemeForm: JSX.Element; let customThemeForm: JSX.Element | undefined;
if (SettingsStore.getValue("feature_custom_themes")) { if (SettingsStore.getValue("feature_custom_themes")) {
let messageElement = null; let messageElement: JSX.Element | undefined;
if (this.state.customThemeMessage.text) { if (this.state.customThemeMessage.text) {
if (this.state.customThemeMessage.isError) { if (this.state.customThemeMessage.isError) {
messageElement = <div className="text-error">{this.state.customThemeMessage.text}</div>; messageElement = <div className="text-error">{this.state.customThemeMessage.text}</div>;
@ -268,7 +268,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
); );
} }
public apparentSelectedThemeId(): string { public apparentSelectedThemeId(): string | undefined {
if (this.state.useSystemTheme) { if (this.state.useSystemTheme) {
return undefined; return undefined;
} }

View file

@ -27,7 +27,7 @@ import AccessibleButton from "../../../components/views/elements/AccessibleButto
import { CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload"; import { CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload";
function installUpdate(): void { function installUpdate(): void {
PlatformPeg.get().installUpdate(); PlatformPeg.get()?.installUpdate();
} }
function getStatusText(status: UpdateCheckStatus, errorDetail?: string): ReactNode { function getStatusText(status: UpdateCheckStatus, errorDetail?: string): ReactNode {
@ -58,11 +58,11 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string): ReactNo
const doneStatuses = [UpdateCheckStatus.Ready, UpdateCheckStatus.Error, UpdateCheckStatus.NotAvailable]; const doneStatuses = [UpdateCheckStatus.Ready, UpdateCheckStatus.Error, UpdateCheckStatus.NotAvailable];
const UpdateCheckButton: React.FC = () => { const UpdateCheckButton: React.FC = () => {
const [state, setState] = useState<CheckUpdatesPayload>(null); const [state, setState] = useState<CheckUpdatesPayload | null>(null);
const onCheckForUpdateClick = (): void => { const onCheckForUpdateClick = (): void => {
setState(null); setState(null);
PlatformPeg.get().startUpdateCheck(); PlatformPeg.get()?.startUpdateCheck();
}; };
useDispatcher(dis, ({ action, ...params }) => { useDispatcher(dis, ({ action, ...params }) => {
@ -71,9 +71,9 @@ const UpdateCheckButton: React.FC = () => {
} }
}); });
const busy = state && !doneStatuses.includes(state.status); const busy = !!state && !doneStatuses.includes(state.status);
let suffix; let suffix: JSX.Element | undefined;
if (state) { if (state) {
suffix = ( suffix = (
<span className="mx_UpdateCheckButton_summary"> <span className="mx_UpdateCheckButton_summary">

View file

@ -31,14 +31,14 @@ import { ExtendedDevice } from "./types";
interface Props { interface Props {
device: ExtendedDevice; device: ExtendedDevice;
pusher?: IPusher | undefined; pusher?: IPusher;
localNotificationSettings?: LocalNotificationSettings | undefined; localNotificationSettings?: LocalNotificationSettings;
isSigningOut: boolean; isSigningOut: boolean;
onVerifyDevice?: () => void; onVerifyDevice?: () => void;
onSignOutDevice: () => void; onSignOutDevice: () => void;
saveDeviceName: (deviceName: string) => Promise<void>; saveDeviceName: (deviceName: string) => Promise<void>;
setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void> | undefined; setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void>;
supportsMSC3881?: boolean | undefined; supportsMSC3881?: boolean;
className?: string; className?: string;
isCurrentDevice?: boolean; isCurrentDevice?: boolean;
} }

View file

@ -23,7 +23,7 @@ import SettingsSubsection from "../shared/SettingsSubsection";
interface IProps { interface IProps {
onShowQr: () => void; onShowQr: () => void;
versions: IServerVersions; versions?: IServerVersions;
} }
export default class LoginWithQRSection extends React.Component<IProps> { export default class LoginWithQRSection extends React.Component<IProps> {

View file

@ -40,6 +40,7 @@ interface IRecommendedVersion {
} }
interface IState { interface IState {
// This is eventually set to the value of room.getRecommendedVersion()
upgradeRecommendation?: IRecommendedVersion; upgradeRecommendation?: IRecommendedVersion;
oldRoomId?: string; oldRoomId?: string;
oldEventId?: string; oldEventId?: string;
@ -52,14 +53,11 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors"); const msc3946ProcessDynamicPredecessor = SettingsStore.getValue("feature_dynamic_room_predecessors");
this.state = { this.state = {};
// This is eventually set to the value of room.getRecommendedVersion()
upgradeRecommendation: null,
};
// we handle lack of this object gracefully later, so don't worry about it failing here. // we handle lack of this object gracefully later, so don't worry about it failing here.
const room = MatrixClientPeg.get().getRoom(this.props.roomId); const room = MatrixClientPeg.get().getRoom(this.props.roomId);
room.getRecommendedVersion().then((v) => { room?.getRecommendedVersion().then((v) => {
const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, ""); const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, "");
const additionalStateChanges: Partial<IState> = {}; const additionalStateChanges: Partial<IState> = {};
@ -99,11 +97,10 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
public render(): React.ReactNode { public render(): React.ReactNode {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
const isSpace = room.isSpaceRoom(); const isSpace = room?.isSpaceRoom();
let unfederatableSection; let unfederatableSection: JSX.Element | undefined;
const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, ""); if (room?.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()["m.federate"] === false) {
if (createEvent && createEvent.getContent()["m.federate"] === false) {
unfederatableSection = <div>{_t("This room is not accessible by remote Matrix servers")}</div>; unfederatableSection = <div>{_t("This room is not accessible by remote Matrix servers")}</div>;
} }
@ -132,13 +129,13 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
); );
} }
let oldRoomLink; let oldRoomLink: JSX.Element | undefined;
if (this.state.oldRoomId) { if (this.state.oldRoomId) {
let copy: string; let copy: string;
if (isSpace) { if (isSpace) {
copy = _t("View older version of %(spaceName)s.", { spaceName: room.name }); copy = _t("View older version of %(spaceName)s.", { spaceName: room?.name ?? this.state.oldRoomId });
} else { } else {
copy = _t("View older messages in %(roomName)s.", { roomName: room.name }); copy = _t("View older messages in %(roomName)s.", { roomName: room?.name ?? this.state.oldRoomId });
} }
oldRoomLink = ( oldRoomLink = (
@ -165,7 +162,7 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
<span className="mx_SettingsTab_subheading">{_t("Room version")}</span> <span className="mx_SettingsTab_subheading">{_t("Room version")}</span>
<div> <div>
<span>{_t("Room version:")}</span>&nbsp; <span>{_t("Room version:")}</span>&nbsp;
{room.getVersion()} {room?.getVersion()}
</div> </div>
{oldRoomLink} {oldRoomLink}
{roomUpgradeButton} {roomUpgradeButton}

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 from "react"; import React, { ReactNode } from "react";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -34,17 +34,16 @@ interface IProps {
} }
export default class BridgeSettingsTab extends React.Component<IProps> { export default class BridgeSettingsTab extends React.Component<IProps> {
private renderBridgeCard(event: MatrixEvent, room: Room): JSX.Element { private renderBridgeCard(event: MatrixEvent, room: Room | null): ReactNode {
const content = event.getContent(); const content = event.getContent();
if (!content || !content.channel || !content.protocol) { if (!room || !content?.channel || !content.protocol) return null;
return null;
}
return <BridgeTile key={event.getId()} room={room} ev={event} />; return <BridgeTile key={event.getId()} room={room} ev={event} />;
} }
public static getBridgeStateEvents(roomId: string): MatrixEvent[] { public static getBridgeStateEvents(roomId: string): MatrixEvent[] {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const roomState = client.getRoom(roomId).currentState; const roomState = client.getRoom(roomId)?.currentState;
if (!roomState) return [];
return BRIDGE_EVENT_TYPES.map((typeName) => roomState.getStateEvents(typeName)).flat(1); return BRIDGE_EVENT_TYPES.map((typeName) => roomState.getStateEvents(typeName)).flat(1);
} }

View file

@ -61,15 +61,14 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client); const canSetCanonical = room?.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", ""); const canonicalAliasEv = room?.currentState.getStateEvents("m.room.canonical_alias", "") ?? undefined;
const urlPreviewSettings = SettingsStore.getValue(UIFeature.URLPreviews) ? ( const urlPreviewSettings =
<UrlPreviewSettings room={room} /> room && SettingsStore.getValue(UIFeature.URLPreviews) ? <UrlPreviewSettings room={room} /> : null;
) : null;
let leaveSection; let leaveSection;
if (room.getMyMembership() === "join") { if (room?.getMyMembership() === "join") {
leaveSection = ( leaveSection = (
<> <>
<span className="mx_SettingsTab_subheading">{_t("Leave room")}</span> <span className="mx_SettingsTab_subheading">{_t("Leave room")}</span>

View file

@ -40,7 +40,7 @@ interface IProps {
interface IState { interface IState {
currentSound: string; currentSound: string;
uploadedFile: File; uploadedFile: File | null;
} }
export default class NotificationsSettingsTab extends React.Component<IProps, IState> { export default class NotificationsSettingsTab extends React.Component<IProps, IState> {
@ -71,7 +71,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.soundUpload.current.click(); this.soundUpload.current?.click();
}; };
private onSoundUploadChanged = (e: React.ChangeEvent<HTMLInputElement>): void => { private onSoundUploadChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
@ -156,7 +156,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
let currentUploadedFile = null; let currentUploadedFile: JSX.Element | undefined;
if (this.state.uploadedFile) { if (this.state.uploadedFile) {
currentUploadedFile = ( currentUploadedFile = (
<div> <div>

View file

@ -239,13 +239,13 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
public render(): React.ReactNode { public render(): React.ReactNode {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
const isSpaceRoom = room.isSpaceRoom(); const isSpaceRoom = room?.isSpaceRoom();
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, ""); const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
const plContent = plEvent ? plEvent.getContent() || {} : {}; const plContent = plEvent ? plEvent.getContent() || {} : {};
const canChangeLevels = room.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client); const canChangeLevels = room?.currentState.mayClientSendStateEvent(EventType.RoomPowerLevels, client);
const plEventsToLabels: Record<EventType | string, string> = { const plEventsToLabels: Record<EventType | string, string | null> = {
// These will be translated for us later. // These will be translated for us later.
[EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"), [EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"),
[EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"), [EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"),
@ -322,7 +322,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
powerLevelDescriptors.users_default.defaultValue, powerLevelDescriptors.users_default.defaultValue,
); );
let currentUserLevel = userLevels[client.getUserId()]; let currentUserLevel = userLevels[client.getUserId()!];
if (currentUserLevel === undefined) { if (currentUserLevel === undefined) {
currentUserLevel = defaultUserLevel; currentUserLevel = defaultUserLevel;
} }
@ -391,16 +391,16 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
} }
} }
const banned = room.getMembersWithMembership("ban"); const banned = room?.getMembersWithMembership("ban");
let bannedUsersSection; let bannedUsersSection: JSX.Element | undefined;
if (banned.length) { if (banned?.length) {
const canBanUsers = currentUserLevel >= banLevel; const canBanUsers = currentUserLevel >= banLevel;
bannedUsersSection = ( bannedUsersSection = (
<SettingsFieldset legend={_t("Banned users")}> <SettingsFieldset legend={_t("Banned users")}>
<ul> <ul>
{banned.map((member) => { {banned.map((member) => {
const banEvent = member.events.member.getContent(); const banEvent = member.events.member?.getContent();
const sender = room.getMember(member.events.member.getSender()); const sender = room?.getMember(member.events.member.getSender());
let bannedBy = member.events.member.getSender(); // start by falling back to mxid let bannedBy = member.events.member.getSender(); // start by falling back to mxid
if (sender) bannedBy = sender.name; if (sender) bannedBy = sender.name;
return ( return (

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 from "react"; import React, { ReactNode } from "react";
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials"; import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
@ -61,16 +61,16 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) { public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context); super(props, context);
const state = context.getRoom(this.props.roomId).currentState; const state = context.getRoom(this.props.roomId)?.currentState;
this.state = { this.state = {
guestAccess: this.pullContentPropertyFromEvent<GuestAccess>( guestAccess: this.pullContentPropertyFromEvent<GuestAccess>(
state.getStateEvents(EventType.RoomGuestAccess, ""), state?.getStateEvents(EventType.RoomGuestAccess, ""),
"guest_access", "guest_access",
GuestAccess.Forbidden, GuestAccess.Forbidden,
), ),
history: this.pullContentPropertyFromEvent<HistoryVisibility>( history: this.pullContentPropertyFromEvent<HistoryVisibility>(
state.getStateEvents(EventType.RoomHistoryVisibility, ""), state?.getStateEvents(EventType.RoomHistoryVisibility, ""),
"history_visibility", "history_visibility",
HistoryVisibility.Shared, HistoryVisibility.Shared,
), ),
@ -85,7 +85,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
this.hasAliases().then((hasAliases) => this.setState({ hasAliases })); this.hasAliases().then((hasAliases) => this.setState({ hasAliases }));
} }
private pullContentPropertyFromEvent<T>(event: MatrixEvent, key: string, defaultValue: T): T { private pullContentPropertyFromEvent<T>(event: MatrixEvent | null | undefined, key: string, defaultValue: T): T {
return event?.getContent()[key] || defaultValue; return event?.getContent()[key] || defaultValue;
} }
@ -117,7 +117,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
"You'll get none of the benefits of encryption, and you won't be able to turn it " + "You'll get none of the benefits of encryption, and you won't be able to turn it " +
"off later. Encrypting messages in a public room will make receiving and sending " + "off later. Encrypting messages in a public room will make receiving and sending " +
"messages slower.", "messages slower.",
null, undefined,
{ b: (sub) => <b>{sub}</b> }, { b: (sub) => <b>{sub}</b> },
)}{" "} )}{" "}
</p> </p>
@ -126,7 +126,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{_t( {_t(
"To avoid these issues, create a <a>new encrypted room</a> for " + "To avoid these issues, create a <a>new encrypted room</a> for " +
"the conversation you plan to have.", "the conversation you plan to have.",
null, undefined,
{ {
a: (sub) => ( a: (sub) => (
<AccessibleButton <AccessibleButton
@ -236,7 +236,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
}; };
private updateBlacklistDevicesFlag = (checked: boolean): void => { private updateBlacklistDevicesFlag = (checked: boolean): void => {
this.context.getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked); this.context.getRoom(this.props.roomId)?.setBlacklistUnverifiedDevices(checked);
}; };
private async hasAliases(): Promise<boolean> { private async hasAliases(): Promise<boolean> {
@ -250,8 +250,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
const client = this.context; const client = this.context;
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
let aliasWarning = null; let aliasWarning: JSX.Element | undefined;
if (room.getJoinRule() === JoinRule.Public && !this.state.hasAliases) { if (room?.getJoinRule() === JoinRule.Public && !this.state.hasAliases) {
aliasWarning = ( aliasWarning = (
<div className="mx_SecurityRoomSettingsTab_warning"> <div className="mx_SecurityRoomSettingsTab_warning">
<WarningIcon width={15} height={15} /> <WarningIcon width={15} height={15} />
@ -297,7 +297,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
"It will mean anyone can find and join the room, so anyone can read messages. " + "It will mean anyone can find and join the room, so anyone can read messages. " +
"You'll get none of the benefits of encryption. Encrypting messages in a public " + "You'll get none of the benefits of encryption. Encrypting messages in a public " +
"room will make receiving and sending messages slower.", "room will make receiving and sending messages slower.",
null, undefined,
{ b: (sub) => <b>{sub}</b> }, { b: (sub) => <b>{sub}</b> },
)}{" "} )}{" "}
</p> </p>
@ -306,7 +306,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
{_t( {_t(
"To avoid these issues, create a <a>new public room</a> for the conversation " + "To avoid these issues, create a <a>new public room</a> for the conversation " +
"you plan to have.", "you plan to have.",
null, undefined,
{ {
a: (sub) => ( a: (sub) => (
<AccessibleButton <AccessibleButton
@ -335,15 +335,15 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
return true; return true;
}; };
private renderHistory(): JSX.Element { private renderHistory(): ReactNode {
if (!SettingsStore.getValue(UIFeature.RoomHistorySettings)) { if (!SettingsStore.getValue(UIFeature.RoomHistorySettings)) {
return null; return null;
} }
const client = this.context; const client = this.context;
const history = this.state.history; const history = this.state.history;
const state = client.getRoom(this.props.roomId).currentState; const state = client.getRoom(this.props.roomId)?.currentState;
const canChangeHistory = state.mayClientSendStateEvent(EventType.RoomHistoryVisibility, client); const canChangeHistory = state?.mayClientSendStateEvent(EventType.RoomHistoryVisibility, client);
const options = [ const options = [
{ {
@ -393,8 +393,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
private renderAdvanced(): JSX.Element { private renderAdvanced(): JSX.Element {
const client = this.context; const client = this.context;
const guestAccess = this.state.guestAccess; const guestAccess = this.state.guestAccess;
const state = client.getRoom(this.props.roomId).currentState; const state = client.getRoom(this.props.roomId)?.currentState;
const canSetGuestAccess = state.mayClientSendStateEvent(EventType.RoomGuestAccess, client); const canSetGuestAccess = state?.mayClientSendStateEvent(EventType.RoomGuestAccess, client);
return ( return (
<> <>
@ -418,10 +418,10 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
const client = this.context; const client = this.context;
const room = client.getRoom(this.props.roomId); const room = client.getRoom(this.props.roomId);
const isEncrypted = this.state.encrypted; const isEncrypted = this.state.encrypted;
const hasEncryptionPermission = room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client); const hasEncryptionPermission = room?.currentState.mayClientSendStateEvent(EventType.RoomEncryption, client);
const canEnableEncryption = !isEncrypted && hasEncryptionPermission; const canEnableEncryption = !isEncrypted && hasEncryptionPermission;
let encryptionSettings = null; let encryptionSettings: JSX.Element | undefined;
if (isEncrypted && SettingsStore.isEnabled("blacklistUnverifiedDevices")) { if (isEncrypted && SettingsStore.isEnabled("blacklistUnverifiedDevices")) {
encryptionSettings = ( encryptionSettings = (
<SettingsFlag <SettingsFlag
@ -435,8 +435,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
const historySection = this.renderHistory(); const historySection = this.renderHistory();
let advanced; let advanced: JSX.Element | undefined;
if (room.getJoinRule() === JoinRule.Public) { if (room?.getJoinRule() === JoinRule.Public) {
advanced = ( advanced = (
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<AccessibleButton <AccessibleButton

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, { ChangeEvent } from "react"; import React, { ChangeEvent, ReactNode } from "react";
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig"; import SdkConfig from "../../../../../SdkConfig";
@ -41,8 +41,8 @@ interface IState {
layout: Layout; layout: Layout;
// User profile data for the message preview // User profile data for the message preview
userId?: string; userId?: string;
displayName: string; displayName?: string;
avatarUrl: string; avatarUrl?: string;
} }
export default class AppearanceUserSettingsTab extends React.Component<IProps, IState> { export default class AppearanceUserSettingsTab extends React.Component<IProps, IState> {
@ -58,16 +58,13 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
systemFont: SettingsStore.getValue("systemFont"), systemFont: SettingsStore.getValue("systemFont"),
showAdvanced: false, showAdvanced: false,
layout: SettingsStore.getValue("layout"), layout: SettingsStore.getValue("layout"),
userId: null,
displayName: null,
avatarUrl: null,
}; };
} }
public async componentDidMount(): Promise<void> { public async componentDidMount(): Promise<void> {
// Fetch the current user profile for the message preview // Fetch the current user profile for the message preview
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const userId = client.getUserId(); const userId = client.getUserId()!;
const profileInfo = await client.getProfileInfo(userId); const profileInfo = await client.getProfileInfo(userId);
if (this.unmounted) return; if (this.unmounted) return;
@ -86,7 +83,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
this.setState({ layout: layout }); this.setState({ layout: layout });
}; };
private renderAdvancedSection(): JSX.Element { private renderAdvancedSection(): ReactNode {
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
@ -115,7 +112,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
/> />
<Field <Field
className="mx_AppearanceUserSettingsTab_systemFont" className="mx_AppearanceUserSettingsTab_systemFont"
label={SettingsStore.getDisplayName("systemFont")} label={SettingsStore.getDisplayName("systemFont")!}
onChange={(value: ChangeEvent<HTMLInputElement>) => { onChange={(value: ChangeEvent<HTMLInputElement>) => {
this.setState({ this.setState({
systemFont: value.target.value, systemFont: value.target.value,

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 from "react"; import React, { ReactNode } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -37,7 +37,7 @@ interface IProps {
} }
interface IState { interface IState {
appVersion: string; appVersion: string | null;
canUpdate: boolean; canUpdate: boolean;
} }
@ -53,13 +53,13 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
public componentDidMount(): void { public componentDidMount(): void {
PlatformPeg.get() PlatformPeg.get()
.getAppVersion() ?.getAppVersion()
.then((ver) => this.setState({ appVersion: ver })) .then((ver) => this.setState({ appVersion: ver }))
.catch((e) => { .catch((e) => {
logger.error("Error getting vector version: ", e); logger.error("Error getting vector version: ", e);
}); });
PlatformPeg.get() PlatformPeg.get()
.canSelfUpdate() ?.canSelfUpdate()
.then((v) => this.setState({ canUpdate: v })) .then((v) => this.setState({ canUpdate: v }))
.catch((e) => { .catch((e) => {
logger.error("Error getting self updatability: ", e); logger.error("Error getting self updatability: ", e);
@ -90,7 +90,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
MatrixClientPeg.get() MatrixClientPeg.get()
.store.deleteAllData() .store.deleteAllData()
.then(() => { .then(() => {
PlatformPeg.get().reload(); PlatformPeg.get()?.reload();
}); });
}; };
@ -106,11 +106,11 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
}); });
}; };
private renderLegal(): JSX.Element { private renderLegal(): ReactNode {
const tocLinks = SdkConfig.get().terms_and_conditions_links; const tocLinks = SdkConfig.get().terms_and_conditions_links;
if (!tocLinks) return null; if (!tocLinks) return null;
const legalLinks = []; const legalLinks: JSX.Element[] = [];
for (const tocEntry of tocLinks) { for (const tocEntry of tocLinks) {
legalLinks.push( legalLinks.push(
<div key={tocEntry.url}> <div key={tocEntry.url}>
@ -248,7 +248,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
); );
} }
let updateButton = null; let updateButton: JSX.Element | undefined;
if (this.state.canUpdate) { if (this.state.canUpdate) {
updateButton = <UpdateCheckButton />; updateButton = <UpdateCheckButton />;
} }

View file

@ -105,7 +105,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
this.setState({ busy: true }); this.setState({ busy: true });
try { try {
const list = Mjolnir.sharedInstance().getPersonalList(); const list = Mjolnir.sharedInstance().getPersonalList();
await list.unbanEntity(rule.kind, rule.entity); await list!.unbanEntity(rule.kind, rule.entity);
} catch (e) { } catch (e) {
logger.error(e); logger.error(e);
@ -142,7 +142,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
const renderRules = (rules: ListRule[]): JSX.Element => { const renderRules = (rules: ListRule[]): JSX.Element => {
if (rules.length === 0) return <i>{_t("None")}</i>; if (rules.length === 0) return <i>{_t("None")}</i>;
const tiles = []; const tiles: JSX.Element[] = [];
for (const rule of rules) { for (const rule of rules) {
tiles.push( tiles.push(
<li key={rule.kind + rule.entity}> <li key={rule.kind + rule.entity}>
@ -173,7 +173,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
const rules = list ? [...list.userRules, ...list.serverRules] : []; const rules = list ? [...list.userRules, ...list.serverRules] : [];
if (!list || rules.length <= 0) return <i>{_t("You have not ignored anyone.")}</i>; if (!list || rules.length <= 0) return <i>{_t("You have not ignored anyone.")}</i>;
const tiles = []; const tiles: JSX.Element[] = [];
for (const rule of rules) { for (const rule of rules) {
tiles.push( tiles.push(
<li key={rule.entity} className="mx_MjolnirUserSettingsTab_listItem"> <li key={rule.entity} className="mx_MjolnirUserSettingsTab_listItem">
@ -205,7 +205,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
}); });
if (!lists || lists.length <= 0) return <i>{_t("You are not subscribed to any lists")}</i>; if (!lists || lists.length <= 0) return <i>{_t("You are not subscribed to any lists")}</i>;
const tiles = []; const tiles: JSX.Element[] = [];
for (const list of lists) { for (const list of lists) {
const room = MatrixClientPeg.get().getRoom(list.roomId); const room = MatrixClientPeg.get().getRoom(list.roomId);
const name = room ? ( const name = room ? (

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 from "react"; import React, { ReactNode } from "react";
import { sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -169,7 +169,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
return MatrixClientPeg.get() return MatrixClientPeg.get()
.getRooms() .getRooms()
.filter((r) => { .filter((r) => {
return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite"); return r.hasMembershipState(MatrixClientPeg.get().getUserId()!, "invite");
}); });
}; };
@ -247,7 +247,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
); );
} }
private renderManageInvites(): JSX.Element { private renderManageInvites(): ReactNode {
const { invitedRoomIds } = this.state; const { invitedRoomIds } = this.state;
if (invitedRoomIds.size === 0) { if (invitedRoomIds.size === 0) {

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 from "react"; import React, { ReactNode } from "react";
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler"; import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler";
@ -28,10 +28,10 @@ import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import { requestMediaPermissions } from "../../../../../utils/media/requestMediaPermissions"; import { requestMediaPermissions } from "../../../../../utils/media/requestMediaPermissions";
interface IState { interface IState {
mediaDevices: IMediaDevices; mediaDevices: IMediaDevices | null;
[MediaDeviceKindEnum.AudioOutput]: string; [MediaDeviceKindEnum.AudioOutput]: string | null;
[MediaDeviceKindEnum.AudioInput]: string; [MediaDeviceKindEnum.AudioInput]: string | null;
[MediaDeviceKindEnum.VideoInput]: string; [MediaDeviceKindEnum.VideoInput]: string | null;
audioAutoGainControl: boolean; audioAutoGainControl: boolean;
audioEchoCancellation: boolean; audioEchoCancellation: boolean;
audioNoiseSuppression: boolean; audioNoiseSuppression: boolean;
@ -104,7 +104,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
}); });
} }
private renderDropdown(kind: MediaDeviceKindEnum, label: string): JSX.Element { private renderDropdown(kind: MediaDeviceKindEnum, label: string): ReactNode {
const devices = this.state.mediaDevices[kind].slice(0); const devices = this.state.mediaDevices[kind].slice(0);
if (devices.length === 0) return null; if (devices.length === 0) return null;
@ -121,11 +121,11 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
); );
} }
public render(): React.ReactNode { public render(): ReactNode {
let requestButton = null; let requestButton: ReactNode | undefined;
let speakerDropdown = null; let speakerDropdown: ReactNode | undefined;
let microphoneDropdown = null; let microphoneDropdown: ReactNode | undefined;
let webcamDropdown = null; let webcamDropdown: ReactNode | undefined;
if (!this.state.mediaDevices) { if (!this.state.mediaDevices) {
requestButton = ( requestButton = (
<div className="mx_VoiceUserSettingsTab_missingMediaPermissions"> <div className="mx_VoiceUserSettingsTab_missingMediaPermissions">

View file

@ -46,8 +46,8 @@ const QuickSettingsButton: React.FC<{
const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } = const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } =
useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces"); useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
let contextMenu: JSX.Element; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed && handle.current) {
contextMenu = ( contextMenu = (
<ContextMenu <ContextMenu
{...alwaysAboveRightOf(handle.current.getBoundingClientRect(), ChevronFace.None, 16)} {...alwaysAboveRightOf(handle.current.getBoundingClientRect(), ChevronFace.None, 16)}

View file

@ -38,9 +38,9 @@ const SpaceSettingsGeneralTab: React.FC<IProps> = ({ matrixClient: cli, space, o
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const userId = cli.getUserId(); const userId = cli.getUserId()!;
const [newAvatar, setNewAvatar] = useState<File>(null); // undefined means to remove avatar const [newAvatar, setNewAvatar] = useState<File | null | undefined>(null); // undefined means to remove avatar
const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId); const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
const avatarChanged = newAvatar !== null; const avatarChanged = newAvatar !== null;
@ -48,8 +48,8 @@ const SpaceSettingsGeneralTab: React.FC<IProps> = ({ matrixClient: cli, space, o
const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId); const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
const nameChanged = name !== space.name; const nameChanged = name !== space.name;
const currentTopic = getTopic(space)?.text; const currentTopic = getTopic(space)?.text ?? "";
const [topic, setTopic] = useState<string>(currentTopic); const [topic, setTopic] = useState(currentTopic);
const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId); const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
const topicChanged = topic !== currentTopic; const topicChanged = topic !== currentTopic;
@ -104,7 +104,7 @@ const SpaceSettingsGeneralTab: React.FC<IProps> = ({ matrixClient: cli, space, o
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<SpaceBasicSettings <SpaceBasicSettings
avatarUrl={avatarUrlForRoom(space, 80, 80, "crop")} avatarUrl={avatarUrlForRoom(space, 80, 80, "crop") ?? undefined}
avatarDisabled={busy || !canSetAvatar} avatarDisabled={busy || !canSetAvatar}
setAvatar={setNewAvatar} setAvatar={setNewAvatar}
name={name} name={name}

View file

@ -47,7 +47,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
false, false,
); );
const userId = cli.getUserId(); const userId = cli.getUserId()!;
const joinRule = useRoomState(space, (state) => state.getJoinRule()); const joinRule = useRoomState(space, (state) => state.getJoinRule());
const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho<boolean>( const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho<boolean>(
@ -120,7 +120,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
); );
} }
let addressesSection; let addressesSection: JSX.Element | undefined;
if (space.getJoinRule() === JoinRule.Public) { if (space.getJoinRule() === JoinRule.Public) {
addressesSection = ( addressesSection = (
<> <>
@ -129,7 +129,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
roomId={space.roomId} roomId={space.roomId}
canSetCanonicalAlias={canSetCanonical} canSetCanonicalAlias={canSetCanonical}
canSetAliases={true} canSetAliases={true}
canonicalAliasEvent={canonicalAliasEv} canonicalAliasEvent={canonicalAliasEv ?? undefined}
hidePublishSetting={!serverSupportsExploringSpaces} hidePublishSetting={!serverSupportsExploringSpaces}
/> />
</> </>

View file

@ -83,7 +83,7 @@ export default class InlineTermsAgreement extends React.Component<IProps, IState
}; };
private renderCheckboxes(): React.ReactNode[] { private renderCheckboxes(): React.ReactNode[] {
const rendered = []; const rendered: JSX.Element[] = [];
for (let i = 0; i < this.state.policies.length; i++) { for (let i = 0; i < this.state.policies.length; i++) {
const policy = this.state.policies[i]; const policy = this.state.policies[i];
const introText = _t( const introText = _t(

View file

@ -77,7 +77,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
const device = await cli.getDevice(request.channel.deviceId); const device = await cli.getDevice(request.channel.deviceId);
const ip = device.last_seen_ip; const ip = device.last_seen_ip;
this.setState({ this.setState({
device: cli.getStoredDevice(cli.getUserId(), request.channel.deviceId), device: cli.getStoredDevice(cli.getUserId()!, request.channel.deviceId),
ip, ip,
}); });
} }
@ -137,7 +137,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
request.cancel(); request.cancel();
}, },
}, },
null, undefined,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );

View file

@ -39,7 +39,7 @@ export function UserOnboardingButton({ selected, minimized }: Props): JSX.Elemen
const visible = useSettingValue<boolean>("FTUE.userOnboardingButton"); const visible = useSettingValue<boolean>("FTUE.userOnboardingButton");
if (!visible || minimized || !showUserOnboardingPage(useCase)) { if (!visible || minimized || !showUserOnboardingPage(useCase)) {
return null; return <></>;
} }
return <UserOnboardingButtonInternal selected={selected} minimized={minimized} />; return <UserOnboardingButtonInternal selected={selected} minimized={minimized} />;

View file

@ -26,7 +26,7 @@ import { shouldShowFeedback } from "../../../utils/Feedback";
export function UserOnboardingFeedback(): JSX.Element { export function UserOnboardingFeedback(): JSX.Element {
if (!shouldShowFeedback()) { if (!shouldShowFeedback()) {
return null; return <></>;
} }
return ( return (

View file

@ -57,7 +57,7 @@ export function UserOnboardingTask({ task, completed = false }: Props): JSX.Elem
kind="primary_outline" kind="primary_outline"
href={task.action.href} href={task.action.href}
target="_blank" target="_blank"
onClick={task.action.onClick} onClick={task.action.onClick ?? null}
> >
{task.action.label} {task.action.label}
</AccessibleButton> </AccessibleButton>

View file

@ -150,7 +150,7 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
}, [videoMuted, setVideoMuted]); }, [videoMuted, setVideoMuted]);
const [videoStream, audioInputs, videoInputs] = useAsyncMemo( const [videoStream, audioInputs, videoInputs] = useAsyncMemo(
async (): Promise<[MediaStream, MediaDeviceInfo[], MediaDeviceInfo[]]> => { async (): Promise<[MediaStream | null, MediaDeviceInfo[], MediaDeviceInfo[]]> => {
let devices = await MediaDeviceHandler.getDevices(); let devices = await MediaDeviceHandler.getDevices();
// We get the preview stream before requesting devices: this is because // We get the preview stream before requesting devices: this is because
@ -335,7 +335,7 @@ const StartCallView: FC<StartCallViewProps> = ({ room, resizing, call, setStarti
<AppTile <AppTile
app={call.widget} app={call.widget}
room={room} room={room}
userId={cli.credentials.userId} userId={cli.credentials.userId!}
creatorUserId={call.widget.creatorUserId} creatorUserId={call.widget.creatorUserId}
waitForIframeLoad={call.widget.waitForIframeLoad} waitForIframeLoad={call.widget.waitForIframeLoad}
showMenubar={false} showMenubar={false}
@ -402,7 +402,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
<AppTile <AppTile
app={call.widget} app={call.widget}
room={room} room={room}
userId={cli.credentials.userId} userId={cli.credentials.userId!}
creatorUserId={call.widget.creatorUserId} creatorUserId={call.widget.creatorUserId}
waitForIframeLoad={call.widget.waitForIframeLoad} waitForIframeLoad={call.widget.waitForIframeLoad}
showMenubar={false} showMenubar={false}

View file

@ -180,7 +180,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
} }
}; };
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall): void { private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall | null): void {
if (oldCall === newCall) return; if (oldCall === newCall) return;
if (oldCall) { if (oldCall) {
@ -430,7 +430,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const { pipMode, call, onResize } = this.props; const { pipMode, call, onResize } = this.props;
const { isLocalOnHold, isRemoteOnHold, sidebarShown, primaryFeed, secondaryFeed, sidebarFeeds } = this.state; const { isLocalOnHold, isRemoteOnHold, sidebarShown, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;
const callRoom = MatrixClientPeg.get().getRoom(call.roomId); const callRoom = MatrixClientPeg.get().getRoom(call.roomId) ?? undefined;
const avatarSize = pipMode ? 76 : 160; const avatarSize = pipMode ? 76 : 160;
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(call.callId); const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(call.callId);
const isOnHold = isLocalOnHold || isRemoteOnHold; const isOnHold = isLocalOnHold || isRemoteOnHold;

View file

@ -190,6 +190,8 @@ function getTextAndOffsetToNode(editor: HTMLDivElement, selectionNode: Node): {
// get text value of text node, ignoring ZWS if it's a caret node // get text value of text node, ignoring ZWS if it's a caret node
function getTextNodeValue(node: Node): string { function getTextNodeValue(node: Node): string {
const nodeText = node.nodeValue; const nodeText = node.nodeValue;
if (!nodeText) return "";
// filter out ZWS for caret nodes // filter out ZWS for caret nodes
if (isCaretNode(node.parentElement)) { if (isCaretNode(node.parentElement)) {
// typed in the caret node, so there is now something more in it than the ZWS // typed in the caret node, so there is now something more in it than the ZWS
@ -200,9 +202,9 @@ function getTextNodeValue(node: Node): string {
// only contains ZWS, which is ignored, so return empty string // only contains ZWS, which is ignored, so return empty string
return ""; return "";
} }
} else {
return nodeText;
} }
return nodeText;
} }
export function getRangeForSelection(editor: HTMLDivElement, model: EditorModel, selection: Selection): Range { export function getRangeForSelection(editor: HTMLDivElement, model: EditorModel, selection: Selection): Range {

View file

@ -424,7 +424,7 @@ class RoomPillPart extends PillPart {
let initialLetter = ""; let initialLetter = "";
let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop"); let avatarUrl = Avatar.avatarUrlForRoom(this.room, 16, 16, "crop");
if (!avatarUrl) { if (!avatarUrl) {
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId); initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId); avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
} }
this.setAvatarVars(node, avatarUrl, initialLetter); this.setAvatarVars(node, avatarUrl, initialLetter);
@ -478,7 +478,7 @@ class UserPillPart extends PillPart {
const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop"); const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop");
let initialLetter = ""; let initialLetter = "";
if (avatarUrl === defaultAvatarUrl) { if (avatarUrl === defaultAvatarUrl) {
initialLetter = Avatar.getInitialLetter(name); initialLetter = Avatar.getInitialLetter(name) ?? "";
} }
this.setAvatarVars(node, avatarUrl, initialLetter); this.setAvatarVars(node, avatarUrl, initialLetter);
} }

View file

@ -18,7 +18,9 @@ import { useState, useEffect, DependencyList } from "react";
type Fn<T> = () => Promise<T>; type Fn<T> = () => Promise<T>;
export const useAsyncMemo = <T>(fn: Fn<T>, deps: DependencyList, initialValue?: T): T | undefined => { export function useAsyncMemo<T>(fn: Fn<T>, deps: DependencyList, initialValue: T): T;
export function useAsyncMemo<T>(fn: Fn<T>, deps: DependencyList, initialValue?: T): T | undefined;
export function useAsyncMemo<T>(fn: Fn<T>, deps: DependencyList, initialValue?: T): T | undefined {
const [value, setValue] = useState<T | undefined>(initialValue); const [value, setValue] = useState<T | undefined>(initialValue);
useEffect(() => { useEffect(() => {
let discard = false; let discard = false;
@ -32,4 +34,4 @@ export const useAsyncMemo = <T>(fn: Fn<T>, deps: DependencyList, initialValue?:
}; };
}, deps); // eslint-disable-line react-hooks/exhaustive-deps }, deps); // eslint-disable-line react-hooks/exhaustive-deps
return value; return value;
}; }

View file

@ -37,7 +37,7 @@ interface UserOnboardingTask {
relevant?: UseCase[]; relevant?: UseCase[];
action?: { action?: {
label: string; label: string;
onClick?: (ev?: ButtonEvent) => void; onClick?: (ev: ButtonEvent) => void;
href?: string; href?: string;
hideOnComplete?: boolean; hideOnComplete?: boolean;
}; };

View file

@ -87,7 +87,7 @@ export function _td(s: string): string {
* for this reason, force fallbackLocale === locale in the first call to translate * for this reason, force fallbackLocale === locale in the first call to translate
* and fallback 'manually' so we can mark fallback strings appropriately * and fallback 'manually' so we can mark fallback strings appropriately
* */ * */
const translateWithFallback = (text: string, options?: IVariables): { translated?: string; isFallback?: boolean } => { const translateWithFallback = (text: string, options?: IVariables): { translated: string; isFallback?: boolean } => {
const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() }); const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() });
if (!translated || translated.startsWith("missing translation:")) { if (!translated || translated.startsWith("missing translation:")) {
const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE }); const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE });
@ -117,7 +117,7 @@ const translateWithFallback = (text: string, options?: IVariables): { translated
// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate() // Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text: string, variables?: IVariables): { translated?: string; isFallback?: boolean } { function safeCounterpartTranslate(text: string, variables?: IVariables): { translated: string; isFallback?: boolean } {
// Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
// It is enough to pass the count variable, but in the future counterpart might make use of other information too // It is enough to pass the count variable, but in the future counterpart might make use of other information too
@ -251,7 +251,7 @@ export function sanitizeForTranslation(text: string): string {
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string * @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/ */
export function substitute(text: string, variables?: IVariables): string; export function substitute(text: string, variables?: IVariables): string;
export function substitute(text: string, variables: IVariables, tags: Tags): string; export function substitute(text: string, variables: IVariables | undefined, tags: Tags | undefined): string;
export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode {
let result: React.ReactNode | string = text; let result: React.ReactNode | string = text;
@ -619,7 +619,7 @@ let cachedCustomTranslationsExpire = 0; // zero to trigger expiration right away
// This awkward class exists so the test runner can get at the function. It is // This awkward class exists so the test runner can get at the function. It is
// not intended for practical or realistic usage. // not intended for practical or realistic usage.
export class CustomTranslationOptions { export class CustomTranslationOptions {
public static lookupFn: (url: string) => ICustomTranslations; public static lookupFn?: (url: string) => ICustomTranslations;
private constructor() { private constructor() {
// static access for tests only // static access for tests only

View file

@ -74,7 +74,7 @@ export class VoiceRecordingStore extends AsyncStoreWithClient<IState> {
* @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to start recording in. * @param {string} voiceRecordingId The room ID (with optionally the thread ID if in one) to start recording in.
* @returns {VoiceRecording} The recording. * @returns {VoiceRecording} The recording.
*/ */
public startRecording(voiceRecordingId: string): VoiceMessageRecording { public startRecording(voiceRecordingId?: string): VoiceMessageRecording {
if (!this.matrixClient) throw new Error("Cannot start a recording without a MatrixClient"); if (!this.matrixClient) throw new Error("Cannot start a recording without a MatrixClient");
if (!voiceRecordingId) throw new Error("Recording must be associated with a room"); if (!voiceRecordingId) throw new Error("Recording must be associated with a room");
if (this.state[voiceRecordingId]) throw new Error("A recording is already in progress"); if (this.state[voiceRecordingId]) throw new Error("A recording is already in progress");

View file

@ -218,15 +218,15 @@ export class StopGapWidgetDriver extends WidgetDriver {
public async sendEvent( public async sendEvent(
eventType: string, eventType: string,
content: IContent, content: IContent,
stateKey: string = null, stateKey?: string,
targetRoomId: string = null, targetRoomId?: string,
): Promise<ISendEventDetails> { ): Promise<ISendEventDetails> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId(); const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
let r: { event_id: string } = null; // eslint-disable-line camelcase let r: { event_id: string } | null = null; // eslint-disable-line camelcase
if (stateKey !== null) { if (stateKey !== null) {
// state event // state event
r = await client.sendStateEvent(roomId, eventType, content, stateKey); r = await client.sendStateEvent(roomId, eventType, content, stateKey);
@ -242,7 +242,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
if (containsEmoji(content, effect.emojis)) { if (containsEmoji(content, effect.emojis)) {
// For initial threads launch, chat effects are disabled // For initial threads launch, chat effects are disabled
// see #19731 // see #19731
const isNotThread = content["m.relates_to"].rel_type !== THREAD_RELATION_TYPE.name; const isNotThread = content["m.relates_to"]?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) { if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` }); dis.dispatch({ action: `effects.${effect.command}` });
} }
@ -300,7 +300,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
} }
} }
private pickRooms(roomIds: (string | Symbols.AnyRoom)[] = null): Room[] { private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) throw new Error("Not attached to a client"); if (!client) throw new Error("Not attached to a client");
@ -309,14 +309,14 @@ export class StopGapWidgetDriver extends WidgetDriver {
? client.getVisibleRooms() ? client.getVisibleRooms()
: roomIds.map((r) => client.getRoom(r)) : roomIds.map((r) => client.getRoom(r))
: [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId())]; : [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId())];
return targetRooms.filter((r) => !!r); return targetRooms.filter((r) => !!r) as Room[];
} }
public async readRoomEvents( public async readRoomEvents(
eventType: string, eventType: string,
msgtype: string | undefined, msgtype: string | undefined,
limitPerRoom: number, limitPerRoom: number,
roomIds: (string | Symbols.AnyRoom)[] = null, roomIds?: (string | Symbols.AnyRoom)[],
): Promise<IRoomEvent[]> { ): Promise<IRoomEvent[]> {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
@ -343,7 +343,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
eventType: string, eventType: string,
stateKey: string | undefined, stateKey: string | undefined,
limitPerRoom: number, limitPerRoom: number,
roomIds: (string | Symbols.AnyRoom)[] = null, roomIds?: (string | Symbols.AnyRoom)[],
): Promise<IRoomEvent[]> { ): Promise<IRoomEvent[]> {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
@ -467,8 +467,8 @@ export class StopGapWidgetDriver extends WidgetDriver {
return { return {
chunk: events.map((e) => e.getEffectiveEvent() as IRoomEvent), chunk: events.map((e) => e.getEffectiveEvent() as IRoomEvent),
nextBatch, nextBatch: nextBatch ?? undefined,
prevBatch, prevBatch: prevBatch ?? undefined,
}; };
} }
} }

View file

@ -48,10 +48,12 @@ export const getBeaconBounds = (beacons: Beacon[]): Bounds | undefined => {
const sortedByLat = [...coords].sort((left, right) => right.latitude - left.latitude); const sortedByLat = [...coords].sort((left, right) => right.latitude - left.latitude);
const sortedByLong = [...coords].sort((left, right) => right.longitude - left.longitude); const sortedByLong = [...coords].sort((left, right) => right.longitude - left.longitude);
if (sortedByLat.length < 1 || sortedByLong.length < 1) return;
return { return {
north: sortedByLat[0].latitude, north: sortedByLat[0]!.latitude,
south: sortedByLat[sortedByLat.length - 1].latitude, south: sortedByLat[sortedByLat.length - 1]!.latitude,
east: sortedByLong[0].longitude, east: sortedByLong[0]!.longitude,
west: sortedByLong[sortedByLong.length - 1].longitude, west: sortedByLong[sortedByLong.length - 1]!.longitude,
}; };
}; };

View file

@ -135,7 +135,7 @@ export class CapabilityText {
// First see if we have a super simple line of text to provide back // First see if we have a super simple line of text to provide back
if (CapabilityText.simpleCaps[capability]) { if (CapabilityText.simpleCaps[capability]) {
const textForKind = CapabilityText.simpleCaps[capability]; const textForKind = CapabilityText.simpleCaps[capability];
if (textForKind[kind]) return { primary: _t(textForKind[kind]) }; if (textForKind[kind]) return { primary: _t(textForKind[kind]!) };
if (textForKind[GENERIC_WIDGET_KIND]) return { primary: _t(textForKind[GENERIC_WIDGET_KIND]) }; if (textForKind[GENERIC_WIDGET_KIND]) return { primary: _t(textForKind[GENERIC_WIDGET_KIND]) };
// ... we'll fall through to the generic capability processing at the end of this // ... we'll fall through to the generic capability processing at the end of this
@ -195,8 +195,8 @@ export class CapabilityText {
: CapabilityText.nonStateSendRecvCaps; : CapabilityText.nonStateSendRecvCaps;
if (evSendRecv[eventCap.eventType]) { if (evSendRecv[eventCap.eventType]) {
const textForKind = evSendRecv[eventCap.eventType]; const textForKind = evSendRecv[eventCap.eventType];
const textForDirection = textForKind[kind] || textForKind[GENERIC_WIDGET_KIND]; const textForDirection = textForKind?.[kind] || textForKind?.[GENERIC_WIDGET_KIND];
if (textForDirection && textForDirection[eventCap.direction]) { if (textForDirection?.[eventCap.direction]) {
return { return {
primary: _t(textForDirection[eventCap.direction]), primary: _t(textForDirection[eventCap.direction]),
// no byline because we would have already represented the event properly // no byline because we would have already represented the event properly

View file

@ -87,7 +87,7 @@ export class Jitsi {
* @param {string} url The URL to parse. * @param {string} url The URL to parse.
* @returns {JitsiWidgetData} The widget data if eligible, otherwise null. * @returns {JitsiWidgetData} The widget data if eligible, otherwise null.
*/ */
public parsePreferredConferenceUrl(url: string): JitsiWidgetData { public parsePreferredConferenceUrl(url: string): JitsiWidgetData | null {
const parsed = new URL(url); const parsed = new URL(url);
if (parsed.hostname !== this.preferredDomain) return null; // invalid if (parsed.hostname !== this.preferredDomain) return null; // invalid
return { return {

View file

@ -57,7 +57,7 @@ describe("PosthogAnalytics", () => {
digest: async (_: AlgorithmIdentifier, encodedMessage: BufferSource) => { digest: async (_: AlgorithmIdentifier, encodedMessage: BufferSource) => {
const message = new TextDecoder().decode(encodedMessage); const message = new TextDecoder().decode(encodedMessage);
const hexHash = shaHashes[message]; const hexHash = shaHashes[message];
const bytes = []; const bytes: number[] = [];
for (let c = 0; c < hexHash.length; c += 2) { for (let c = 0; c < hexHash.length; c += 2) {
bytes.push(parseInt(hexHash.slice(c, c + 2), 16)); bytes.push(parseInt(hexHash.slice(c, c + 2), 16));
} }
@ -68,6 +68,7 @@ describe("PosthogAnalytics", () => {
}); });
afterEach(() => { afterEach(() => {
// @ts-ignore
window.crypto = null; window.crypto = null;
SdkConfig.unset(); // we touch the config, so clean up SdkConfig.unset(); // we touch the config, so clean up
}); });
@ -116,7 +117,7 @@ describe("PosthogAnalytics", () => {
foo: "bar", foo: "bar",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][0]).toBe("JestTestEvents"); expect(mocked(fakePosthog).capture.mock.calls[0][0]).toBe("JestTestEvents");
expect(mocked(fakePosthog).capture.mock.calls[0][1]["foo"]).toEqual("bar"); expect(mocked(fakePosthog).capture.mock.calls[0][1]!["foo"]).toEqual("bar");
}); });
it("Should not track events if anonymous", async () => { it("Should not track events if anonymous", async () => {
@ -209,7 +210,7 @@ describe("PosthogAnalytics", () => {
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "JestTestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({
WebLayout: "IRC", WebLayout: "IRC",
}); });
}); });
@ -226,7 +227,7 @@ describe("PosthogAnalytics", () => {
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "JestTestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({
WebLayout: "Bubble", WebLayout: "Bubble",
}); });
}); });
@ -243,7 +244,7 @@ describe("PosthogAnalytics", () => {
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "JestTestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({
WebLayout: "Group", WebLayout: "Group",
}); });
}); });
@ -261,7 +262,7 @@ describe("PosthogAnalytics", () => {
analytics.trackEvent<ITestEvent>({ analytics.trackEvent<ITestEvent>({
eventName: "JestTestEvents", eventName: "JestTestEvents",
}); });
expect(mocked(fakePosthog).capture.mock.calls[0][1]["$set"]).toStrictEqual({ expect(mocked(fakePosthog).capture.mock.calls[0][1]!["$set"]).toStrictEqual({
WebLayout: "Compact", WebLayout: "Compact",
}); });
}); });

View file

@ -53,7 +53,7 @@ describe("ScalarAuthClient", function () {
client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject); client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject);
sac.exchangeForScalarToken = jest.fn((arg) => { sac.exchangeForScalarToken = jest.fn((arg) => {
if (arg === tokenObject) return Promise.resolve("wokentoken"); return Promise.resolve(arg === tokenObject ? "wokentoken" : "othertoken");
}); });
await sac.connect(); await sac.connect();

View file

@ -223,7 +223,7 @@ describe("SlashCommands", () => {
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());
}); });
}); });
@ -232,7 +232,7 @@ describe("SlashCommands", () => {
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).error).toBe(command.getUsage()); expect(command.run(roomId, null, undefined).error).toBe(command.getUsage());
}); });
it("should make things rainbowy", () => { it("should make things rainbowy", () => {

View file

@ -58,7 +58,7 @@ describe("SlidingSyncManager", () => {
event_id: "$abc123", event_id: "$abc123",
sender: client.getUserId()!, sender: client.getUserId()!,
content: { content: {
creator: client.getUserId(), creator: client.getUserId()!,
}, },
}), }),
]); ]);

View file

@ -125,7 +125,7 @@ describe("VoiceMessageRecording", () => {
const encryptedFile = {} as unknown as IEncryptedFile; const encryptedFile = {} as unknown as IEncryptedFile;
beforeEach(() => { beforeEach(() => {
voiceRecording.onDataAvailable(testBuf); voiceRecording.onDataAvailable!(testBuf);
}); });
it("contentLength should return the buffer length", () => { it("contentLength should return the buffer length", () => {
@ -143,9 +143,9 @@ describe("VoiceMessageRecording", () => {
}); });
describe("upload", () => { describe("upload", () => {
let uploadFileClient: MatrixClient; let uploadFileClient: MatrixClient | null;
let uploadFileRoomId: string; let uploadFileRoomId: string | null;
let uploadBlob: Blob; let uploadBlob: Blob | null;
beforeEach(() => { beforeEach(() => {
uploadFileClient = null; uploadFileClient = null;
@ -182,8 +182,8 @@ describe("VoiceMessageRecording", () => {
expect(mocked(uploadFile)).toHaveBeenCalled(); expect(mocked(uploadFile)).toHaveBeenCalled();
expect(uploadFileClient).toBe(client); expect(uploadFileClient).toBe(client);
expect(uploadFileRoomId).toBe(roomId); expect(uploadFileRoomId).toBe(roomId);
expect(uploadBlob.type).toBe(contentType); expect(uploadBlob?.type).toBe(contentType);
const blobArray = await uploadBlob.arrayBuffer(); const blobArray = await uploadBlob!.arrayBuffer();
expect(new Uint8Array(blobArray)).toEqual(testBuf); expect(new Uint8Array(blobArray)).toEqual(testBuf);
}); });

View file

@ -79,7 +79,7 @@ describe("RoomView", () => {
stores.client = cli; stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli); stores.rightPanelStore.useUnitTestClient(cli);
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(null); jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(undefined);
}); });
afterEach(async () => { afterEach(async () => {
@ -175,7 +175,7 @@ describe("RoomView", () => {
instance = await getRoomViewInstance(); instance = await getRoomViewInstance();
oldRoom = new Room("!old:example.com", cli, cli.getSafeUserId()); oldRoom = new Room("!old:example.com", cli, cli.getSafeUserId());
rooms.set(oldRoom.roomId, oldRoom); rooms.set(oldRoom.roomId, oldRoom);
jest.spyOn(room, "findPredecessor").mockReturnValue({ roomId: oldRoom.roomId, eventId: null }); jest.spyOn(room, "findPredecessor").mockReturnValue({ roomId: oldRoom.roomId });
}); });
it("and it has 0 unreads, getHiddenHighlightCount should return 0", async () => { it("and it has 0 unreads, getHiddenHighlightCount should return 0", async () => {

View file

@ -181,7 +181,7 @@ describe("Login", function () {
const { container, rerender } = render(getRawComponent()); const { container, rerender } = render(getRawComponent());
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
fireEvent.click(container.querySelector(".mx_SSOButton")); fireEvent.click(container.querySelector(".mx_SSOButton")!);
expect(platform.startSingleSignOn.mock.calls[0][0].baseUrl).toBe("https://matrix.org"); expect(platform.startSingleSignOn.mock.calls[0][0].baseUrl).toBe("https://matrix.org");
fetchMock.get("https://server2/_matrix/client/versions", { fetchMock.get("https://server2/_matrix/client/versions", {
@ -190,7 +190,7 @@ describe("Login", function () {
}); });
rerender(getRawComponent("https://server2")); rerender(getRawComponent("https://server2"));
fireEvent.click(container.querySelector(".mx_SSOButton")); fireEvent.click(container.querySelector(".mx_SSOButton")!);
expect(platform.startSingleSignOn.mock.calls[1][0].baseUrl).toBe("https://server2"); expect(platform.startSingleSignOn.mock.calls[1][0].baseUrl).toBe("https://server2");
}); });

View file

@ -118,7 +118,7 @@ describe("Registration", function () {
const { container, rerender } = render(getRawComponent()); const { container, rerender } = render(getRawComponent());
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
fireEvent.click(container.querySelector(".mx_SSOButton")); fireEvent.click(container.querySelector(".mx_SSOButton")!);
expect(registerRequest.mock.instances[0].baseUrl).toBe("https://matrix.org"); expect(registerRequest.mock.instances[0].baseUrl).toBe("https://matrix.org");
fetchMock.get("https://server2/_matrix/client/versions", { fetchMock.get("https://server2/_matrix/client/versions", {
@ -128,7 +128,7 @@ describe("Registration", function () {
rerender(getRawComponent("https://server2")); rerender(getRawComponent("https://server2"));
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…")); await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
fireEvent.click(container.querySelector(".mx_SSOButton")); fireEvent.click(container.querySelector(".mx_SSOButton")!);
expect(registerRequest.mock.instances[1].baseUrl).toBe("https://server2"); expect(registerRequest.mock.instances[1].baseUrl).toBe("https://server2");
}); });
}); });

View file

@ -84,7 +84,7 @@ describe("<BeaconListItem />", () => {
const member = new RoomMember(roomId, aliceId); const member = new RoomMember(roomId, aliceId);
member.name = `Alice`; member.name = `Alice`;
const room = mockClient.getRoom(roomId); const room = mockClient.getRoom(roomId)!;
jest.spyOn(room, "getMember").mockReturnValue(member); jest.spyOn(room, "getMember").mockReturnValue(member);
return beacons; return beacons;
@ -185,7 +185,7 @@ describe("<BeaconListItem />", () => {
const { container } = getComponent({ beacon, onClick }); const { container } = getComponent({ beacon, onClick });
// click the beacon name // click the beacon name
fireEvent.click(container.querySelector(".mx_BeaconStatus_description")); fireEvent.click(container.querySelector(".mx_BeaconStatus_description")!);
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
}); });
}); });

View file

@ -47,7 +47,7 @@ describe("<ShareLatestLocation />", () => {
const { container, asFragment } = getComponent(); const { container, asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot();
fireEvent.click(container.querySelector(".mx_CopyableText_copyButton")); fireEvent.click(container.querySelector(".mx_CopyableText_copyButton")!);
await flushPromises(); await flushPromises();
expect(copyPlaintext).toHaveBeenCalledWith("51,42"); expect(copyPlaintext).toHaveBeenCalledWith("51,42");

View file

@ -575,7 +575,7 @@ function createMenu(
return mount( return mount(
<RoomContext.Provider value={context as IRoomState}> <RoomContext.Provider value={context as IRoomState}>
<MessageContextMenu chevronFace={null} mxEvent={mxEvent} onFinished={jest.fn()} {...props} /> <MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
</RoomContext.Provider>, </RoomContext.Provider>,
); );
} }

View file

@ -32,7 +32,6 @@ describe("AccessSecretStorageDialog", () => {
const defaultProps: ComponentProps<typeof AccessSecretStorageDialog> = { const defaultProps: ComponentProps<typeof AccessSecretStorageDialog> = {
onFinished: jest.fn(), onFinished: jest.fn(),
checkPrivateKey: jest.fn(), checkPrivateKey: jest.fn(),
keyInfo: undefined,
}; };
const renderComponent = (props = {}): void => { const renderComponent = (props = {}): void => {

View file

@ -66,11 +66,11 @@ describe("EventListSummary", function () {
} }
const generateMembershipEvent = ( const generateMembershipEvent = (
eventId: string, eventId: string,
{ senderId, userId, membership, prevMembership }: MembershipEventParams, { senderId, userId, membership, prevMembership }: MembershipEventParams & { userId: string },
): MatrixEvent => { ): MatrixEvent => {
const member = new RoomMember(roomId, userId); const member = new RoomMember(roomId, userId);
// Use localpart as display name; // Use localpart as display name;
member.name = userId.match(/@([^:]*):/)[1]; member.name = userId.match(/@([^:]*):/)![1];
jest.spyOn(member, "getAvatarUrl").mockReturnValue("avatar.jpeg"); jest.spyOn(member, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png"); jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const e = mkMembership({ const e = mkMembership({
@ -89,8 +89,8 @@ describe("EventListSummary", function () {
}; };
// Generate mock MatrixEvents from the array of parameters // Generate mock MatrixEvents from the array of parameters
const generateEvents = (parameters: MembershipEventParams[]) => { const generateEvents = (parameters: Array<MembershipEventParams & { userId: string }>) => {
const res = []; const res: MatrixEvent[] = [];
for (let i = 0; i < parameters.length; i++) { for (let i = 0; i < parameters.length; i++) {
res.push(generateMembershipEvent(`event${i}`, parameters[i])); res.push(generateMembershipEvent(`event${i}`, parameters[i]));
} }
@ -108,7 +108,9 @@ describe("EventListSummary", function () {
events.forEach((e) => { events.forEach((e) => {
e.userId = userId; e.userId = userId;
}); });
eventsForUsers = eventsForUsers.concat(generateEvents(events)); eventsForUsers = eventsForUsers.concat(
generateEvents(events as Array<MembershipEventParams & { userId: string }>),
);
} }
return eventsForUsers; return eventsForUsers;
}; };

View file

@ -29,7 +29,10 @@ describe("<LearnMore />", () => {
}; };
const getComponent = (props = {}) => <LearnMore {...defaultProps} {...props} />; const getComponent = (props = {}) => <LearnMore {...defaultProps} {...props} />;
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined); const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise(() => {}),
close: jest.fn(),
});
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View file

@ -36,7 +36,7 @@ describe("<TooltipTarget />", () => {
// clean up renderer tooltips // clean up renderer tooltips
const wrapper = document.querySelector(".mx_Tooltip_wrapper"); const wrapper = document.querySelector(".mx_Tooltip_wrapper");
while (wrapper?.firstChild) { while (wrapper?.firstChild) {
wrapper.removeChild(wrapper.lastChild); wrapper.removeChild(wrapper.lastChild!);
} }
}); });

View file

@ -50,10 +50,10 @@ describe("MKeyVerificationConclusion", () => {
}) => { }) => {
class MockVerificationRequest extends EventEmitter { class MockVerificationRequest extends EventEmitter {
constructor( constructor(
public readonly pending: boolean, public readonly pending?: boolean,
public readonly cancelled: boolean, public readonly cancelled?: boolean,
public readonly done: boolean, public readonly done?: boolean,
public readonly otherUserId: string, public readonly otherUserId?: string,
) { ) {
super(); super();
} }

View file

@ -140,7 +140,9 @@ describe("MLocationBody", () => {
}); });
it("opens map dialog on click", () => { it("opens map dialog on click", () => {
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined); const modalSpy = jest
.spyOn(Modal, "createDialog")
.mockReturnValue({ finished: new Promise(() => {}), close: jest.fn() });
const component = getComponent(); const component = getComponent();
act(() => { act(() => {

View file

@ -63,7 +63,7 @@ describe("MessageEvent", () => {
event = mkEvent({ event = mkEvent({
event: true, event: true,
type: VoiceBroadcastInfoEventType, type: VoiceBroadcastInfoEventType,
user: client.getUserId(), user: client.getUserId()!,
room: room.roomId, room: room.roomId,
content: { content: {
state: VoiceBroadcastInfoState.Started, state: VoiceBroadcastInfoState.Started,

View file

@ -108,7 +108,7 @@ describe("<PinnedMessagesCard />", () => {
const room = pins.props().room; const room = pins.props().room;
const pinListener = mocked(room.currentState).on.mock.calls.find( const pinListener = mocked(room.currentState).on.mock.calls.find(
([eventName, listener]) => eventName === RoomStateEvent.Events, ([eventName, listener]) => eventName === RoomStateEvent.Events,
)[1]; )![1];
await act(async () => { await act(async () => {
// Emit the update // Emit the update

View file

@ -37,7 +37,7 @@ function generateRoomId() {
describe("MemberList", () => { describe("MemberList", () => {
function createRoom(opts = {}) { function createRoom(opts = {}) {
const room = new Room(generateRoomId(), null, client.getUserId()); const room = new Room(generateRoomId(), client, client.getUserId()!);
if (opts) { if (opts) {
Object.assign(room, opts); Object.assign(room, opts);
} }

View file

@ -370,7 +370,7 @@ describe("MessageComposer", () => {
replyToEvent = mkEvent({ replyToEvent = mkEvent({
event: true, event: true,
type: EventType.RoomMessage, type: EventType.RoomMessage,
user: cli.getUserId(), user: cli.getUserId()!,
content: {}, content: {},
}); });

View file

@ -377,7 +377,7 @@ describe("RoomHeader (React Testing Library)", () => {
content, content,
}); });
room.addLiveEvents([event]); room.addLiveEvents([event]);
return { event_id: event.getId() }; return { event_id: event.getId()! };
}); });
alice = mkRoomMember(room.roomId, "@alice:example.org"); alice = mkRoomMember(room.roomId, "@alice:example.org");

View file

@ -21,7 +21,7 @@ import { Key } from "../../../../src/Keyboard";
import { mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils/platform"; import { mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils/platform";
import { KeyboardKey, KeyboardShortcut } from "../../../../src/components/views/settings/KeyboardShortcut"; import { KeyboardKey, KeyboardShortcut } from "../../../../src/components/views/settings/KeyboardShortcut";
const renderKeyboardShortcut = (Component: React.FunctionComponentFactory<any>, props: Record<string, any>) => { const renderKeyboardShortcut = (Component: React.FunctionComponent<any>, props: Record<string, any>) => {
return render(<Component {...props} />).container; return render(<Component {...props} />).container;
}; };

View file

@ -30,7 +30,6 @@ describe("<DeviceDetails />", () => {
}; };
const defaultProps: ComponentProps<typeof DeviceDetails> = { const defaultProps: ComponentProps<typeof DeviceDetails> = {
device: baseDevice, device: baseDevice,
pusher: null,
isSigningOut: false, isSigningOut: false,
onSignOutDevice: jest.fn(), onSignOutDevice: jest.fn(),
saveDeviceName: jest.fn(), saveDeviceName: jest.fn(),

View file

@ -53,7 +53,7 @@ describe("<SelectableDeviceTile />", () => {
const { container } = render(getComponent({ onSelect })); const { container } = render(getComponent({ onSelect }));
act(() => { act(() => {
fireEvent.click(container.querySelector(`#device-tile-checkbox-${device.device_id}`)); fireEvent.click(container.querySelector(`#device-tile-checkbox-${device.device_id}`)!);
}); });
expect(onSelect).toHaveBeenCalled(); expect(onSelect).toHaveBeenCalled();

View file

@ -92,7 +92,7 @@ describe("deleteDevices()", () => {
// opened modal // opened modal
expect(modalSpy).toHaveBeenCalled(); expect(modalSpy).toHaveBeenCalled();
const [, { title, authData, aestheticsForStagePhases }] = modalSpy.mock.calls[0]; const { title, authData, aestheticsForStagePhases } = modalSpy.mock.calls[0][1]!;
// modal opened as expected // modal opened as expected
expect(title).toEqual("Authentication"); expect(title).toEqual("Authentication");

View file

@ -21,7 +21,7 @@ import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning"; import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { sleep } from "matrix-js-sdk/src/utils"; import { defer, sleep } from "matrix-js-sdk/src/utils";
import { import {
ClientEvent, ClientEvent,
IMyDevice, IMyDevice,
@ -927,12 +927,9 @@ describe("<SessionManagerTab />", () => {
// get a handle for resolving the delete call // get a handle for resolving the delete call
// because promise flushing after the confirm modal is resolving this too // because promise flushing after the confirm modal is resolving this too
// and we want to test the loading state here // and we want to test the loading state here
let resolveDeleteRequest: (v?: IAuthData) => void; const resolveDeleteRequest = defer<IAuthData>();
mockClient.deleteMultipleDevices.mockImplementation(() => { mockClient.deleteMultipleDevices.mockImplementation(() => {
const promise = new Promise<IAuthData>((resolve) => { return resolveDeleteRequest.promise;
resolveDeleteRequest = resolve;
});
return promise;
}); });
const { getByTestId } = render(getComponent()); const { getByTestId } = render(getComponent());
@ -972,7 +969,7 @@ describe("<SessionManagerTab />", () => {
undefined, undefined,
); );
resolveDeleteRequest?.(); resolveDeleteRequest.resolve({});
}); });
it("signs out of all other devices from other sessions context menu", async () => { it("signs out of all other devices from other sessions context menu", async () => {

View file

@ -54,7 +54,7 @@ function textMessageReply(body: string, msgtype = "m.text") {
function mergeAdjacentParts(parts: Part[]) { function mergeAdjacentParts(parts: Part[]) {
let prevPart: Part | undefined; let prevPart: Part | undefined;
for (let i = 0; i < parts.length; ++i) { for (let i = 0; i < parts.length; ++i) {
let part = parts[i]; let part: Part | undefined = parts[i];
const isEmpty = !part.text.length; const isEmpty = !part.text.length;
const isMerged = !isEmpty && prevPart && prevPart.merge?.(part); const isMerged = !isEmpty && prevPart && prevPart.merge?.(part);
if (isEmpty || isMerged) { if (isEmpty || isMerged) {

View file

@ -38,7 +38,7 @@ async function md2html(markdown: string): Promise<string> {
const pc = createPartCreator(); const pc = createPartCreator();
const oldModel = new EditorModel([], pc, () => {}); const oldModel = new EditorModel([], pc, () => {});
await oldModel.update(markdown, "insertText", new DocumentOffset(markdown.length, false)); await oldModel.update(markdown, "insertText", new DocumentOffset(markdown.length, false));
return htmlSerializeIfNeeded(oldModel, { forceHTML: true }); return htmlSerializeIfNeeded(oldModel, { forceHTML: true })!;
} }
function html2md(html: string): string { function html2md(html: string): string {

View file

@ -143,7 +143,7 @@ describe("useProfileInfo", () => {
}); });
it("should be able to handle an empty result", async () => { it("should be able to handle an empty result", async () => {
cli.getProfileInfo = () => null; cli.getProfileInfo = () => null as unknown as Promise<{}>;
const query = "@user:home.server"; const query = "@user:home.server";
const wrapper = mount( const wrapper = mount(

View file

@ -107,7 +107,7 @@ const setUpClientRoomAndStores = (): {
content, content,
}); });
room.addLiveEvents([event]); room.addLiveEvents([event]);
return { event_id: event.getId() }; return { event_id: event.getId()! };
}); });
setupAsyncStoreWithClient(WidgetStore.instance, client); setupAsyncStoreWithClient(WidgetStore.instance, client);

View file

@ -20,7 +20,7 @@ import { AppModule } from "../../src/modules/AppModule";
describe("AppModule", () => { describe("AppModule", () => {
describe("constructor", () => { describe("constructor", () => {
it("should call the factory immediately", () => { it("should call the factory immediately", () => {
let module: MockModule; let module: MockModule | undefined;
const appModule = new AppModule((api) => { const appModule = new AppModule((api) => {
if (module) { if (module) {
throw new Error("State machine error: Factory called twice"); throw new Error("State machine error: Factory called twice");

View file

@ -30,7 +30,7 @@ export class MockModule extends RuntimeModule {
} }
export function registerMockModule(): MockModule { export function registerMockModule(): MockModule {
let module: MockModule; let module: MockModule | undefined;
ModuleRunner.instance.registerModule((api) => { ModuleRunner.instance.registerModule((api) => {
if (module) { if (module) {
throw new Error("State machine error: ModuleRunner created the module twice"); throw new Error("State machine error: ModuleRunner created the module twice");

View file

@ -800,7 +800,7 @@ describe("SpaceStore", () => {
testUtils.mockStateEventImplementation([childEvent]), testUtils.mockStateEventImplementation([childEvent]),
); );
client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, undefined); client.emit(RoomStateEvent.Events, childEvent, spaceRoom.currentState, null);
}; };
const addMember = (spaceId: string, user: RoomMember) => { const addMember = (spaceId: string, user: RoomMember) => {
@ -964,7 +964,7 @@ describe("SpaceStore", () => {
} }
}); });
client.emit(RoomStateEvent.Members, event, null, null); client.emit(RoomStateEvent.Members, event, space.currentState, dm1Partner);
return deferred.resolve(true) as unknown as Promise<boolean>; return deferred.resolve(true) as unknown as Promise<boolean>;
}); });
@ -1279,7 +1279,7 @@ describe("SpaceStore", () => {
user: dm1Partner.userId, user: dm1Partner.userId,
room: space1, room: space1,
}); });
client.emit(RoomStateEvent.Members, memberEvent, undefined, undefined); client.emit(RoomStateEvent.Members, memberEvent, rootSpace.currentState, dm1Partner);
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy(); expect(SpaceStore.instance.getSpaceFilteredUserIds(space1).has(dm1Partner.userId)).toBeTruthy();
const dm1Room = mkRoom(dm1); const dm1Room = mkRoom(dm1);

View file

@ -21,7 +21,7 @@ import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import { flushPromises } from "../test-utils"; import { flushPromises } from "../test-utils";
import { VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording"; import { VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording";
const stubClient = {} as undefined as MatrixClient; const stubClient = {} as unknown as MatrixClient;
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(stubClient); jest.spyOn(MatrixClientPeg, "get").mockReturnValue(stubClient);
describe("VoiceRecordingStore", () => { describe("VoiceRecordingStore", () => {

View file

@ -216,7 +216,7 @@ describe("RoomListStore", () => {
// But when we update the feature flag // But when we update the feature flag
featureFlagValue = true; featureFlagValue = true;
watchCallback( watchCallback!(
"feature_dynamic_room_predecessors", "feature_dynamic_room_predecessors",
"", "",
SettingLevel.DEFAULT, SettingLevel.DEFAULT,

View file

@ -68,6 +68,7 @@ describe("RecentAlgorithm", () => {
it("returns a fake ts for rooms without a timeline", () => { it("returns a fake ts for rooms without a timeline", () => {
const room = mkRoom(cli, "!new:example.org"); const room = mkRoom(cli, "!new:example.org");
// @ts-ignore
room.timeline = undefined; room.timeline = undefined;
expect(algorithm.getLastTs(room, "@john:matrix.org")).toBe(Number.MAX_SAFE_INTEGER); expect(algorithm.getLastTs(room, "@john:matrix.org")).toBe(Number.MAX_SAFE_INTEGER);
}); });

View file

@ -196,7 +196,7 @@ describe("geolocation utilities", () => {
// suppress expected errors from test log // suppress expected errors from test log
jest.spyOn(logger, "error").mockImplementation(() => {}); jest.spyOn(logger, "error").mockImplementation(() => {});
geolocation.watchPosition.mockImplementation((_callback, error) => { geolocation.watchPosition.mockImplementation((_callback, error) => {
error(getMockGeolocationPositionError(1, "message")); error!(getMockGeolocationPositionError(1, "message"));
return -1; return -1;
}); });
const positionHandler = jest.fn(); const positionHandler = jest.fn();
@ -224,7 +224,7 @@ describe("geolocation utilities", () => {
jest.spyOn(logger, "error").mockImplementation(() => {}); jest.spyOn(logger, "error").mockImplementation(() => {});
const timeoutError = getMockGeolocationPositionError(3, "message"); const timeoutError = getMockGeolocationPositionError(3, "message");
geolocation.getCurrentPosition.mockImplementation((callback, error) => error(timeoutError)); geolocation.getCurrentPosition.mockImplementation((callback, error) => error!(timeoutError));
await expect(() => getCurrentPosition()).rejects.toThrow(GeolocationError.Timeout); await expect(() => getCurrentPosition()).rejects.toThrow(GeolocationError.Timeout);
}); });

View file

@ -135,7 +135,7 @@ describe("VoiceBroadcastRecording", () => {
event_id: infoEvent.getId(), event_id: infoEvent.getId(),
}, },
} as VoiceBroadcastInfoEventContent, } as VoiceBroadcastInfoEventContent,
client.getUserId(), client.getUserId()!,
); );
}); });
}; };

View file

@ -49,14 +49,14 @@ describe("VoiceBroadcastRecordingsStore", () => {
infoEvent = mkVoiceBroadcastInfoStateEvent( infoEvent = mkVoiceBroadcastInfoStateEvent(
roomId, roomId,
VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Started,
client.getUserId(), client.getUserId()!,
client.getDeviceId(), client.getDeviceId()!,
); );
otherInfoEvent = mkVoiceBroadcastInfoStateEvent( otherInfoEvent = mkVoiceBroadcastInfoStateEvent(
roomId, roomId,
VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Started,
client.getUserId(), client.getUserId()!,
client.getDeviceId(), client.getDeviceId()!,
); );
recording = new VoiceBroadcastRecording(infoEvent, client); recording = new VoiceBroadcastRecording(infoEvent, client);
otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client); otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client);

View file

@ -54,7 +54,7 @@ describe("VoiceBroadcastResumer", () => {
event_id: startedInfoEvent.getId(), event_id: startedInfoEvent.getId(),
}, },
} as VoiceBroadcastInfoEventContent, } as VoiceBroadcastInfoEventContent,
client.getUserId(), client.getUserId()!,
); );
}); });
}; };

View file

@ -161,7 +161,7 @@ describe("startNewVoiceBroadcastRecording", () => {
device_id: client.getDeviceId(), device_id: client.getDeviceId(),
state: VoiceBroadcastInfoState.Started, state: VoiceBroadcastInfoState.Started,
}, },
client.getUserId(), client.getUserId()!,
); );
expect(recording!.infoEvent).toBe(infoEvent); expect(recording!.infoEvent).toBe(infoEvent);
expect(recording!.start).toHaveBeenCalled(); expect(recording!.start).toHaveBeenCalled();