From 74d30187a46b288aefcc746a5c83645f537f551d Mon Sep 17 00:00:00 2001 From: Kerry Date: Mon, 22 May 2023 11:53:23 +1200 Subject: [PATCH] Apply `strictNullChecks` to `src/components/views/rooms/*` (#10875) * fix everything except notificationbadge * unit test ThirdPartyMemberInfo * fix RoomPreviewBar dm tests * lint * test PinnedEventTile --- src/components/views/rooms/AppsDrawer.tsx | 8 +- .../views/rooms/EditMessageComposer.tsx | 14 +++- .../views/rooms/PinnedEventTile.tsx | 4 + .../views/rooms/ReadReceiptMarker.tsx | 2 +- src/components/views/rooms/RoomPreviewBar.tsx | 4 +- .../views/rooms/ThirdPartyMemberInfo.tsx | 5 +- .../views/rooms/EditMessageComposer-test.tsx | 6 ++ .../views/rooms/PinnedEventTile-test.tsx | 72 ++++++++++++++++++ .../views/rooms/RoomPreviewBar-test.tsx | 6 +- .../views/rooms/ThirdPartyMemberInfo-test.tsx | 75 +++++++++++++++++++ .../PinnedEventTile-test.tsx.snap | 66 ++++++++++++++++ .../RoomPreviewBar-test.tsx.snap | 8 +- .../ThirdPartyMemberInfo-test.tsx.snap | 73 ++++++++++++++++++ 13 files changed, 326 insertions(+), 17 deletions(-) create mode 100644 test/components/views/rooms/PinnedEventTile-test.tsx create mode 100644 test/components/views/rooms/ThirdPartyMemberInfo-test.tsx create mode 100644 test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap create mode 100644 test/components/views/rooms/__snapshots__/ThirdPartyMemberInfo-test.tsx.snap diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 13e7707d2fa..378538af182 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -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"; @@ -124,7 +124,7 @@ export default class AppsDrawer extends React.Component { Container.Top, this.topApps() .slice(1) - .map((_, i) => this.resizer.forHandleAt(i).size), + .map((_, i) => this.resizer.forHandleAt(i)!.size), ); this.setState({ resizingHorizontal: false }); }, @@ -339,7 +339,9 @@ const PersistentVResizer: React.FC = ({ return ( { diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 9b581565ccb..6d561d3cd2f 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -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; @@ -149,8 +150,14 @@ class EditMessageComposer extends React.Component { @@ -411,7 +418,8 @@ class EditMessageComposer extends React.Component partCreator.deserializePart(p)); + // (editState.hasEditorState() checks getSerializedParts is not null) + parts = filterBoolean(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); diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 29ca93eaf48..6928641f625 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -71,6 +71,10 @@ export default class PinnedEventTile extends React.Component { 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 = ( diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index 6d453085d54..c23368e92a2 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -165,7 +165,7 @@ export default class ReadReceiptMarker extends React.PureComponent { 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(" wants to chat", {}, { userName: () => inviterElement })]; primaryActionLabel = _t("Start chatting"); } else { diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 233b1452f76..15645492571 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -53,11 +53,12 @@ export default class ThirdPartyMemberInfo extends React.Component kickLevel : false, - senderName: sender?.name ?? this.props.event.getSender(), + senderName: sender?.name ?? senderId, }; } diff --git a/test/components/views/rooms/EditMessageComposer-test.tsx b/test/components/views/rooms/EditMessageComposer-test.tsx index 3b56d2e151f..478830e3ba0 100644 --- a/test/components/views/rooms/EditMessageComposer-test.tsx +++ b/test/components/views/rooms/EditMessageComposer-test.tsx @@ -148,6 +148,12 @@ describe("", () => { 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()); diff --git a/test/components/views/rooms/PinnedEventTile-test.tsx b/test/components/views/rooms/PinnedEventTile-test.tsx new file mode 100644 index 00000000000..7febe0b4bd3 --- /dev/null +++ b/test/components/views/rooms/PinnedEventTile-test.tsx @@ -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("", () => { + 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(); + + 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"); + }); +}); diff --git a/test/components/views/rooms/RoomPreviewBar-test.tsx b/test/components/views/rooms/RoomPreviewBar-test.tsx index b6566132ac6..9bdde45192b 100644 --- a/test/components/views/rooms/RoomPreviewBar-test.tsx +++ b/test/components/views/rooms/RoomPreviewBar-test.tsx @@ -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. @@ -212,7 +212,7 @@ describe("", () => { const userMemberWithDmInvite = makeMockRoomMember({ userId, membership: "invite", - memberContent: { is_direct: true }, + memberContent: { is_direct: true, membership: "invite" }, }); const inviterMember = makeMockRoomMember({ userId: inviterUserId, @@ -299,7 +299,7 @@ describe("", () => { onRejectClick.mockClear(); }); - it("renders invite message to a non-dm room", () => { + it("renders invite message", () => { const component = getComponent({ inviterName, room }); expect(getMessage(component)).toMatchSnapshot(); }); diff --git a/test/components/views/rooms/ThirdPartyMemberInfo-test.tsx b/test/components/views/rooms/ThirdPartyMemberInfo-test.tsx new file mode 100644 index 00000000000..d1857ee53e3 --- /dev/null +++ b/test/components/views/rooms/ThirdPartyMemberInfo-test.tsx @@ -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("", () => { + 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 = {}): 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(); + 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(); + }); +}); diff --git a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap new file mode 100644 index 00000000000..6b260599998 --- /dev/null +++ b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render pinned event 1`] = ` +
+
+ + + + + + @alice:server.org + +
+
+ + First pinned message + +
+
+ +
+
+`; diff --git a/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap b/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap index d9631c7a0ca..80b5c894321 100644 --- a/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/RoomPreviewBar-test.tsx.snap @@ -150,12 +150,12 @@ exports[` with an invite with an invited email when invitedEma `; -exports[` with an invite without an invited email for a dm room renders invite message to a non-dm room 1`] = ` +exports[` with an invite without an invited email for a dm room renders invite message 1`] = `

- Do you want to join RoomPreviewBar-test-room? + Do you want to chat with @inviter:test.com?

with an invite without an invited email for a dm roo @inviter:test.com ) - invited you + wants to chat

@@ -207,7 +207,7 @@ exports[` with an invite without an invited email for a dm roo role="button" tabindex="0" > - Accept + Start chatting
should render invite 1`] = ` +
+
+
+
+

+ bob@bob.com +

+
+
+
+
+ Invited by Alice DisplayName +
+
+
+
+
+`; + +exports[` should render invite when room in not available 1`] = ` +
+
+
+
+

+ bob@bob.com +

+
+
+
+
+ Invited by Alice DisplayName +
+
+
+
+
+`;