Skip to content

Commit

Permalink
Apply strictNullChecks to src/components/views/rooms/* (#10875)
Browse files Browse the repository at this point in the history
* fix everything except notificationbadge

* unit test ThirdPartyMemberInfo

* fix RoomPreviewBar dm tests

* lint

* test PinnedEventTile
  • Loading branch information
Kerry committed May 21, 2023
1 parent 08c0f33 commit 74d3018
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 17 deletions.
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: "bob@bob.com",
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

0 comments on commit 74d3018

Please sign in to comment.