Conform more code to strictNullChecks (#10444

* Conform more code to `strictNullChecks`

* Fix tests

* Fix tests
This commit is contained in:
Michael Telatynski 2023-03-27 08:01:09 +01:00 committed by GitHub
parent ba2608ec74
commit c225b8ec29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 85 additions and 75 deletions

View file

@ -24,8 +24,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils"; import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
import { ListenerMap } from "matrix-js-sdk/src/models/typed-event-emitter";
import shouldHideEvent from "../../shouldHideEvent"; import shouldHideEvent from "../../shouldHideEvent";
import { wantsDateSeparator } from "../../DateUtils"; import { wantsDateSeparator } from "../../DateUtils";
@ -543,7 +541,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return null; return null;
} }
private collectGhostReadMarker = (node: HTMLElement): void => { private collectGhostReadMarker = (node: HTMLElement | null): void => {
if (node) { if (node) {
// now the element has appeared, change the style which will trigger the CSS transition // now the element has appeared, change the style which will trigger the CSS transition
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -788,13 +786,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
continuation={continuation} continuation={continuation}
isRedacted={mxEv.isRedacted()} isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()} replacingEventId={mxEv.replacingEventId()}
editState={isEditing && this.props.editState} editState={isEditing ? this.props.editState : undefined}
onHeightChanged={this.onHeightChanged} onHeightChanged={this.onHeightChanged}
readReceipts={readReceipts} readReceipts={readReceipts}
readReceiptMap={this.readReceiptMap} readReceiptMap={this.readReceiptMap}
showUrlPreview={this.props.showUrlPreview} showUrlPreview={this.props.showUrlPreview}
checkUnmounting={this.isUnmounting} checkUnmounting={this.isUnmounting}
eventSendStatus={mxEv.getAssociatedStatus()} eventSendStatus={mxEv.getAssociatedStatus() ?? undefined}
isTwelveHour={this.props.isTwelveHour} isTwelveHour={this.props.isTwelveHour}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
last={last} last={last}
@ -836,9 +834,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return null; return null;
} }
const receiptDestination: ReadReceipt<string, ListenerMap<string>> = this.context.threadId const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room;
? room.getThread(this.context.threadId)
: room;
const receipts: IReadReceiptProps[] = []; const receipts: IReadReceiptProps[] = [];
@ -1215,7 +1211,7 @@ class CreationGrouper extends BaseGrouper {
let summaryText: string; let summaryText: string;
const roomId = ev.getRoomId(); const roomId = ev.getRoomId();
const creator = ev.sender ? ev.sender.name : ev.getSender(); const creator = ev.sender?.name ?? ev.getSender();
if (DMRoomMap.shared().getUserIdForRoomId(roomId)) { if (DMRoomMap.shared().getUserIdForRoomId(roomId)) {
summaryText = _t("%(creator)s created this DM.", { creator }); summaryText = _t("%(creator)s created this DM.", { creator });
} else { } else {

View file

@ -377,7 +377,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private roomView = createRef<HTMLElement>(); private roomView = createRef<HTMLElement>();
private searchResultsPanel = createRef<ScrollPanel>(); private searchResultsPanel = createRef<ScrollPanel>();
private messagePanel?: TimelinePanel; private messagePanel: TimelinePanel | null = null;
private roomViewBody = createRef<HTMLDivElement>(); private roomViewBody = createRef<HTMLDivElement>();
public static contextType = SDKContext; public static contextType = SDKContext;
@ -611,11 +611,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const newState: Partial<IRoomState> = { const newState: Partial<IRoomState> = {
roomId: roomId ?? undefined, roomId: roomId ?? undefined,
roomAlias: this.context.roomViewStore.getRoomAlias(), roomAlias: this.context.roomViewStore.getRoomAlias() ?? undefined,
roomLoading: this.context.roomViewStore.isRoomLoading(), roomLoading: this.context.roomViewStore.isRoomLoading(),
roomLoadError: this.context.roomViewStore.getRoomLoadError(), roomLoadError: this.context.roomViewStore.getRoomLoadError() ?? undefined,
joining: this.context.roomViewStore.isJoining(), joining: this.context.roomViewStore.isJoining(),
replyToEvent: this.context.roomViewStore.getQuotingEvent(), replyToEvent: this.context.roomViewStore.getQuotingEvent() ?? undefined,
// we should only peek once we have a ready client // we should only peek once we have a ready client
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(), shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
@ -654,7 +654,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// and the root event. // and the root event.
// The rest will be lost for now, until the aggregation API on the server // The rest will be lost for now, until the aggregation API on the server
// becomes available to fetch a whole thread // becomes available to fetch a whole thread
if (!initialEvent) { if (!initialEvent && this.context.client) {
initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined; initialEvent = (await fetchInitialEvent(this.context.client, roomId, initialEventId)) ?? undefined;
} }
@ -848,7 +848,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
isPeeking: true, // this will change to false if peeking fails isPeeking: true, // this will change to false if peeking fails
}); });
this.context.client this.context.client
.peekInRoom(roomId) ?.peekInRoom(roomId)
.then((room) => { .then((room) => {
if (this.unmounted) { if (this.unmounted) {
return; return;
@ -883,7 +883,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
} else if (room) { } else if (room) {
// Stop peeking because we have joined this room previously // Stop peeking because we have joined this room previously
this.context.client.stopPeeking(); this.context.client?.stopPeeking();
this.setState({ isPeeking: false }); this.setState({ isPeeking: false });
} }
} }
@ -909,9 +909,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.onRoomViewStoreUpdate(true); this.onRoomViewStoreUpdate(true);
const call = this.getCallForRoom(); const call = this.getCallForRoom();
const callState = call ? call.state : null; const callState = call?.state;
this.setState({ this.setState({
callState: callState, callState,
}); });
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState); this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
@ -959,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
if (this.state.shouldPeek) { if (this.state.shouldPeek) {
this.context.client.stopPeeking(); this.context.client?.stopPeeking();
} }
// stop tracking room changes to format permalinks // stop tracking room changes to format permalinks
@ -1010,7 +1010,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (this.viewsLocalRoom) { if (this.viewsLocalRoom) {
// clean up if this was a local room // clean up if this was a local room
this.context.client.store.removeRoom(this.state.room.roomId); this.context.client?.store.removeRoom(this.state.room.roomId);
} }
} }
@ -1469,7 +1469,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private updatePermissions(room: Room): void { private updatePermissions(room: Room): void {
if (room) { if (room) {
const me = this.context.client.getUserId(); const me = this.context.client.getSafeUserId();
const canReact = const canReact =
room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me); room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me);
const canSendMessages = room.maySendMessage(); const canSendMessages = room.maySendMessage();
@ -1866,7 +1866,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update. // otherwise react calls it with null on each update.
private gatherTimelinePanelRef = (r?: TimelinePanel): void => { private gatherTimelinePanelRef = (r: TimelinePanel | null): void => {
this.messagePanel = r; this.messagePanel = r;
}; };

View file

@ -171,7 +171,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
if (payload.event && !payload.event.getThread()) return; if (payload.event && !payload.event.getThread()) return;
this.setState( this.setState(
{ {
editState: payload.event ? new EditorStateTransfer(payload.event) : null, editState: payload.event ? new EditorStateTransfer(payload.event) : undefined,
}, },
() => { () => {
if (payload.event) { if (payload.event) {
@ -213,9 +213,11 @@ export default class ThreadView extends React.Component<IProps, IState> {
}; };
private get threadLastReply(): MatrixEvent | undefined { private get threadLastReply(): MatrixEvent | undefined {
return this.state.thread?.lastReply((ev: MatrixEvent) => { return (
this.state.thread?.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;
}); }) ?? undefined
);
} }
private updateThread = (thread?: Thread): void => { private updateThread = (thread?: Thread): void => {
@ -245,7 +247,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void { private setupThreadListeners(thread?: Thread | undefined, oldThread?: Thread | undefined): void {
if (oldThread) { if (oldThread) {
this.state.thread.off(ThreadEvent.NewReply, this.updateThreadRelation); this.state.thread?.off(ThreadEvent.NewReply, this.updateThreadRelation);
this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation); this.props.room.off(RoomEvent.LocalEchoUpdated, this.updateThreadRelation);
} }
if (thread) { if (thread) {
@ -344,7 +346,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null; const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : undefined;
const threadRelation = this.threadRelation; const threadRelation = this.threadRelation;

View file

@ -79,7 +79,7 @@ interface IProps {
// representing. This may or may not have a room, depending on what it's // representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for // a timeline representing. If it has a room, we maintain RRs etc for
// that room. // that room.
timelineSet?: EventTimelineSet; timelineSet: EventTimelineSet;
// overlay events from a second timelineset on the main timeline // overlay events from a second timelineset on the main timeline
// added to support virtual rooms // added to support virtual rooms
// events from the overlay timeline set will be added by localTimestamp // events from the overlay timeline set will be added by localTimestamp

View file

@ -33,6 +33,7 @@ interface IProps {
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => { const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
const info = SettingsStore.getBetaInfo(featureId); const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null;
return ( return (
<GenericFeatureFeedbackDialog <GenericFeatureFeedbackDialog

View file

@ -42,7 +42,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
const { matrixClient: cli, room, member, onFinished } = props; const { matrixClient: cli, room, member, onFinished } = props;
const [keepStateEvents, setKeepStateEvents] = useState(true); const [keepStateEvents, setKeepStateEvents] = useState(true);
let timeline = room.getLiveTimeline(); let timeline: EventTimeline | null = room.getLiveTimeline();
let eventsToRedact: MatrixEvent[] = []; let eventsToRedact: MatrixEvent[] = [];
while (timeline) { while (timeline) {
eventsToRedact = [ eventsToRedact = [
@ -93,7 +93,7 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
await Promise.all( await Promise.all(
eventsToRedact.reverse().map(async (event): Promise<void> => { eventsToRedact.reverse().map(async (event): Promise<void> => {
try { try {
await cli.redactEvent(room.roomId, event.getId()); await cli.redactEvent(room.roomId, event.getId()!);
} catch (err) { } catch (err) {
// log and swallow errors // log and swallow errors
logger.error("Could not redact", event.getId()); logger.error("Could not redact", event.getId());

View file

@ -71,7 +71,7 @@ interface IProps {
type ToolInfo = [label: string, tool: Tool]; type ToolInfo = [label: string, tool: Tool];
const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => { const DevtoolsDialog: React.FC<IProps> = ({ roomId, onFinished }) => {
const [tool, setTool] = useState<ToolInfo>(null); const [tool, setTool] = useState<ToolInfo | null>(null);
let body: JSX.Element; let body: JSX.Element;
let onBack: () => void; let onBack: () => void;

View file

@ -51,6 +51,7 @@ import { ButtonEvent } from "../elements/AccessibleButton";
import { isLocationEvent } from "../../../utils/EventUtils"; import { isLocationEvent } from "../../../utils/EventUtils";
import { isSelfLocation, locationEventGeoUri } from "../../../utils/location"; import { isSelfLocation, locationEventGeoUri } from "../../../utils/location";
import { RoomContextDetails } from "../rooms/RoomContextDetails"; import { RoomContextDetails } from "../rooms/RoomContextDetails";
import { filterBoolean } from "../../../utils/arrays";
const AVATAR_SIZE = 30; const AVATAR_SIZE = 30;
@ -194,7 +195,7 @@ const transformEvent = (event: MatrixEvent): { type: string; content: IContent }
}; };
const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => { const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
const userId = cli.getUserId(); const userId = cli.getSafeUserId();
const [profileInfo, setProfileInfo] = useState<any>({}); const [profileInfo, setProfileInfo] = useState<any>({});
useEffect(() => { useEffect(() => {
cli.getProfileInfo(userId).then((info) => setProfileInfo(info)); cli.getProfileInfo(userId).then((info) => setProfileInfo(info));
@ -242,7 +243,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
if (lcQuery) { if (lcQuery) {
rooms = new QueryMatcher<Room>(rooms, { rooms = new QueryMatcher<Room>(rooms, {
keys: ["name"], keys: ["name"],
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)], funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])],
shouldMatchWordsOnly: false, shouldMatchWordsOnly: false,
}).match(lcQuery); }).match(lcQuery);
} }

View file

@ -34,6 +34,7 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
const onSubmit = async (e: SyntheticEvent): Promise<void> => { const onSubmit = async (e: SyntheticEvent): Promise<void> => {
e.preventDefault(); e.preventDefault();
if (!fieldRef.current) return;
if (email) { if (email) {
const valid = await fieldRef.current.validate({}); const valid = await fieldRef.current.validate({});

View file

@ -141,7 +141,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
return !error; return !error;
}, },
invalid: function ({ error }) { invalid: function ({ error }) {
return error; return error ?? null;
}, },
}, },
], ],

View file

@ -49,7 +49,7 @@ interface ITermsDialogProps {
/** /**
* urls that the user has already agreed to * urls that the user has already agreed to
*/ */
agreedUrls?: string[]; agreedUrls: string[];
/** /**
* Called with: * Called with:
@ -127,7 +127,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const rows = []; const rows: JSX.Element[] = [];
for (const policiesAndService of this.props.policiesAndServicePairs) { for (const policiesAndService of this.props.policiesAndServicePairs) {
const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl); const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl);
@ -135,8 +135,8 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
for (let i = 0; i < policyValues.length; ++i) { for (let i = 0; i < policyValues.length; ++i) {
const termDoc = policyValues[i]; const termDoc = policyValues[i];
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version")); const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== "version"));
let serviceName; let serviceName: JSX.Element | undefined;
let summary; let summary: JSX.Element | undefined;
if (i === 0) { if (i === 0) {
serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host); serviceName = this.nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
summary = this.summaryForServiceType(policiesAndService.service.serviceType); summary = this.summaryForServiceType(policiesAndService.service.serviceType);

View file

@ -100,7 +100,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
}; };
private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.props.validator(fieldState); const result = await this.props.validator!(fieldState);
this.setState({ this.setState({
valid: !!result.valid, valid: !!result.valid,
}); });

View file

@ -38,7 +38,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null); const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null);
const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null); const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null);
let generalMenu: JSX.Element; let generalMenu: JSX.Element | undefined;
if (generalMenuPosition !== null) { if (generalMenuPosition !== null) {
if (room.isSpaceRoom()) { if (room.isSpaceRoom()) {
generalMenu = ( generalMenu = (
@ -59,7 +59,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
} }
} }
let notificationMenu: JSX.Element; let notificationMenu: JSX.Element | undefined;
if (notificationMenuPosition !== null) { if (notificationMenuPosition !== null) {
notificationMenu = ( notificationMenu = (
<RoomNotificationContextMenu <RoomNotificationContextMenu

View file

@ -440,7 +440,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
// Sort results by most recent activity // Sort results by most recent activity
const myUserId = cli.getUserId(); const myUserId = cli.getSafeUserId();
for (const resultArray of Object.values(results)) { for (const resultArray of Object.values(results)) {
resultArray.sort((a: Result, b: Result) => { resultArray.sort((a: Result, b: Result) => {
if (isRoomResult(a) || isRoomResult(b)) { if (isRoomResult(a) || isRoomResult(b)) {

View file

@ -27,17 +27,17 @@ enum Phases {
interface IProps { interface IProps {
onValueChanged?: (value: string, shouldSubmit: boolean) => void; onValueChanged?: (value: string, shouldSubmit: boolean) => void;
initialValue?: string; initialValue: string;
label?: string; label: string;
placeholder?: string; placeholder: string;
className?: string; className: string;
labelClassName?: string; labelClassName?: string;
placeholderClassName?: string; placeholderClassName: string;
// Overrides blurToSubmit if true // Overrides blurToSubmit if true
blurToCancel?: boolean; blurToCancel?: boolean;
// Will cause onValueChanged(value, true) to fire on blur // Will cause onValueChanged(value, true) to fire on blur
blurToSubmit?: boolean; blurToSubmit: boolean;
editable?: boolean; editable: boolean;
} }
interface IState { interface IState {
@ -109,11 +109,11 @@ export default class EditableText extends React.Component<IProps, IState> {
this.value = this.props.initialValue; this.value = this.props.initialValue;
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
this.onValueChanged(false); this.onValueChanged(false);
this.editableDiv.current.blur(); this.editableDiv.current?.blur();
}; };
private onValueChanged = (shouldSubmit: boolean): void => { private onValueChanged = (shouldSubmit: boolean): void => {
this.props.onValueChanged(this.value, shouldSubmit); this.props.onValueChanged?.(this.value, shouldSubmit);
}; };
private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => { private onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>): void => {
@ -171,7 +171,7 @@ export default class EditableText extends React.Component<IProps, IState> {
private onFinish = ( private onFinish = (
ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>, ev: React.KeyboardEvent<HTMLDivElement> | React.FocusEvent<HTMLDivElement>,
shouldSubmit?: boolean, shouldSubmit = false,
): void => { ): void => {
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this; const self = this;

View file

@ -187,8 +187,8 @@ export default class EventListSummary extends React.Component<IProps> {
let transition = t; let transition = t;
if (i < transitions.length - 1 && modMap[t] && modMap[t].after === t2) { if (i < transitions.length - 1 && modMap[t] && modMap[t]!.after === t2) {
transition = modMap[t].newTransition; transition = modMap[t]!.newTransition;
i++; i++;
} }
@ -380,7 +380,7 @@ export default class EventListSummary extends React.Component<IProps> {
return res ?? null; return res ?? null;
} }
private static getTransitionSequence(events: IUserEvents[]): TransitionType[] { private static getTransitionSequence(events: IUserEvents[]): Array<TransitionType | null> {
return events.map(EventListSummary.getTransition); return events.map(EventListSummary.getTransition);
} }

View file

@ -148,6 +148,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> { private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> {
try { try {
const inReplyToEventId = getParentEventId(ev); const inReplyToEventId = getParentEventId(ev);
if (!inReplyToEventId) return null;
return await this.getEvent(inReplyToEventId); return await this.getEvent(inReplyToEventId);
} catch (e) { } catch (e) {
return null; return null;
@ -196,7 +197,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
}; };
private getReplyChainColorClass(ev: MatrixEvent): string { private getReplyChainColorClass(ev: MatrixEvent): string {
return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyChain"); return getUserNameColorClass(ev.getSender()!).replace("Username", "ReplyChain");
} }
public render(): React.ReactNode { public render(): React.ReactNode {
@ -231,8 +232,8 @@ export default class ReplyChain extends React.Component<IProps, IState> {
pill: ( pill: (
<Pill <Pill
type={PillType.UserMention} type={PillType.UserMention}
room={room} room={room ?? undefined}
url={makeUserPermalink(ev.getSender())} url={makeUserPermalink(ev.getSender()!)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")} shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/> />
), ),

View file

@ -63,7 +63,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
const event = this.props.mxEvent; const event = this.props.mxEvent;
const room = cli.getRoom(event.getRoomId()); const room = cli.getRoom(event.getRoomId());
event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged); event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
const canRedact = room.currentState.maySendRedactionForEvent(event, userId); const canRedact = room?.currentState.maySendRedactionForEvent(event, userId) ?? false;
this.state = { canRedact, sendStatus: event.getAssociatedStatus() }; this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
} }

View file

@ -71,7 +71,7 @@ export const usePinnedEvents = (room?: Room): string[] => {
}; };
function getReadPinnedEventIds(room?: Room): Set<string> { function getReadPinnedEventIds(room?: Room): Set<string> {
return new Set(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []);
} }
export const useReadPinnedEvents = (room?: Room): Set<string> => { export const useReadPinnedEvents = (room?: Room): Set<string> => {

View file

@ -448,7 +448,7 @@ export const UserOptionsSection: React.FC<{
const inviter = new MultiInviter(roomId || ""); const inviter = new MultiInviter(roomId || "");
await inviter.invite([member.userId]).then(() => { await inviter.invite([member.userId]).then(() => {
if (inviter.getCompletionState(member.userId) !== "invited") { if (inviter.getCompletionState(member.userId) !== "invited") {
throw new Error(inviter.getErrorText(member.userId)); throw new Error(inviter.getErrorText(member.userId) ?? undefined);
} }
}); });
} catch (err) { } catch (err) {
@ -766,8 +766,8 @@ export const BanToggleButton = ({
const myMember = child.getMember(cli.credentials.userId || ""); const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId); const theirMember = child.getMember(member.userId);
return ( return (
myMember && !!myMember &&
theirMember && !!theirMember &&
theirMember.membership !== "ban" && theirMember.membership !== "ban" &&
myMember.powerLevel > theirMember.powerLevel && myMember.powerLevel > theirMember.powerLevel &&
child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel) child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel)

View file

@ -25,7 +25,7 @@ import { OverflowMenuContext } from "./MessageComposerButtons";
interface IEmojiButtonProps { interface IEmojiButtonProps {
addEmoji: (unicode: string) => boolean; addEmoji: (unicode: string) => boolean;
menuPosition: MenuProps; menuPosition?: MenuProps;
className?: string; className?: string;
} }

View file

@ -73,8 +73,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
return null; return null;
} }
let mainButtons: ReactElement[]; let mainButtons: ReactNode[];
let moreButtons: ReactElement[]; let moreButtons: ReactNode[];
if (narrow) { if (narrow) {
mainButtons = [ mainButtons = [
isWysiwygLabEnabled ? ( isWysiwygLabEnabled ? (

View file

@ -75,8 +75,8 @@ export const usePermalinkEvent = (
const fetchRoomEvent = async (): Promise<void> => { const fetchRoomEvent = async (): Promise<void> => {
try { try {
const eventData = await MatrixClientPeg.get().fetchRoomEvent( const eventData = await MatrixClientPeg.get().fetchRoomEvent(
parseResult.roomIdOrAlias, parseResult.roomIdOrAlias!,
parseResult.eventId, parseResult.eventId!,
); );
setEvent(new MatrixEvent(eventData)); setEvent(new MatrixEvent(eventData));
} catch {} } catch {}

View file

@ -83,7 +83,7 @@ const findRoom = (roomIdOrAlias: string): Room | null => {
export const usePermalinkTargetRoom = ( export const usePermalinkTargetRoom = (
type: PillType | null, type: PillType | null,
parseResult: PermalinkParts | null, parseResult: PermalinkParts | null,
permalinkRoom: Room | null, permalinkRoom: Room | undefined,
): Room | null => { ): Room | null => {
// The listed permalink types require a room. // The listed permalink types require a room.
// If it cannot be initially determined, it will be looked up later by a memo hook. // If it cannot be initially determined, it will be looked up later by a memo hook.

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { Optional } from "matrix-events-sdk";
import { _t } from "../languageHandler"; import { _t } from "../languageHandler";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
@ -75,7 +74,7 @@ const onLearnMorePreviouslyOptedIn = (): void => {
const TOAST_KEY = "analytics"; const TOAST_KEY = "analytics";
export function getPolicyUrl(): Optional<string> { export function getPolicyUrl(): string | undefined {
return SdkConfig.get("privacy_policy_url"); return SdkConfig.get("privacy_policy_url");
} }

View file

@ -22,7 +22,10 @@ import { Member } from "./direct-messages";
import DMRoomMap from "./DMRoomMap"; import DMRoomMap from "./DMRoomMap";
export const compareMembers = export const compareMembers =
(activityScores: Record<string, IActivityScore>, memberScores: Record<string, IMemberScore>) => (
activityScores: Record<string, IActivityScore | undefined>,
memberScores: Record<string, IMemberScore | undefined>,
) =>
(a: Member | RoomMember, b: Member | RoomMember): number => { (a: Member | RoomMember, b: Member | RoomMember): number => {
const aActivityScore = activityScores[a.userId]?.score ?? 0; const aActivityScore = activityScores[a.userId]?.score ?? 0;
const aMemberScore = memberScores[a.userId]?.score ?? 0; const aMemberScore = memberScores[a.userId]?.score ?? 0;

View file

@ -716,7 +716,7 @@ describe("MessagePanel", function () {
// Increase the length of the loop here to test performance issues with // Increase the length of the loop here to test performance issues with
// rendering // rendering
const events = []; const events: MatrixEvent[] = [];
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
events.push( events.push(
TestUtilsMatrix.mkMembership({ TestUtilsMatrix.mkMembership({

View file

@ -54,6 +54,7 @@ describe("ForwardDialog", () => {
}); });
const mockClient = getMockClientWithEventEmitter({ const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId), getUserId: jest.fn().mockReturnValue(aliceId),
getSafeUserId: jest.fn().mockReturnValue(aliceId),
isGuest: jest.fn().mockReturnValue(false), isGuest: jest.fn().mockReturnValue(false),
getVisibleRooms: jest.fn().mockReturnValue([]), getVisibleRooms: jest.fn().mockReturnValue([]),
getRoom: jest.fn(), getRoom: jest.fn(),
@ -92,6 +93,7 @@ describe("ForwardDialog", () => {
DMRoomMap.makeShared(); DMRoomMap.makeShared();
jest.clearAllMocks(); jest.clearAllMocks();
mockClient.getUserId.mockReturnValue("@bob:example.org"); mockClient.getUserId.mockReturnValue("@bob:example.org");
mockClient.getSafeUserId.mockReturnValue("@bob:example.org");
mockClient.sendEvent.mockReset(); mockClient.sendEvent.mockReset();
}); });

View file

@ -112,6 +112,7 @@ const mockClient = mocked({
setIgnoredUsers: jest.fn(), setIgnoredUsers: jest.fn(),
isCryptoEnabled: jest.fn(), isCryptoEnabled: jest.fn(),
getUserId: jest.fn(), getUserId: jest.fn(),
getSafeUserId: jest.fn(),
on: jest.fn(), on: jest.fn(),
off: jest.fn(), off: jest.fn(),
isSynapseAdministrator: jest.fn().mockResolvedValue(false), isSynapseAdministrator: jest.fn().mockResolvedValue(false),
@ -348,6 +349,7 @@ describe("<DeviceItem />", () => {
}); });
it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => { it("when userId is the same as userId from client, uses isCrossSigningVerified to determine if button is shown", () => {
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
mockClient.getUserId.mockReturnValueOnce(defaultUserId); mockClient.getUserId.mockReturnValueOnce(defaultUserId);
renderComponent(); renderComponent();
@ -431,6 +433,7 @@ describe("<UserOptionsSection />", () => {
}); });
it("does not show ignore or direct message buttons when member userId matches client userId", () => { it("does not show ignore or direct message buttons when member userId matches client userId", () => {
mockClient.getSafeUserId.mockReturnValueOnce(member.userId);
mockClient.getUserId.mockReturnValueOnce(member.userId); mockClient.getUserId.mockReturnValueOnce(member.userId);
renderComponent(); renderComponent();
@ -676,6 +679,7 @@ describe("<PowerLevelEditor />", () => {
content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 }, content: { users: { [defaultUserId]: startPowerLevel }, users_default: 1 },
}); });
mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent); mockRoom.currentState.getStateEvents.mockReturnValue(powerLevelEvent);
mockClient.getSafeUserId.mockReturnValueOnce(defaultUserId);
mockClient.getUserId.mockReturnValueOnce(defaultUserId); mockClient.getUserId.mockReturnValueOnce(defaultUserId);
mockClient.setPowerLevel.mockResolvedValueOnce({ event_id: "123" }); mockClient.setPowerLevel.mockResolvedValueOnce({ event_id: "123" });
renderComponent(); renderComponent();
@ -879,7 +883,7 @@ describe("<BanToggleButton />", () => {
}, },
}; };
expect(callback(mockRoom)).toBe(null); expect(callback(mockRoom)).toBe(false);
expect(callback(mockRoom)).toBe(true); expect(callback(mockRoom)).toBe(true);
}); });