Conform more code to strictNullChecks (#10374)

* Apply `strictNullChecks` to `src/components/views/room_settings/*`

* Restore tsconfig.json

* Conform more code to `strictNullChecks`

* Iterate

* Update matrix-widget-api

* Conform more code to `strictNullChecks`
This commit is contained in:
Michael Telatynski 2023-03-16 11:07:29 +00:00 committed by GitHub
parent 9c816bb720
commit 1c9ea423c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 223 additions and 179 deletions

View file

@ -25,7 +25,7 @@ import { IS_MAC } from "./Keyboard";
* The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo. * The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo.
*/ */
export type KeyCombo = { export type KeyCombo = {
key?: string; key: string;
/** On PC: ctrl is pressed; on Mac: meta is pressed */ /** On PC: ctrl is pressed; on Mac: meta is pressed */
ctrlOrCmdKey?: boolean; ctrlOrCmdKey?: boolean;

View file

@ -306,7 +306,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
homeserverUrl: this.matrixClient.baseUrl, homeserverUrl: this.matrixClient.baseUrl,
identityServerUrl: this.matrixClient.idBaseUrl, identityServerUrl: this.matrixClient.idBaseUrl,
userId: this.matrixClient.credentials.userId, userId: this.matrixClient.credentials.userId,
deviceId: this.matrixClient.getDeviceId(), deviceId: this.matrixClient.getDeviceId() ?? undefined,
accessToken: this.matrixClient.getAccessToken(), accessToken: this.matrixClient.getAccessToken(),
guest: this.matrixClient.isGuest(), guest: this.matrixClient.isGuest(),
}; };

View file

@ -576,7 +576,7 @@ async function combinedPagination(searchResult: ISeshatSearchResults): Promise<I
const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0)); const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0));
restoreEncryptionInfo(newSlice); restoreEncryptionInfo(newSlice);
searchResult.pendingRequest = null; searchResult.pendingRequest = undefined;
return result; return result;
} }

View file

@ -41,7 +41,8 @@ interface IProps {
defaultName?: string; defaultName?: string;
parentSpace?: Room; parentSpace?: Room;
defaultEncrypted?: boolean; defaultEncrypted?: boolean;
onFinished(proceed?: boolean, opts?: IOpts): void; onFinished(proceed?: false): void;
onFinished(proceed: true, opts: IOpts): void;
} }
interface IState { interface IState {

View file

@ -46,7 +46,8 @@ interface IProps {
widgetDefinition: IModalWidgetOpenRequestData; widgetDefinition: IModalWidgetOpenRequestData;
widgetRoomId?: string; widgetRoomId?: string;
sourceWidgetId: string; sourceWidgetId: string;
onFinished(success?: boolean, data?: IModalWidgetReturnData): void; onFinished(success: true, data: IModalWidgetReturnData): void;
onFinished(success?: false, data?: void): void;
} }
interface IState { interface IState {

View file

@ -133,13 +133,13 @@ export default class ImageView extends React.Component<IProps, IState> {
// We want to recalculate zoom whenever the window's size changes // We want to recalculate zoom whenever the window's size changes
window.addEventListener("resize", this.recalculateZoom); window.addEventListener("resize", this.recalculateZoom);
// After the image loads for the first time we want to calculate the zoom // After the image loads for the first time we want to calculate the zoom
this.image.current.addEventListener("load", this.imageLoaded); this.image.current?.addEventListener("load", this.imageLoaded);
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
this.focusLock.current.removeEventListener("wheel", this.onWheel); this.focusLock.current.removeEventListener("wheel", this.onWheel);
window.removeEventListener("resize", this.recalculateZoom); window.removeEventListener("resize", this.recalculateZoom);
this.image.current.removeEventListener("load", this.imageLoaded); this.image.current?.removeEventListener("load", this.imageLoaded);
} }
private imageLoaded = (): void => { private imageLoaded = (): void => {
@ -171,6 +171,7 @@ export default class ImageView extends React.Component<IProps, IState> {
private setZoomAndRotation = (inputRotation?: number): void => { private setZoomAndRotation = (inputRotation?: number): void => {
const image = this.image.current; const image = this.image.current;
const imageWrapper = this.imageWrapper.current; const imageWrapper = this.imageWrapper.current;
if (!image || !imageWrapper) return;
const rotation = inputRotation ?? this.state.rotation; const rotation = inputRotation ?? this.state.rotation;
@ -236,8 +237,8 @@ export default class ImageView extends React.Component<IProps, IState> {
// Zoom relative to the given point on the image. // Zoom relative to the given point on the image.
// First we need to figure out the offset of the anchor point // First we need to figure out the offset of the anchor point
// relative to the center of the image, accounting for rotation. // relative to the center of the image, accounting for rotation.
let offsetX; let offsetX: number | undefined;
let offsetY; let offsetY: number | undefined;
// The modulo operator can return negative values for some // The modulo operator can return negative values for some
// rotations, so we have to do some extra work to normalize it // rotations, so we have to do some extra work to normalize it
switch (((this.state.rotation % 360) + 360) % 360) { switch (((this.state.rotation % 360) + 360) % 360) {
@ -310,7 +311,7 @@ export default class ImageView extends React.Component<IProps, IState> {
private onDownloadClick = (): void => { private onDownloadClick = (): void => {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = this.props.src; a.href = this.props.src;
a.download = this.props.name; if (this.props.name) a.download = this.props.name;
a.target = "_blank"; a.target = "_blank";
a.rel = "noreferrer noopener"; a.rel = "noreferrer noopener";
a.click(); a.click();
@ -334,9 +335,9 @@ export default class ImageView extends React.Component<IProps, IState> {
ev.preventDefault(); ev.preventDefault();
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
event_id: this.props.mxEvent.getId(), event_id: this.props.mxEvent?.getId(),
highlighted: true, highlighted: true,
room_id: this.props.mxEvent.getRoomId(), room_id: this.props.mxEvent?.getRoomId(),
metricsTrigger: undefined, // room doesn't change metricsTrigger: undefined, // room doesn't change
}); });
this.props.onFinished(); this.props.onFinished();
@ -440,11 +441,11 @@ export default class ImageView extends React.Component<IProps, IState> {
let info: JSX.Element | undefined; let info: JSX.Element | undefined;
if (showEventMeta) { if (showEventMeta) {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent!;
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
let permalink = "#"; let permalink = "#";
if (this.props.permalinkCreator) { if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); permalink = this.props.permalinkCreator.forEvent(mxEvent.getId());
} }
const senderName = mxEvent.sender?.name ?? mxEvent.getSender(); const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
@ -453,7 +454,7 @@ export default class ImageView extends React.Component<IProps, IState> {
<a <a
href={permalink} href={permalink}
onClick={this.onPermalinkClicked} onClick={this.onPermalinkClicked}
aria-label={formatFullDate(new Date(this.props.mxEvent.getTs()), showTwelveHour, false)} aria-label={formatFullDate(new Date(mxEvent.getTs()), showTwelveHour, false)}
> >
<MessageTimestamp <MessageTimestamp
showFullDate={true} showFullDate={true}

View file

@ -66,17 +66,17 @@ interface IProps<T> {
// should typically be less than `overflowItems` unless applying // should typically be less than `overflowItems` unless applying
// margins in the parent component when using multiple LazyRenderList in one viewport. // margins in the parent component when using multiple LazyRenderList in one viewport.
// use 0 to only rerender when items will come into view. // use 0 to only rerender when items will come into view.
overflowMargin?: number; overflowMargin: number;
// the amount of items to add at the top and bottom to render, // the amount of items to add at the top and bottom to render,
// so not every scroll of causes a rerender. // so not every scroll of causes a rerender.
overflowItems?: number; overflowItems: number;
element?: string; element?: string;
className?: string; className?: string;
} }
interface IState { interface IState {
renderRange: ItemRange | null; renderRange: ItemRange;
} }
export default class LazyRenderList<T = any> extends React.Component<IProps<T>, IState> { export default class LazyRenderList<T = any> extends React.Component<IProps<T>, IState> {
@ -88,9 +88,7 @@ export default class LazyRenderList<T = any> extends React.Component<IProps<T>,
public constructor(props: IProps<T>) { public constructor(props: IProps<T>) {
super(props); super(props);
this.state = { this.state = LazyRenderList.getDerivedStateFromProps(props, {} as IState) as IState;
renderRange: null,
};
} }
public static getDerivedStateFromProps(props: IProps<unknown>, state: IState): Partial<IState> | null { public static getDerivedStateFromProps(props: IProps<unknown>, state: IState): Partial<IState> | null {

View file

@ -35,7 +35,7 @@ const ALGORITHM = "m.megolm.v1.aes-sha2";
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => { const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const roomId = mxEvent.getRoomId(); const roomId = mxEvent.getRoomId()!;
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
const prevContent = mxEvent.getPrevContent() as IRoomEncryption; const prevContent = mxEvent.getPrevContent() as IRoomEncryption;
@ -51,13 +51,13 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
if (prevContent.algorithm === ALGORITHM) { if (prevContent.algorithm === ALGORITHM) {
subtitle = _t("Some encryption parameters have been changed."); subtitle = _t("Some encryption parameters have been changed.");
} else if (dmPartner) { } else if (dmPartner) {
const displayName = room.getMember(dmPartner)?.rawDisplayName || dmPartner; const displayName = room?.getMember(dmPartner)?.rawDisplayName || dmPartner;
subtitle = _t( subtitle = _t(
"Messages here are end-to-end encrypted. " + "Messages here are end-to-end encrypted. " +
"Verify %(displayName)s in their profile - tap on their avatar.", "Verify %(displayName)s in their profile - tap on their avatar.",
{ displayName }, { displayName },
); );
} else if (isLocalRoom(room)) { } else if (room && isLocalRoom(room)) {
subtitle = _t("Messages in this chat will be end-to-end encrypted."); subtitle = _t("Messages in this chat will be end-to-end encrypted.");
} else { } else {
subtitle = _t( subtitle = _t(

View file

@ -183,7 +183,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
<SmartMarker <SmartMarker
map={map} map={map}
id={`${mapId}-marker`} id={`${mapId}-marker`}
geoUri={latestLocationState.uri} geoUri={latestLocationState?.uri}
roomMember={markerRoomMember ?? undefined} roomMember={markerRoomMember ?? undefined}
useMemberColor useMemberColor
/> />

View file

@ -47,10 +47,10 @@ interface IProps {
room: Room; room: Room;
onClose: () => void; onClose: () => void;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
e2eStatus?: E2EStatus; e2eStatus?: E2EStatus;
classNames?: string; classNames?: string;
timelineSet?: EventTimelineSet; timelineSet: EventTimelineSet;
timelineRenderingType?: TimelineRenderingType; timelineRenderingType?: TimelineRenderingType;
showComposer?: boolean; showComposer?: boolean;
composerRelation?: IEventRelation; composerRelation?: IEventRelation;
@ -77,7 +77,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
private layoutWatcherRef: string; private layoutWatcherRef: string;
private timelinePanel = React.createRef<TimelinePanel>(); private timelinePanel = React.createRef<TimelinePanel>();
private card = React.createRef<HTMLDivElement>(); private card = React.createRef<HTMLDivElement>();
private readReceiptsSettingWatcher: string; private readReceiptsSettingWatcher: string | undefined;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@ -87,7 +87,6 @@ export default class TimelineCard extends React.Component<IProps, IState> {
atEndOfLiveTimeline: true, atEndOfLiveTimeline: true,
narrow: false, narrow: false,
}; };
this.readReceiptsSettingWatcher = null;
} }
public componentDidMount(): void { public componentDidMount(): void {
@ -129,7 +128,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
case Action.EditEvent: case Action.EditEvent:
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) {
@ -199,7 +198,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const highlightedEventId = this.state.isInitialEventHighlighted ? this.state.initialEventId : null; const highlightedEventId = this.state.isInitialEventHighlighted ? this.state.initialEventId : undefined;
let jumpToBottom; let jumpToBottom;
if (!this.state.atEndOfLiveTimeline) { if (!this.state.atEndOfLiveTimeline) {
@ -232,7 +231,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
header={this.renderTimelineCardHeader()} header={this.renderTimelineCardHeader()}
ref={this.card} ref={this.card}
> >
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} /> {this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
<div className="mx_TimelineCard_timeline"> <div className="mx_TimelineCard_timeline">
{jumpToBottom} {jumpToBottom}
<TimelinePanel <TimelinePanel

View file

@ -69,8 +69,9 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD); const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const noCommonMethodError: JSX.Element = let noCommonMethodError: JSX.Element | undefined;
!showSAS && !showQR ? ( if (!showSAS && !showQR) {
noCommonMethodError = (
<p> <p>
{_t( {_t(
"The device you are trying to verify doesn't support scanning a " + "The device you are trying to verify doesn't support scanning a " +
@ -79,12 +80,13 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
{ brand }, { brand },
)} )}
</p> </p>
) : null; );
}
if (this.props.layout === "dialog") { if (this.props.layout === "dialog") {
// HACK: This is a terrible idea. // HACK: This is a terrible idea.
let qrBlockDialog: JSX.Element; let qrBlockDialog: JSX.Element | undefined;
let sasBlockDialog: JSX.Element; let sasBlockDialog: JSX.Element | undefined;
if (showQR) { if (showQR) {
qrBlockDialog = ( qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption"> <div className="mx_VerificationPanel_QRPhase_startOption">
@ -132,7 +134,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
); );
} }
let qrBlock: JSX.Element; let qrBlock: JSX.Element | undefined;
if (showQR) { if (showQR) {
qrBlock = ( qrBlock = (
<div className="mx_UserInfo_container"> <div className="mx_UserInfo_container">
@ -150,7 +152,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
); );
} }
let sasBlock: JSX.Element; let sasBlock: JSX.Element | undefined;
if (showSAS) { if (showSAS) {
const disabled = this.state.emojiButtonClicked; const disabled = this.state.emojiButtonClicked;
const sasLabel = showQR const sasLabel = showQR
@ -189,18 +191,20 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} }
private onReciprocateYesClick = (): void => { private onReciprocateYesClick = (): void => {
if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true }); this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.confirm(); this.state.reciprocateQREvent.confirm();
}; };
private onReciprocateNoClick = (): void => { private onReciprocateNoClick = (): void => {
if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true }); this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.cancel(); this.state.reciprocateQREvent.cancel();
}; };
private getDevice(): DeviceInfo { private getDevice(): DeviceInfo | null {
const deviceId = this.props.request && this.props.request.channel.deviceId; const cli = MatrixClientPeg.get();
return MatrixClientPeg.get().getStoredDevice(MatrixClientPeg.get().getUserId(), deviceId); return cli.getStoredDevice(cli.getSafeUserId(), this.props.request?.channel.deviceId);
} }
private renderQRReciprocatePhase(): JSX.Element { private renderQRReciprocatePhase(): JSX.Element {
@ -253,7 +257,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private renderVerifiedPhase(): JSX.Element { private renderVerifiedPhase(): JSX.Element {
const { member, request } = this.props; const { member, request } = this.props;
let text: string; let text: string | undefined;
if (!request.isSelfVerification) { if (!request.isSelfVerification) {
if (this.props.isRoomEncrypted) { if (this.props.isRoomEncrypted) {
text = _t("Verify all users in a room to ensure it's secure."); text = _t("Verify all users in a room to ensure it's secure.");
@ -348,7 +352,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const emojis = this.state.sasEvent ? ( const emojis = this.state.sasEvent ? (
<VerificationShowSas <VerificationShowSas
displayName={displayName} displayName={displayName}
device={this.getDevice()} device={this.getDevice() ?? undefined}
sas={this.state.sasEvent.sas} sas={this.state.sasEvent.sas}
onCancel={this.onSasMismatchesClick} onCancel={this.onSasMismatchesClick}
onDone={this.onSasMatchesClick} onDone={this.onSasMatchesClick}
@ -383,11 +387,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
}; };
private onSasMatchesClick = (): void => { private onSasMatchesClick = (): void => {
this.state.sasEvent.confirm(); this.state.sasEvent?.confirm();
}; };
private onSasMismatchesClick = (): void => { private onSasMismatchesClick = (): void => {
this.state.sasEvent.mismatch(); this.state.sasEvent?.mismatch();
}; };
private updateVerifierState = (): void => { private updateVerifierState = (): void => {

View file

@ -55,9 +55,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
// Don't render anything as we are about to transition // Don't render anything as we are about to transition
if (!app || !isRight) return null; if (!app || !isRight) return null;
let contextMenu; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect(); const rect = handle.current!.getBoundingClientRect();
contextMenu = ( contextMenu = (
<WidgetContextMenu <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
@ -92,7 +92,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
fullWidth fullWidth
showMenubar={false} showMenubar={false}
room={room} room={room}
userId={cli.getUserId()} userId={cli.getSafeUserId()}
creatorUserId={app.creatorUserId} creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}

View file

@ -252,10 +252,10 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
} }
if (isEmpty) { if (isEmpty) {
this.formatBarRef.current.hide(); this.formatBarRef.current?.hide();
} }
this.setState({ this.setState({
autoComplete: this.props.model.autoComplete, autoComplete: this.props.model.autoComplete ?? undefined,
// if a change is happening then clear the showVisualBell // if a change is happening then clear the showVisualBell
showVisualBell: diff ? false : this.state.showVisualBell, showVisualBell: diff ? false : this.state.showVisualBell,
}); });
@ -266,12 +266,16 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// If the user is entering a command, only consider them typing if it is one which sends a message into the room // If the user is entering a command, only consider them typing if it is one which sends a message into the room
if (isTyping && this.props.model.parts[0].type === "command") { if (isTyping && this.props.model.parts[0].type === "command") {
const { cmd } = parseCommandString(this.props.model.parts[0].text); const { cmd } = parseCommandString(this.props.model.parts[0].text);
const command = CommandMap.get(cmd); const command = CommandMap.get(cmd!);
if (!command || !command.isEnabled() || command.category !== CommandCategories.messages) { if (!command?.isEnabled() || command.category !== CommandCategories.messages) {
isTyping = false; isTyping = false;
} }
} }
SdkContextClass.instance.typingStore.setSelfTyping(this.props.room.roomId, this.props.threadId, isTyping); SdkContextClass.instance.typingStore.setSelfTyping(
this.props.room.roomId,
this.props.threadId ?? null,
isTyping,
);
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(); this.props.onChange();
@ -280,14 +284,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private showPlaceholder(): void { private showPlaceholder(): void {
// escape single quotes // escape single quotes
const placeholder = this.props.placeholder.replace(/'/g, "\\'"); const placeholder = this.props.placeholder?.replace(/'/g, "\\'");
this.editorRef.current.style.setProperty("--placeholder", `'${placeholder}'`); this.editorRef.current?.style.setProperty("--placeholder", `'${placeholder}'`);
this.editorRef.current.classList.add("mx_BasicMessageComposer_inputEmpty"); this.editorRef.current?.classList.add("mx_BasicMessageComposer_inputEmpty");
} }
private hidePlaceholder(): void { private hidePlaceholder(): void {
this.editorRef.current.classList.remove("mx_BasicMessageComposer_inputEmpty"); this.editorRef.current?.classList.remove("mx_BasicMessageComposer_inputEmpty");
this.editorRef.current.style.removeProperty("--placeholder"); this.editorRef.current?.style.removeProperty("--placeholder");
} }
private onCompositionStart = (): void => { private onCompositionStart = (): void => {
@ -327,9 +331,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
private onCutCopy = (event: ClipboardEvent, type: string): void => { private onCutCopy = (event: ClipboardEvent, type: string): void => {
const selection = document.getSelection(); const selection = document.getSelection()!;
const text = selection.toString(); const text = selection.toString();
if (text) { if (text && this.editorRef.current) {
const { model } = this.props; const { model } = this.props;
const range = getRangeForSelection(this.editorRef.current, model, selection); const range = getRangeForSelection(this.editorRef.current, model, selection);
const selectedParts = range.parts.map((p) => p.serialize()); const selectedParts = range.parts.map((p) => p.serialize());
@ -412,7 +416,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const { model } = this.props; const { model } = this.props;
this._isCaretAtEnd = position.isAtEnd(model); this._isCaretAtEnd = position.isAtEnd(model);
this.lastCaret = position.asOffset(model); this.lastCaret = position.asOffset(model);
this.lastSelection = cloneSelection(document.getSelection()); this.lastSelection = cloneSelection(document.getSelection()!);
} }
private refreshLastCaretIfNeeded(): DocumentOffset | undefined { private refreshLastCaretIfNeeded(): DocumentOffset | undefined {
@ -422,7 +426,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
if (!this.editorRef.current) { if (!this.editorRef.current) {
return; return;
} }
const selection = document.getSelection(); const selection = document.getSelection()!;
if (!this.lastSelection || !selectionEquals(this.lastSelection, selection)) { if (!this.lastSelection || !selectionEquals(this.lastSelection, selection)) {
this.lastSelection = cloneSelection(selection); this.lastSelection = cloneSelection(selection);
const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection); const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection);
@ -467,7 +471,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const { isEmpty } = this.props.model; const { isEmpty } = this.props.model;
this.refreshLastCaretIfNeeded(); this.refreshLastCaretIfNeeded();
const selection = document.getSelection(); const selection = document.getSelection()!;
if (this.hasTextSelected && selection.isCollapsed) { if (this.hasTextSelected && selection.isCollapsed) {
this.hasTextSelected = false; this.hasTextSelected = false;
this.formatBarRef.current?.hide(); this.formatBarRef.current?.hide();
@ -485,7 +489,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const model = this.props.model; const model = this.props.model;
let handled = false; let handled = false;
if (this.state.surroundWith && document.getSelection().type !== "Caret") { if (this.state.surroundWith && document.getSelection()!.type !== "Caret") {
// This surrounds the selected text with a character. This is // This surrounds the selected text with a character. This is
// intentionally left out of the keybinding manager as the keybinds // intentionally left out of the keybinding manager as the keybinds
// here shouldn't be changeable // here shouldn't be changeable
@ -538,7 +542,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.tabCompleteName(); this.tabCompleteName();
handled = true; handled = true;
} else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction!)) { } else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction!)) {
this.formatBarRef.current.hide(); this.formatBarRef.current?.hide();
} }
if (handled) { if (handled) {
@ -654,7 +658,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private onAutoCompleteConfirm = (completion: ICompletion): void => { private onAutoCompleteConfirm = (completion: ICompletion): void => {
this.modifiedFlag = true; this.modifiedFlag = true;
this.props.model.autoComplete.onComponentConfirm(completion); this.props.model.autoComplete?.onComponentConfirm(completion);
}; };
private onAutoCompleteSelectionChange = (completionIndex: number): void => { private onAutoCompleteSelectionChange = (completionIndex: number): void => {
@ -691,9 +695,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
public componentWillUnmount(): void { public componentWillUnmount(): void {
document.removeEventListener("selectionchange", this.onSelectionChange); document.removeEventListener("selectionchange", this.onSelectionChange);
this.editorRef.current.removeEventListener("input", this.onInput, true); this.editorRef.current?.removeEventListener("input", this.onInput, true);
this.editorRef.current.removeEventListener("compositionstart", this.onCompositionStart, true); this.editorRef.current?.removeEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true); this.editorRef.current?.removeEventListener("compositionend", this.onCompositionEnd, true);
SettingsStore.unwatchSetting(this.useMarkdownHandle); SettingsStore.unwatchSetting(this.useMarkdownHandle);
SettingsStore.unwatchSetting(this.emoticonSettingHandle); SettingsStore.unwatchSetting(this.emoticonSettingHandle);
SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle); SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
@ -716,10 +720,10 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.updateEditorState(this.getInitialCaretPosition()); this.updateEditorState(this.getInitialCaretPosition());
// attach input listener by hand so React doesn't proxy the events, // attach input listener by hand so React doesn't proxy the events,
// as the proxied event doesn't support inputType, which we need. // as the proxied event doesn't support inputType, which we need.
this.editorRef.current.addEventListener("input", this.onInput, true); this.editorRef.current?.addEventListener("input", this.onInput, true);
this.editorRef.current.addEventListener("compositionstart", this.onCompositionStart, true); this.editorRef.current?.addEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current.addEventListener("compositionend", this.onCompositionEnd, true); this.editorRef.current?.addEventListener("compositionend", this.onCompositionEnd, true);
this.editorRef.current.focus(); this.editorRef.current?.focus();
} }
private getInitialCaretPosition(): DocumentPosition { private getInitialCaretPosition(): DocumentPosition {
@ -826,7 +830,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
public focus(): void { public focus(): void {
this.editorRef.current.focus(); this.editorRef.current?.focus();
} }
public insertMention(userId: string): void { public insertMention(userId: string): void {

View file

@ -149,7 +149,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
} }
private getRoom(): Room { private getRoom(): Room | null {
return this.props.mxClient.getRoom(this.props.editState.getEvent().getRoomId()); return this.props.mxClient.getRoom(this.props.editState.getEvent().getRoomId());
} }
@ -237,10 +237,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
private get events(): MatrixEvent[] { private get events(): MatrixEvent[] {
const liveTimelineEvents = this.context.liveTimeline.getEvents(); const liveTimelineEvents = this.context.liveTimeline?.getEvents();
const pendingEvents = this.getRoom().getPendingEvents(); const pendingEvents = this.getRoom()?.getPendingEvents();
const isInThread = Boolean(this.props.editState.getEvent().getThread()); const isInThread = Boolean(this.props.editState.getEvent().getThread());
return liveTimelineEvents.concat(isInThread ? [] : pendingEvents); return liveTimelineEvents?.concat(isInThread ? [] : pendingEvents) ?? [];
} }
private cancelEdit = (): void => { private cancelEdit = (): void => {
@ -304,10 +304,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
}); });
// Replace emoticon at the end of the message // Replace emoticon at the end of the message
if (SettingsStore.getValue("MessageComposerInput.autoReplaceEmoji")) { if (SettingsStore.getValue("MessageComposerInput.autoReplaceEmoji") && this.editorRef.current) {
const caret = this.editorRef.current?.getCaret(); const caret = this.editorRef.current.getCaret();
const position = this.model.positionForOffset(caret.offset, caret.atNodeEnd); const position = this.model.positionForOffset(caret.offset, caret.atNodeEnd);
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON); this.editorRef.current.replaceEmoticon(position, REGEX_EMOTICON);
} }
const editContent = createEditContent(this.model, editedEvent); const editContent = createEditContent(this.model, editedEvent);
const newContent = editContent["m.new_content"]; const newContent = editContent["m.new_content"];
@ -382,7 +382,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
// store caret and serialized parts in the // store caret and serialized parts in the
// editorstate so it can be restored when the remote echo event tile gets rendered // editorstate so it can be restored when the remote echo event tile gets rendered
// in case we're currently editing a pending event // in case we're currently editing a pending event
const sel = document.getSelection(); const sel = document.getSelection()!;
let caret: DocumentOffset | undefined; let caret: DocumentOffset | undefined;
if (sel.focusNode) { if (sel.focusNode) {
caret = getCaretOffsetAndText(this.editorRef.current?.editorRef.current, sel).caret; caret = getCaretOffsetAndText(this.editorRef.current?.editorRef.current, sel).caret;

View file

@ -238,7 +238,7 @@ interface IState {
isQuoteExpanded?: boolean; isQuoteExpanded?: boolean;
thread: Thread; thread: Thread | null;
threadNotification?: NotificationCountType; threadNotification?: NotificationCountType;
} }
@ -438,12 +438,12 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (thread.id === this.props.mxEvent.getId()) { if (thread.id === this.props.mxEvent.getId()) {
this.updateThread(thread); this.updateThread(thread);
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
room.off(ThreadEvent.New, this.onNewThread); room?.off(ThreadEvent.New, this.onNewThread);
} }
}; };
private get thread(): Thread | null { private get thread(): Thread | null {
let thread = this.props.mxEvent.getThread(); let thread: Thread | undefined = this.props.mxEvent.getThread();
/** /**
* Accessing the threads value through the room due to a race condition * Accessing the threads value through the room due to a race condition
* that will be solved when there are proper backend support for threads * that will be solved when there are proper backend support for threads
@ -452,7 +452,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
*/ */
if (!thread) { if (!thread) {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
thread = room?.findThreadForEvent(this.props.mxEvent); thread = room?.findThreadForEvent(this.props.mxEvent) ?? undefined;
} }
return thread ?? null; return thread ?? null;
} }
@ -471,7 +471,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
} }
private renderThreadInfo(): React.ReactNode { private renderThreadInfo(): React.ReactNode {
if (this.state.thread?.id === this.props.mxEvent.getId()) { if (this.state.thread && this.state.thread.id === this.props.mxEvent.getId()) {
return ( return (
<ThreadSummary mxEvent={this.props.mxEvent} thread={this.state.thread} data-testid="thread-summary" /> <ThreadSummary mxEvent={this.props.mxEvent} thread={this.state.thread} data-testid="thread-summary" />
); );
@ -506,7 +506,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
const { permalinkCreator, mxEvent } = this.props; const { permalinkCreator, mxEvent } = this.props;
const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId()); const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId()!);
await copyPlaintext(matrixToUrl); await copyPlaintext(matrixToUrl);
}; };
@ -1104,7 +1104,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const useIRCLayout = this.props.layout === Layout.IRC; const useIRCLayout = this.props.layout === Layout.IRC;
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null; const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
const ircTimestamp = useIRCLayout ? linkedTimestamp : null; const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : null; const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : undefined;
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock(); const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock(); const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
@ -1493,7 +1493,7 @@ class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
} }
interface ISentReceiptProps { interface ISentReceiptProps {
messageState: string; // TODO: Types for message sending state messageState: EventStatus | null;
} }
function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element { function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {

View file

@ -164,7 +164,7 @@ const NewRoomIntro: React.FC = () => {
} }
const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = room?.getMember(creator)?.rawDisplayName || creator; const creatorName = (creator && room?.getMember(creator)?.rawDisplayName) || creator;
let createdText: string; let createdText: string;
if (creator === cli.getUserId()) { if (creator === cli.getUserId()) {
@ -178,7 +178,7 @@ const NewRoomIntro: React.FC = () => {
let parentSpace: Room | undefined; let parentSpace: Room | undefined;
if ( if (
SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getSafeUserId()) && SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getSafeUserId()) &&
SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace, room.roomId) SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace!, room.roomId)
) { ) {
parentSpace = SpaceStore.instance.activeSpaceRoom; parentSpace = SpaceStore.instance.activeSpaceRoom;
} }

View file

@ -231,7 +231,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
contextMenu = ( contextMenu = (
<IconizedContextMenu <IconizedContextMenu
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())} {...contextMenuBelow(plusMenuHandle.current!.getBoundingClientRect())}
onFinished={closePlusMenu} onFinished={closePlusMenu}
compact compact
> >
@ -357,7 +357,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
contextMenu = ( contextMenu = (
<IconizedContextMenu <IconizedContextMenu
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())} {...contextMenuBelow(plusMenuHandle.current!.getBoundingClientRect())}
onFinished={closePlusMenu} onFinished={closePlusMenu}
compact compact
> >

View file

@ -227,12 +227,14 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// selection must be collapsed and caret at start // selection must be collapsed and caret at start
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) { if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
const events = this.context.liveTimeline const events = this.context.liveTimeline
.getEvents() ?.getEvents()
.concat(replyingToThread ? [] : this.props.room.getPendingEvents()); .concat(replyingToThread ? [] : this.props.room.getPendingEvents());
const editEvent = findEditableEvent({ const editEvent = events
events, ? findEditableEvent({
isForward: false, events,
}); isForward: false,
})
: undefined;
if (editEvent) { if (editEvent) {
// We're selecting history, so prevent the key event from doing anything else // We're selecting history, so prevent the key event from doing anything else
event.preventDefault(); event.preventDefault();
@ -297,7 +299,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
if (events[i].getType() === EventType.RoomMessage) { if (events[i].getType() === EventType.RoomMessage) {
let shouldReact = true; let shouldReact = true;
const lastMessage = events[i]; const lastMessage = events[i];
const userId = MatrixClientPeg.get().getUserId(); const userId = MatrixClientPeg.get().getSafeUserId();
const messageReactions = this.props.room.relations.getChildEventsForEvent( const messageReactions = this.props.room.relations.getChildEventsForEvent(
lastMessage.getId()!, lastMessage.getId()!,
RelationType.Annotation, RelationType.Annotation,
@ -307,10 +309,10 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// if we have already sent this reaction, don't redact but don't re-send // if we have already sent this reaction, don't redact but don't re-send
if (messageReactions) { if (messageReactions) {
const myReactionEvents = const myReactionEvents =
messageReactions.getAnnotationsBySender()[userId] || new Set<MatrixEvent>(); messageReactions.getAnnotationsBySender()?.[userId] || new Set<MatrixEvent>();
const myReactionKeys = [...myReactionEvents] const myReactionKeys = [...myReactionEvents]
.filter((event) => !event.isRedacted()) .filter((event) => !event.isRedacted())
.map((event) => event.getRelation().key); .map((event) => event.getRelation()?.key);
shouldReact = !myReactionKeys.includes(reaction); shouldReact = !myReactionKeys.includes(reaction);
} }
if (shouldReact) { if (shouldReact) {

View file

@ -46,7 +46,7 @@ interface IKeyboardShortcutProps {
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value, className = "mx_KeyboardShortcut" }) => { export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value, className = "mx_KeyboardShortcut" }) => {
if (!value) return null; if (!value) return null;
const modifiersElement = []; const modifiersElement: JSX.Element[] = [];
if (value.ctrlOrCmdKey) { if (value.ctrlOrCmdKey) {
modifiersElement.push(<KeyboardKey key="ctrlOrCmdKey" name={IS_MAC ? Key.META : Key.CONTROL} />); modifiersElement.push(<KeyboardKey key="ctrlOrCmdKey" name={IS_MAC ? Key.META : Key.CONTROL} />);
} else if (value.ctrlKey) { } else if (value.ctrlKey) {

View file

@ -41,7 +41,7 @@ import SdkConfig from "../../../SdkConfig";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import TagComposer from "../elements/TagComposer"; import TagComposer from "../elements/TagComposer";
import { objectClone } from "../../../utils/objects"; import { objectClone } from "../../../utils/objects";
import { arrayDiff } from "../../../utils/arrays"; import { arrayDiff, filterBoolean } from "../../../utils/arrays";
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications"; import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
import { import {
updateExistingPushRulesWithActions, updateExistingPushRulesWithActions,
@ -167,7 +167,7 @@ const maximumVectorState = (
if (!definition.syncedRuleIds?.length) { if (!definition.syncedRuleIds?.length) {
return undefined; return undefined;
} }
const vectorState = definition.syncedRuleIds.reduce<VectorState>((maxVectorState, ruleId) => { const vectorState = definition.syncedRuleIds.reduce<VectorState | undefined>((maxVectorState, ruleId) => {
// already set to maximum // already set to maximum
if (maxVectorState === VectorState.Loud) { if (maxVectorState === VectorState.Loud) {
return maxVectorState; return maxVectorState;
@ -345,7 +345,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
for (const rule of defaultRules[category]) { for (const rule of defaultRules[category]) {
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.rule_id]; const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.rule_id];
const vectorState = definition.ruleToVectorState(rule); const vectorState = definition.ruleToVectorState(rule);
preparedNewState.vectorPushRules[category].push({ preparedNewState.vectorPushRules[category]!.push({
ruleId: rule.rule_id, ruleId: rule.rule_id,
rule, rule,
vectorState, vectorState,
@ -355,7 +355,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
} }
// Quickly sort the rules for display purposes // Quickly sort the rules for display purposes
preparedNewState.vectorPushRules[category].sort((a, b) => { preparedNewState.vectorPushRules[category]!.sort((a, b) => {
let idxA = RULE_DISPLAY_ORDER.indexOf(a.ruleId); let idxA = RULE_DISPLAY_ORDER.indexOf(a.ruleId);
let idxB = RULE_DISPLAY_ORDER.indexOf(b.ruleId); let idxB = RULE_DISPLAY_ORDER.indexOf(b.ruleId);
@ -367,7 +367,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}); });
if (category === KEYWORD_RULE_CATEGORY) { if (category === KEYWORD_RULE_CATEGORY) {
preparedNewState.vectorPushRules[category].push({ preparedNewState.vectorPushRules[category]!.push({
ruleId: KEYWORD_RULE_ID, ruleId: KEYWORD_RULE_ID,
description: _t("Messages containing keywords"), description: _t("Messages containing keywords"),
vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState, vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState,
@ -540,8 +540,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]): Promise<void> { private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]): Promise<void> {
try { try {
// De-duplicate and remove empties // De-duplicate and remove empties
keywords = Array.from(new Set(keywords)).filter((k) => !!k); keywords = filterBoolean(Array.from(new Set(keywords)));
const oldKeywords = Array.from(new Set(originalRules.map((r) => r.pattern))).filter((k) => !!k); const oldKeywords = filterBoolean(Array.from(new Set(originalRules.map((r) => r.pattern))));
// Note: Technically because of the UI interaction (at the time of writing), the diff // Note: Technically because of the UI interaction (at the time of writing), the diff
// will only ever be +/-1 so we don't really have to worry about efficiently handling // will only ever be +/-1 so we don't really have to worry about efficiently handling
@ -555,13 +555,13 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
} }
} }
let ruleVectorState = this.state.vectorKeywordRuleInfo.vectorState; let ruleVectorState = this.state.vectorKeywordRuleInfo?.vectorState;
if (ruleVectorState === VectorState.Off) { if (ruleVectorState === VectorState.Off) {
// When the current global keywords rule is OFF, we need to look at // When the current global keywords rule is OFF, we need to look at
// the flavor of existing rules to apply the same actions // the flavor of existing rules to apply the same actions
// when creating the new rule. // when creating the new rule.
if (originalRules.length) { if (originalRules.length) {
ruleVectorState = PushRuleVectorState.contentRuleVectorStateKind(originalRules[0]); ruleVectorState = PushRuleVectorState.contentRuleVectorStateKind(originalRules[0]) ?? undefined;
} else { } else {
ruleVectorState = VectorState.On; // default ruleVectorState = VectorState.On; // default
} }
@ -776,7 +776,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}
@ -840,7 +840,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
</tr> </tr>
)); ));
if (!rows.length) return null; // no targets to show if (!rows?.length) return null; // no targets to show
return ( return (
<div className="mx_UserNotifSettings_floatingSection"> <div className="mx_UserNotifSettings_floatingSection">

View file

@ -72,7 +72,7 @@ interface IProps {
interface IState { interface IState {
defaultIdServer?: string; defaultIdServer?: string;
currentClientIdServer: string; currentClientIdServer?: string;
idServer?: string; idServer?: string;
error?: string; error?: string;
busy: boolean; busy: boolean;

View file

@ -402,14 +402,14 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
{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 (
<BannedUser <BannedUser
key={member.userId} key={member.userId}
canUnban={canBanUsers} canUnban={canBanUsers}
member={member} member={member}
reason={banEvent.reason} reason={banEvent?.reason}
by={bannedBy} by={bannedBy}
/> />
); );

View file

@ -61,7 +61,7 @@ interface IProps {
interface IState { interface IState {
language: string; language: string;
spellCheckEnabled: boolean; spellCheckEnabled?: boolean;
spellCheckLanguages: string[]; spellCheckLanguages: string[];
haveIdServer: boolean; haveIdServer: boolean;
serverSupportsSeparateAddAndBind?: boolean; serverSupportsSeparateAddAndBind?: boolean;
@ -406,7 +406,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
<div className="mx_SettingsTab_section mx_SettingsTab_section_spellcheck"> <div className="mx_SettingsTab_section mx_SettingsTab_section_spellcheck">
<span className="mx_SettingsTab_subheading"> <span className="mx_SettingsTab_subheading">
{_t("Spell check")} {_t("Spell check")}
<ToggleSwitch checked={this.state.spellCheckEnabled} onChange={this.onSpellCheckEnabledChange} /> <ToggleSwitch checked={!!this.state.spellCheckEnabled} onChange={this.onSpellCheckEnabledChange} />
</span> </span>
{this.state.spellCheckEnabled && !IS_MAC && ( {this.state.spellCheckEnabled && !IS_MAC && (
<SpellCheckSettings <SpellCheckSettings

View file

@ -56,7 +56,7 @@ const confirmSignOut = async (sessionsToSignOutCount: number): Promise<boolean>
}); });
const [confirmed] = await finished; const [confirmed] = await finished;
return confirmed; return !!confirmed;
}; };
const useSignOut = ( const useSignOut = (

View file

@ -61,7 +61,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
private refreshMediaDevices = async (stream?: MediaStream): Promise<void> => { private refreshMediaDevices = async (stream?: MediaStream): Promise<void> => {
this.setState({ this.setState({
mediaDevices: await MediaDeviceHandler.getDevices(), mediaDevices: (await MediaDeviceHandler.getDevices()) ?? null,
[MediaDeviceKindEnum.AudioOutput]: MediaDeviceHandler.getAudioOutput(), [MediaDeviceKindEnum.AudioOutput]: MediaDeviceHandler.getAudioOutput(),
[MediaDeviceKindEnum.AudioInput]: MediaDeviceHandler.getAudioInput(), [MediaDeviceKindEnum.AudioInput]: MediaDeviceHandler.getAudioInput(),
[MediaDeviceKindEnum.VideoInput]: MediaDeviceHandler.getVideoInput(), [MediaDeviceKindEnum.VideoInput]: MediaDeviceHandler.getVideoInput(),
@ -105,8 +105,8 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
} }
private renderDropdown(kind: MediaDeviceKindEnum, label: string): ReactNode { 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) return null;
const defaultDevice = MediaDeviceHandler.getDefaultDevice(devices); const defaultDevice = MediaDeviceHandler.getDefaultDevice(devices);
return ( return (

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) ?? undefined,
ip, ip,
}); });
} }
@ -118,7 +118,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
should_peek: false, should_peek: false,
metricsTrigger: "VerificationRequest", metricsTrigger: "VerificationRequest",
}); });
const member = cli.getUser(request.otherUserId); const member = cli.getUser(request.otherUserId) ?? undefined;
RightPanelStore.instance.setCards( RightPanelStore.instance.setCards(
[ [
{ phase: RightPanelPhases.RoomSummary }, { phase: RightPanelPhases.RoomSummary },

View file

@ -158,14 +158,14 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
// non-blank labels for the devices. // non-blank labels for the devices.
let stream: MediaStream | null = null; let stream: MediaStream | null = null;
try { try {
if (devices.audioinput.length > 0) { if (devices!.audioinput.length > 0) {
// Holding just an audio stream will be enough to get us all device labels, so // Holding just an audio stream will be enough to get us all device labels, so
// if video is muted, don't bother requesting video. // if video is muted, don't bother requesting video.
stream = await navigator.mediaDevices.getUserMedia({ stream = await navigator.mediaDevices.getUserMedia({
audio: true, audio: true,
video: !videoMuted && devices.videoinput.length > 0 && { deviceId: videoInputId }, video: !videoMuted && devices!.videoinput.length > 0 && { deviceId: videoInputId },
}); });
} else if (devices.videoinput.length > 0) { } else if (devices!.videoinput.length > 0) {
// We have to resort to a video stream, even if video is supposed to be muted. // We have to resort to a video stream, even if video is supposed to be muted.
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoInputId } }); stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoInputId } });
} }
@ -182,7 +182,7 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
stream = null; stream = null;
} }
return [stream, devices.audioinput, devices.videoinput]; return [stream, devices?.audioinput ?? [], devices?.videoinput ?? []];
}, },
[videoInputId, videoMuted], [videoInputId, videoMuted],
[null, [], []], [null, [], []],

View file

@ -22,7 +22,7 @@ import { Action } from "../actions";
export interface ViewRoomErrorPayload extends Pick<ActionPayload, "action"> { export interface ViewRoomErrorPayload extends Pick<ActionPayload, "action"> {
action: Action.ViewRoomError; action: Action.ViewRoomError;
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
room_id: Room["roomId"]; room_id: Room["roomId"] | null;
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
room_alias?: string; room_alias?: string;
err?: MatrixError; err?: MatrixError;

View file

@ -47,7 +47,7 @@ export default class HistoryManager {
this.removedSinceLastPush = false; this.removedSinceLastPush = false;
} }
private shouldPush(inputType: string, diff: IDiff): boolean { private shouldPush(inputType?: string, diff?: IDiff): boolean {
// right now we can only push a step after // right now we can only push a step after
// the input has been applied to the model, // the input has been applied to the model,
// so we can't push the state before something happened. // so we can't push the state before something happened.
@ -102,7 +102,7 @@ export default class HistoryManager {
} }
// needs to persist parts and caret position // needs to persist parts and caret position
public tryPush(model: EditorModel, caret: Caret, inputType: string, diff: IDiff): boolean { public tryPush(model: EditorModel, caret: Caret, inputType?: string, diff?: IDiff): boolean {
// ignore state restoration echos. // ignore state restoration echos.
// these respect the inputType values of the input event, // these respect the inputType values of the input event,
// but are actually passed in from MessageEditor calling model.reset() // but are actually passed in from MessageEditor calling model.reset()

View file

@ -171,7 +171,7 @@ export default class EditorModel {
this._autoComplete = null; this._autoComplete = null;
this.autoCompletePartIdx = null; this.autoCompletePartIdx = null;
} }
this.updateCallback(caret, inputType); this.updateCallback?.(caret, inputType);
} }
/** /**

View file

@ -119,7 +119,7 @@ export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
room_id: ev.getRoomId(), room_id: ev.getRoomId(),
session_id: sessionId, session_id: sessionId,
device_id: wireContent.device_id, device_id: wireContent.device_id,
user_id: ev.getSender(), user_id: ev.getSender()!,
sender_key: wireContent.sender_key, sender_key: wireContent.sender_key,
}; };

View file

@ -62,14 +62,14 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
): void => { ): void => {
if (this.modalInstance) return; if (this.modalInstance) return;
this.openSourceWidgetId = sourceWidget.id; this.openSourceWidgetId = sourceWidget.id;
this.openSourceWidgetRoomId = widgetRoomId; this.openSourceWidgetRoomId = widgetRoomId ?? null;
this.modalInstance = Modal.createDialog( this.modalInstance = Modal.createDialog(
ModalWidgetDialog, ModalWidgetDialog,
{ {
widgetDefinition: { ...requestData }, widgetDefinition: { ...requestData },
widgetRoomId, widgetRoomId,
sourceWidgetId: sourceWidget.id, sourceWidgetId: sourceWidget.id,
onFinished: (success: boolean, data?: IModalWidgetReturnData) => { onFinished: (success, data) => {
if (!success) { if (!success) {
this.closeModalWidget(sourceWidget, widgetRoomId, { "m.exited": true }); this.closeModalWidget(sourceWidget, widgetRoomId, { "m.exited": true });
} else { } else {
@ -81,13 +81,17 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
this.modalInstance = null; this.modalInstance = null;
}, },
}, },
null, undefined,
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
); );
}; };
public closeModalWidget = (sourceWidget: Widget, widgetRoomId?: string, data?: IModalWidgetReturnData): void => { public closeModalWidget = (
sourceWidget: Widget,
widgetRoomId: string | undefined,
data: IModalWidgetReturnData,
): void => {
if (!this.modalInstance) return; if (!this.modalInstance) return;
if (this.openSourceWidgetId === sourceWidget.id && this.openSourceWidgetRoomId === widgetRoomId) { if (this.openSourceWidgetId === sourceWidget.id && this.openSourceWidgetRoomId === widgetRoomId) {
this.openSourceWidgetId = null; this.openSourceWidgetId = null;

View file

@ -50,4 +50,4 @@ export class RoomScrollStateStore {
if (window.mxRoomScrollStateStore === undefined) { if (window.mxRoomScrollStateStore === undefined) {
window.mxRoomScrollStateStore = new RoomScrollStateStore(); window.mxRoomScrollStateStore = new RoomScrollStateStore();
} }
export default window.mxRoomScrollStateStore; export default window.mxRoomScrollStateStore!;

View file

@ -395,7 +395,6 @@ export class RoomViewStore extends EventEmitter {
roomId: payload.room_id, roomId: payload.room_id,
initialEventId: null, initialEventId: null,
initialEventPixelOffset: null, initialEventPixelOffset: null,
isInitialEventHighlighted: null,
initialEventScrollIntoView: true, initialEventScrollIntoView: true,
roomAlias: null, roomAlias: null,
roomLoading: true, roomLoading: true,
@ -567,13 +566,13 @@ export class RoomViewStore extends EventEmitter {
} }
} }
private getInvitingUserId(roomId: string): string { private getInvitingUserId(roomId: string): string | undefined {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
if (room?.getMyMembership() === "invite") { if (room?.getMyMembership() === "invite") {
const myMember = room.getMember(cli.getUserId()); const myMember = room.getMember(cli.getSafeUserId());
const inviteEvent = myMember ? myMember.events.member : null; const inviteEvent = myMember ? myMember.events.member : null;
return inviteEvent && inviteEvent.getSender(); return inviteEvent?.getSender();
} }
} }

View file

@ -278,28 +278,28 @@ export default class RightPanelStore extends ReadyWatchingStore {
// (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available) // (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available)
switch (card.phase) { switch (card.phase) {
case RightPanelPhases.ThreadView: case RightPanelPhases.ThreadView:
if (!card.state.threadHeadEvent) { if (!card.state?.threadHeadEvent) {
logger.warn("removed card from right panel because of missing threadHeadEvent in card state"); logger.warn("removed card from right panel because of missing threadHeadEvent in card state");
} }
return !!card.state.threadHeadEvent; return !!card.state?.threadHeadEvent;
case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.RoomMemberInfo:
case RightPanelPhases.SpaceMemberInfo: case RightPanelPhases.SpaceMemberInfo:
case RightPanelPhases.EncryptionPanel: case RightPanelPhases.EncryptionPanel:
if (!card.state.member) { if (!card.state?.member) {
logger.warn("removed card from right panel because of missing member in card state"); logger.warn("removed card from right panel because of missing member in card state");
} }
return !!card.state.member; return !!card.state?.member;
case RightPanelPhases.Room3pidMemberInfo: case RightPanelPhases.Room3pidMemberInfo:
case RightPanelPhases.Space3pidMemberInfo: case RightPanelPhases.Space3pidMemberInfo:
if (!card.state.memberInfoEvent) { if (!card.state?.memberInfoEvent) {
logger.warn("removed card from right panel because of missing memberInfoEvent in card state"); logger.warn("removed card from right panel because of missing memberInfoEvent in card state");
} }
return !!card.state.memberInfoEvent; return !!card.state?.memberInfoEvent;
case RightPanelPhases.Widget: case RightPanelPhases.Widget:
if (!card.state.widgetId) { if (!card.state?.widgetId) {
logger.warn("removed card from right panel because of missing widgetId in card state"); logger.warn("removed card from right panel because of missing widgetId in card state");
} }
return !!card.state.widgetId; return !!card.state?.widgetId;
} }
return true; return true;
} }

View file

@ -168,7 +168,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this.rootSpaces; return this.rootSpaces;
} }
public get activeSpace(): SpaceKey | undefined { public get activeSpace(): SpaceKey {
return this._activeSpace; return this._activeSpace;
} }
@ -1018,7 +1018,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private onRoomStateMembers = (ev: MatrixEvent): void => { private onRoomStateMembers = (ev: MatrixEvent): void => {
const room = this.matrixClient.getRoom(ev.getRoomId()); const room = this.matrixClient.getRoom(ev.getRoomId());
const userId = ev.getStateKey(); const userId = ev.getStateKey()!;
if ( if (
room?.isSpaceRoom() && // only consider space rooms room?.isSpaceRoom() && // only consider space rooms
DMRoomMap.shared().getDMRoomsForUserId(userId).length > 0 && // only consider members we have a DM with DMRoomMap.shared().getDMRoomsForUserId(userId).length > 0 && // only consider members we have a DM with
@ -1049,9 +1049,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private onRoomFavouriteChange(room: Room): void { private onRoomFavouriteChange(room: Room): void {
if (this.enabledMetaSpaces.includes(MetaSpace.Favourites)) { if (this.enabledMetaSpaces.includes(MetaSpace.Favourites)) {
if (room.tags[DefaultTagID.Favourite]) { if (room.tags[DefaultTagID.Favourite]) {
this.roomIdsBySpace.get(MetaSpace.Favourites).add(room.roomId); this.roomIdsBySpace.get(MetaSpace.Favourites)?.add(room.roomId);
} else { } else {
this.roomIdsBySpace.get(MetaSpace.Favourites).delete(room.roomId); this.roomIdsBySpace.get(MetaSpace.Favourites)?.delete(room.roomId);
} }
this.emit(MetaSpace.Favourites); this.emit(MetaSpace.Favourites);
} }
@ -1064,7 +1064,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const homeRooms = this.roomIdsBySpace.get(MetaSpace.Home); const homeRooms = this.roomIdsBySpace.get(MetaSpace.Home);
if (this.showInHomeSpace(room)) { if (this.showInHomeSpace(room)) {
homeRooms?.add(room.roomId); homeRooms?.add(room.roomId);
} else if (!this.roomIdsBySpace.get(MetaSpace.Orphans).has(room.roomId)) { } else if (!this.roomIdsBySpace.get(MetaSpace.Orphans)?.has(room.roomId)) {
this.roomIdsBySpace.get(MetaSpace.Home)?.delete(room.roomId); this.roomIdsBySpace.get(MetaSpace.Home)?.delete(room.roomId);
} }
@ -1076,7 +1076,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
if (enabledMetaSpaces.has(MetaSpace.Orphans) || enabledMetaSpaces.has(MetaSpace.Home)) { if (enabledMetaSpaces.has(MetaSpace.Orphans) || enabledMetaSpaces.has(MetaSpace.Home)) {
if (isDm && this.roomIdsBySpace.get(MetaSpace.Orphans).delete(room.roomId)) { if (isDm && this.roomIdsBySpace.get(MetaSpace.Orphans)?.delete(room.roomId)) {
this.emit(MetaSpace.Orphans); this.emit(MetaSpace.Orphans);
this.emit(MetaSpace.Home); this.emit(MetaSpace.Home);
} }

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { defer } from "matrix-js-sdk/src/utils";
import React from "react"; import React from "react";
import BaseDialog from "../../../components/views/dialogs/BaseDialog"; import BaseDialog from "../../../components/views/dialogs/BaseDialog";
@ -46,11 +45,7 @@ export const ConfirmListenBroadcastStopCurrentDialog: React.FC<Props> = ({ onFin
}; };
export const showConfirmListenBroadcastStopCurrentDialog = async (): Promise<boolean> => { export const showConfirmListenBroadcastStopCurrentDialog = async (): Promise<boolean> => {
const { promise, resolve } = defer<boolean>(); const { finished } = Modal.createDialog(ConfirmListenBroadcastStopCurrentDialog);
const [confirmed] = await finished;
Modal.createDialog(ConfirmListenBroadcastStopCurrentDialog, { return !!confirmed;
onFinished: resolve,
});
return promise;
}; };

View file

@ -35,7 +35,7 @@ export const useVoiceBroadcastPlayback = (
position: number; position: number;
timeLeft: number; timeLeft: number;
}; };
sender: RoomMember; sender: RoomMember | null;
liveness: VoiceBroadcastLiveness; liveness: VoiceBroadcastLiveness;
playbackState: VoiceBroadcastPlaybackState; playbackState: VoiceBroadcastPlaybackState;
toggle(): void; toggle(): void;

View file

@ -44,7 +44,7 @@ const showStopBroadcastingDialog = async (): Promise<boolean> => {
button: _t("Yes, stop broadcast"), button: _t("Yes, stop broadcast"),
}); });
const [confirmed] = await finished; const [confirmed] = await finished;
return confirmed; return !!confirmed;
}; };
export const useVoiceBroadcastRecording = ( export const useVoiceBroadcastRecording = (
@ -54,7 +54,7 @@ export const useVoiceBroadcastRecording = (
timeLeft: number; timeLeft: number;
recordingState: VoiceBroadcastRecordingState; recordingState: VoiceBroadcastRecordingState;
room: Room; room: Room;
sender: RoomMember; sender: RoomMember | null;
stopRecording(): void; stopRecording(): void;
toggleRecording(): void; toggleRecording(): void;
} => { } => {

View file

@ -32,7 +32,7 @@ export * from "./components/atoms/VoiceBroadcastHeader";
export * from "./components/atoms/VoiceBroadcastPlaybackControl"; export * from "./components/atoms/VoiceBroadcastPlaybackControl";
export * from "./components/atoms/VoiceBroadcastRecordingConnectionError"; export * from "./components/atoms/VoiceBroadcastRecordingConnectionError";
export * from "./components/atoms/VoiceBroadcastRoomSubtitle"; export * from "./components/atoms/VoiceBroadcastRoomSubtitle";
export * from "./components/molecules/ConfirmListeBroadcastStopCurrent"; export * from "./components/molecules/ConfirmListenBroadcastStopCurrent";
export * from "./components/molecules/VoiceBroadcastPlaybackBody"; export * from "./components/molecules/VoiceBroadcastPlaybackBody";
export * from "./components/molecules/VoiceBroadcastSmallPlaybackBody"; export * from "./components/molecules/VoiceBroadcastSmallPlaybackBody";
export * from "./components/molecules/VoiceBroadcastPreRecordingPip"; export * from "./components/molecules/VoiceBroadcastPreRecordingPip";

View file

@ -674,7 +674,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.mkEvent({ TestUtilsMatrix.mkEvent({

View file

@ -35,6 +35,7 @@ import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore"; import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore"; import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore";
import { SdkContextClass } from "../../../src/contexts/SDKContext"; import { SdkContextClass } from "../../../src/contexts/SDKContext";
import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
const RightPanelBase = wrapInMatrixClientContext(_RightPanel); const RightPanelBase = wrapInMatrixClientContext(_RightPanel);
@ -110,7 +111,13 @@ describe("RightPanel", () => {
}); });
await viewedRoom; await viewedRoom;
const { container } = render(<RightPanel room={r1} resizeNotifier={resizeNotifier} />); const { container } = render(
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
);
expect(container.getElementsByClassName("mx_RoomSummaryCard")).toHaveLength(1); expect(container.getElementsByClassName("mx_RoomSummaryCard")).toHaveLength(1);
const switchedPhases = waitForRpsUpdate(); const switchedPhases = waitForRpsUpdate();
@ -152,7 +159,13 @@ describe("RightPanel", () => {
await spinUpStores(); await spinUpStores();
// Run initial render with room 1, and also running lifecycle methods // Run initial render with room 1, and also running lifecycle methods
const { container, rerender } = render(<RightPanel room={r1} resizeNotifier={resizeNotifier} />); const { container, rerender } = render(
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
);
// Wait for RPS room 1 updates to fire // Wait for RPS room 1 updates to fire
const rpsUpdated = waitForRpsUpdate(); const rpsUpdated = waitForRpsUpdate();
dis.dispatch({ dis.dispatch({
@ -172,7 +185,13 @@ describe("RightPanel", () => {
room_id: "r2", room_id: "r2",
}); });
await _rpsUpdated; await _rpsUpdated;
rerender(<RightPanel room={r2} resizeNotifier={resizeNotifier} />); rerender(
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>,
);
// After all that setup, now to the interesting part... // After all that setup, now to the interesting part...
// We want to verify that as we change to room 2, we should always have // We want to verify that as we change to room 2, we should always have

View file

@ -50,6 +50,7 @@ import { ElementWidgetCapabilities } from "../../../../src/stores/widgets/Elemen
import { ElementWidget } from "../../../../src/stores/widgets/StopGapWidget"; import { ElementWidget } from "../../../../src/stores/widgets/StopGapWidget";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import { ModuleRunner } from "../../../../src/modules/ModuleRunner"; import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
describe("AppTile", () => { describe("AppTile", () => {
let cli: MatrixClient; let cli: MatrixClient;
@ -153,7 +154,11 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods // Run initial render with room 1, and also running lifecycle methods
const renderResult = render( const renderResult = render(
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} /> <RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
); );
// Wait for RPS room 1 updates to fire // Wait for RPS room 1 updates to fire
@ -178,7 +183,11 @@ describe("AppTile", () => {
renderResult.rerender( renderResult.rerender(
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} /> <RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
); );
@ -214,7 +223,11 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods // Run initial render with room 1, and also running lifecycle methods
const renderResult = render( const renderResult = render(
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} /> <RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
); );
// Wait for RPS room 1 updates to fire // Wait for RPS room 1 updates to fire
@ -256,7 +269,11 @@ describe("AppTile", () => {
}); });
renderResult.rerender( renderResult.rerender(
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} /> <RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>, </MatrixClientContext.Provider>,
); );
await rpsUpdated2; await rpsUpdated2;

View file

@ -68,7 +68,7 @@ describe("PreferencesUserSettingsTab", () => {
const expectSetValueToHaveBeenCalled = ( const expectSetValueToHaveBeenCalled = (
name: string, name: string,
roomId: string | undefined, roomId: string | null,
level: SettingLevel, level: SettingLevel,
value: boolean, value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value); ) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);