Apply `strictNullChecks` to `src/components/views/rooms/*` (#10875)
* fix everything except notificationbadge * unit test ThirdPartyMemberInfo * fix RoomPreviewBar dm tests * lint * test PinnedEventTilepull/28788/head^2
parent
08c0f332b3
commit
74d30187a4
|
@ -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<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 });
|
||||
},
|
||||
|
@ -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={() => {
|
||||
|
|
|
@ -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<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 => {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -53,11 +53,12 @@ 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()!,
|
||||
|
@ -65,7 +66,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -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("<RoomPreviewBar />", () => {
|
|||
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("<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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
`;
|
|
@ -150,12 +150,12 @@ exports[`<RoomPreviewBar /> with an invite with an invited email when invitedEma
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`<RoomPreviewBar /> with an invite without an invited email for a dm room renders invite message to a non-dm room 1`] = `
|
||||
exports[`<RoomPreviewBar /> with an invite without an invited email for a dm room renders invite message 1`] = `
|
||||
<div
|
||||
class="mx_RoomPreviewBar_message"
|
||||
>
|
||||
<h3>
|
||||
Do you want to join RoomPreviewBar-test-room?
|
||||
Do you want to chat with @inviter:test.com?
|
||||
</h3>
|
||||
<p>
|
||||
<span
|
||||
|
@ -192,7 +192,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
|
|||
@inviter:test.com
|
||||
)
|
||||
</span>
|
||||
invited you
|
||||
wants to chat
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -207,7 +207,7 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
|
|||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Accept
|
||||
Start chatting
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ThirdPartyMemberInfo /> should render invite 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MemberInfo"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_name"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_MemberInfo_cancel"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Close"
|
||||
/>
|
||||
<h2>
|
||||
bob@bob.com
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MemberInfo_container"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_profile"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_profileField"
|
||||
>
|
||||
Invited by Alice DisplayName
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<ThirdPartyMemberInfo /> should render invite when room in not available 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MemberInfo"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_name"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_MemberInfo_cancel"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
title="Close"
|
||||
/>
|
||||
<h2>
|
||||
bob@bob.com
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MemberInfo_container"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_profile"
|
||||
>
|
||||
<div
|
||||
class="mx_MemberInfo_profileField"
|
||||
>
|
||||
Invited by Alice DisplayName
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in New Issue