Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Apply strictNullChecks to src/components/views/rooms/* #10875

Merged
merged 8 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/components/views/rooms/AppsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.

import React from "react";
import classNames from "classnames";
import { Resizable } from "re-resizable";
import { Resizable, Size } from "re-resizable";
import { Room } from "matrix-js-sdk/src/models/room";
import { IWidget } from "matrix-widget-api";

Expand Down Expand Up @@ -124,7 +124,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
Container.Top,
this.topApps()
.slice(1)
.map((_, i) => this.resizer.forHandleAt(i).size),
.map((_, i) => this.resizer.forHandleAt(i)!.size),
);
this.setState({ resizingHorizontal: false });
},
Expand Down Expand Up @@ -339,7 +339,9 @@ const PersistentVResizer: React.FC<IPersistentResizerProps> = ({

return (
<Resizable
size={{ height: Math.min(defaultHeight, maxHeight), width: undefined }}
// types do not support undefined height/width
// but resizable code checks specifically for undefined on Size prop
size={{ height: Math.min(defaultHeight, maxHeight), width: undefined } as unknown as Size}
minHeight={minHeight}
maxHeight={maxHeight}
onResizeStart={() => {
Expand Down
14 changes: 11 additions & 3 deletions src/components/views/rooms/EditMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { PosthogAnalytics } from "../../../PosthogAnalytics";
import { editorRoomKey, editorStateKey } from "../../../Editing";
import DocumentOffset from "../../../editor/offset";
import { attachMentions, attachRelation } from "./SendMessageComposer";
import { filterBoolean } from "../../../utils/arrays";

function getHtmlReplyFallback(mxEvent: MatrixEvent): string {
const html = mxEvent.getContent().formatted_body;
Expand Down Expand Up @@ -149,8 +150,14 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
this.dispatcherRef = dis.register(this.onAction);
}

private getRoom(): Room | null {
return this.props.mxClient.getRoom(this.props.editState.getEvent().getRoomId());
private getRoom(): Room {
const roomId = this.props.editState.getEvent().getRoomId();
const room = this.props.mxClient.getRoom(roomId);
// Something is very wrong if we encounter this
if (!room) {
throw new Error(`Cannot find room for event ${roomId}`);
}
return room;
}

private onKeyDown = (event: KeyboardEvent): void => {
Expand Down Expand Up @@ -411,7 +418,8 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
if (editState.hasEditorState()) {
// if restoring state from a previous editor,
// restore serialized parts from the state
parts = editState.getSerializedParts().map((p) => partCreator.deserializePart(p));
// (editState.hasEditorState() checks getSerializedParts is not null)
parts = filterBoolean<Part>(editState.getSerializedParts()!.map((p) => partCreator.deserializePart(p)));
} else {
// otherwise, either restore serialized parts from localStorage or parse the body of the event
const restoredParts = this.restoreStoredEditorState(partCreator);
Expand Down
4 changes: 4 additions & 0 deletions src/components/views/rooms/PinnedEventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export default class PinnedEventTile extends React.Component<IProps> {
public render(): React.ReactNode {
const sender = this.props.event.getSender();

if (!sender) {
throw new Error("Pinned event unexpectedly has no sender");
}

let unpinButton: JSX.Element | undefined;
if (this.props.onUnpinClicked) {
unpinButton = (
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/ReadReceiptMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat
return 0;
}

return info.top + info.parent.getBoundingClientRect().top;
return (info.top ?? 0) + info.parent.getBoundingClientRect().top;
}

private animateMarker(): void {
Expand Down
4 changes: 3 additions & 1 deletion src/components/views/rooms/RoomPreviewBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,9 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {

const isDM = this.isDMInvite();
if (isDM) {
title = _t("Do you want to chat with %(user)s?", { user: inviteMember.name });
title = _t("Do you want to chat with %(user)s?", {
user: inviteMember?.name ?? this.props.inviterName,
});
subTitle = [avatar, _t("<userName/> wants to chat", {}, { userName: () => inviterElement })];
primaryActionLabel = _t("Start chatting");
} else {
Expand Down
5 changes: 3 additions & 2 deletions src/components/views/rooms/ThirdPartyMemberInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,20 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
this.room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId());
const me = this.room?.getMember(MatrixClientPeg.get().getUserId()!);
const powerLevels = this.room?.currentState.getStateEvents("m.room.power_levels", "");
const senderId = this.props.event.getSender()!;

let kickLevel = powerLevels ? powerLevels.getContent().kick : 50;
if (typeof kickLevel !== "number") kickLevel = 50;

const sender = this.room?.getMember(this.props.event.getSender());
const sender = this.room?.getMember(senderId);

this.state = {
stateKey: this.props.event.getStateKey()!,
roomId: this.props.event.getRoomId()!,
displayName: this.props.event.getContent().display_name,
invited: true,
canKick: me ? me.powerLevel > kickLevel : false,
senderName: sender?.name ?? this.props.event.getSender(),
senderName: sender?.name ?? senderId,
};
}

Expand Down
6 changes: 6 additions & 0 deletions test/components/views/rooms/EditMessageComposer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ describe("<EditMessageComposer/>", () => {
expect(mockClient.sendMessage).toHaveBeenCalledWith(editedEvent.getRoomId()!, null, expectedBody);
});

it("should throw when room for message is not found", () => {
mockClient.getRoom.mockReturnValue(null);
const editState = new EditorStateTransfer(editedEvent);
expect(() => getComponent(editState)).toThrow("Cannot find room for event !abc:test");
});

describe("createEditContent", () => {
it("sends plaintext messages correctly", () => {
const model = new EditorModel([], createPartCreator());
Expand Down
72 changes: 72 additions & 0 deletions test/components/views/rooms/PinnedEventTile-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { render } from "@testing-library/react";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";

import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import PinnedEventTile from "../../../../src/components/views/rooms/PinnedEventTile";
import { getMockClientWithEventEmitter } from "../../../test-utils";

describe("<PinnedEventTile />", () => {
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const mockClient = getMockClientWithEventEmitter({
getRoom: jest.fn(),
});
const room = new Room(roomId, mockClient, userId);
const permalinkCreator = new RoomPermalinkCreator(room);

const getComponent = (event: MatrixEvent) =>
render(<PinnedEventTile permalinkCreator={permalinkCreator} event={event} />);

beforeEach(() => {
mockClient.getRoom.mockReturnValue(room);
});

it("should render pinned event", () => {
const pin1 = new MatrixEvent({
type: "m.room.message",
sender: userId,
content: {
body: "First pinned message",
msgtype: "m.text",
},
room_id: roomId,
origin_server_ts: 0,
});

const { container } = getComponent(pin1);

expect(container).toMatchSnapshot();
});

it("should throw when pinned event has no sender", () => {
const pin1 = new MatrixEvent({
type: "m.room.message",
sender: undefined,
content: {
body: "First pinned message",
msgtype: "m.text",
},
room_id: roomId,
origin_server_ts: 0,
});

expect(() => getComponent(pin1)).toThrow("Pinned event unexpectedly has no sender");
});
});
6 changes: 3 additions & 3 deletions test/components/views/rooms/RoomPreviewBar-test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -212,7 +212,7 @@ describe("<RoomPreviewBar />", () => {
const userMemberWithDmInvite = makeMockRoomMember({
userId,
membership: "invite",
memberContent: { is_direct: true },
memberContent: { is_direct: true, membership: "invite" },
});
const inviterMember = makeMockRoomMember({
userId: inviterUserId,
Expand Down Expand Up @@ -299,7 +299,7 @@ describe("<RoomPreviewBar />", () => {
onRejectClick.mockClear();
});

it("renders invite message to a non-dm room", () => {
it("renders invite message", () => {
const component = getComponent({ inviterName, room });
expect(getMessage(component)).toMatchSnapshot();
});
Expand Down
75 changes: 75 additions & 0 deletions test/components/views/rooms/ThirdPartyMemberInfo-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { render, screen } from "@testing-library/react";
import { EventType, IEvent, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";

import ThirdPartyMemberInfo from "../../../../src/components/views/rooms/ThirdPartyMemberInfo";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";

describe("<ThirdPartyMemberInfo />", () => {
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
getRoom: jest.fn(),
});

// make invite event with defaults
const makeInviteEvent = (props: Partial<IEvent> = {}): MatrixEvent =>
new MatrixEvent({
type: EventType.RoomThirdPartyInvite,
state_key: "123456",
sender: userId,
room_id: roomId,
content: {
display_name: "[email protected]",
key_validity_url: "https://isthiskeyvalid.org",
public_key: "abc123",
},
...props,
});
const defaultEvent = makeInviteEvent();

const getComponent = (event: MatrixEvent = defaultEvent) => render(<ThirdPartyMemberInfo event={event} />);
const room = new Room(roomId, mockClient, userId);
const aliceMember = new RoomMember(roomId, userId);
aliceMember.name = "Alice DisplayName";

beforeEach(() => {
jest.spyOn(room, "getMember").mockImplementation((id) => (id === userId ? aliceMember : null));
mockClient.getRoom.mockClear().mockReturnValue(room);
});

it("should render invite", () => {
const { container } = getComponent();
expect(container).toMatchSnapshot();
});

it("should render invite when room in not available", () => {
const event = makeInviteEvent({ room_id: "not_available" });
const { container } = getComponent(event);
expect(container).toMatchSnapshot();
});

it("should use inviter's id when room member is not available", () => {
const event = makeInviteEvent({ sender: "@charlie:server.org" });
getComponent(event);

expect(screen.getByText("Invited by @charlie:server.org")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<PinnedEventTile /> should render pinned event 1`] = `
<div>
<div
class="mx_PinnedEventTile"
>
<span
class="mx_BaseAvatar mx_PinnedEventTile_senderAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
>
A
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="data:image/png;base64,00"
style="width: 24px; height: 24px;"
/>
</span>
<span
class="mx_PinnedEventTile_sender mx_Username_color6"
>
@alice:server.org
</span>
<div
class="mx_PinnedEventTile_message"
>
<div
class="mx_MTextBody mx_EventTile_content"
>
<span
class="mx_EventTile_body"
dir="auto"
>
First pinned message
</span>
</div>
</div>
<div
class="mx_PinnedEventTile_footer"
>
<span
class="mx_MessageTimestamp mx_PinnedEventTile_timestamp"
>
Thu, Jan 1 1970 00:00:00
</span>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
role="button"
tabindex="0"
>
View message
</div>
</div>
</div>
</div>
`;
Loading