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

Fix: set disabled state on invite to room button in memberlist #11893

Merged
merged 3 commits into from
Nov 21, 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
58 changes: 22 additions & 36 deletions src/components/views/rooms/MemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
RoomStateEvent,
User,
UserEvent,
JoinRule,
EventType,
ClientEvent,
} from "matrix-js-sdk/src/matrix";
Expand All @@ -54,6 +53,8 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature";
import PosthogTrackers from "../../../PosthogTrackers";
import { SDKContext } from "../../../contexts/SDKContext";
import { canInviteTo } from "../../../utils/room/canInviteTo";
import { inviteToRoom } from "../../../utils/room/inviteToRoom";

const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
Expand Down Expand Up @@ -132,9 +133,7 @@ export default class MemberList extends React.Component<IProps, IState> {
const cli = MatrixClientPeg.safeGet();
const room = cli.getRoom(this.props.roomId);

return (
!!room?.canInvite(cli.getSafeUserId()) || !!(room?.isSpaceRoom() && room.getJoinRule() === JoinRule.Public)
);
return !!room && canInviteTo(room);
}

private getMembersState(invitedMembers: Array<RoomMember>, joinedMembers: Array<RoomMember>): IState {
Expand Down Expand Up @@ -365,32 +364,25 @@ export default class MemberList extends React.Component<IProps, IState> {
let inviteButton: JSX.Element | undefined;

if (room?.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers)) {
let inviteButtonText = _t("room|invite_this_room");
if (room.isSpaceRoom()) {
inviteButtonText = _t("space|invite_this_space");
}
const inviteButtonText = room.isSpaceRoom() ? _t("space|invite_this_space") : _t("room|invite_this_room");

const button = (
<Button
size="sm"
kind="secondary"
className="mx_MemberList_invite"
onClick={this.onInviteButtonClick}
disabled={!this.state.canInvite}
>
<UserAddIcon width="1em" height="1em" />
{inviteButtonText}
</Button>
);

if (this.state.canInvite) {
inviteButton = (
<Button
size="sm"
kind="secondary"
className="mx_MemberList_invite"
onClick={this.onInviteButtonClick}
>
<UserAddIcon width="1em" height="1em" />
{inviteButtonText}
</Button>
);
inviteButton = button;
} else {
inviteButton = (
<Tooltip label={_t("member_list|invite_button_no_perms_tooltip")}>
<Button size="sm" kind="secondary" className="mx_MemberList_invite" onClick={() => {}}>
<UserAddIcon width="1em" height="1em" />
{inviteButtonText}
</Button>
</Tooltip>
);
inviteButton = <Tooltip label={_t("member_list|invite_button_no_perms_tooltip")}>{button}</Tooltip>;
}
}

Expand Down Expand Up @@ -454,15 +446,9 @@ export default class MemberList extends React.Component<IProps, IState> {
private onInviteButtonClick = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebRightPanelMemberListInviteButton", ev);

if (MatrixClientPeg.safeGet().isGuest()) {
dis.dispatch({ action: "require_registration" });
return;
}
const cli = MatrixClientPeg.safeGet();
const room = cli.getRoom(this.props.roomId)!;

// open the room inviter
dis.dispatch({
action: "view_invite",
roomId: this.props.roomId,
});
inviteToRoom(room);
};
}
106 changes: 105 additions & 1 deletion test/components/views/rooms/MemberList-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,37 @@ limitations under the License.
*/

import React from "react";
import { act, render, RenderResult, screen } from "@testing-library/react";
import { act, fireEvent, render, RenderResult, screen } from "@testing-library/react";
import { Room, MatrixClient, RoomState, RoomMember, User, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { compare } from "matrix-js-sdk/src/utils";
import { mocked, MockedObject } from "jest-mock";

import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import * as TestUtils from "../../../test-utils";
import MemberList from "../../../../src/components/views/rooms/MemberList";
import { SDKContext } from "../../../../src/contexts/SDKContext";
import { TestSdkContext } from "../../../TestSdkContext";
import {
filterConsole,
flushPromises,
getMockClientWithEventEmitter,
mockClientMethodsUser,
} from "../../../test-utils";
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";

jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));

function generateRoomId() {
return "!" + Math.random().toString().slice(2, 10) + ":domain";
}

describe("MemberList", () => {
filterConsole(
"Age for event was not available, using `now - origin_server_ts` as a fallback. If the device clock is not correct issues might occur.",
);
function createRoom(opts = {}) {
const room = new Room(generateRoomId(), client, client.getUserId()!);
if (opts) {
Expand Down Expand Up @@ -331,5 +347,93 @@ describe("MemberList", () => {
);
expect(await screen.findByText(/User's server unreachable/)).toBeInTheDocument();
});

describe("Invite button", () => {
const roomId = "!room:server.org";
let client!: MockedObject<MatrixClient>;
let room!: Room;

beforeEach(function () {
mocked(shouldShowComponent).mockReturnValue(true);
client = getMockClientWithEventEmitter({
...mockClientMethodsUser(),
getRoom: jest.fn(),
hasLazyLoadMembersEnabled: jest.fn(),
});
room = new Room(roomId, client, client.getSafeUserId());
client.getRoom.mockReturnValue(room);
});

afterEach(() => {
jest.restoreAllMocks();
});

const renderComponent = () => {
const context = new TestSdkContext();
context.client = client;
render(
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
onClose={jest.fn()}
onSearchQueryChanged={jest.fn()}
roomId={room.roomId}
/>
</SDKContext.Provider>,
);
};

it("does not render invite button when current user is not a member", async () => {
renderComponent();
await flushPromises();

expect(screen.queryByText("Invite to this room")).not.toBeInTheDocument();
});

it("does not render invite button UI customisation hides invites", async () => {
mocked(shouldShowComponent).mockReturnValue(false);
renderComponent();
await flushPromises();

expect(screen.queryByText("Invite to this room")).not.toBeInTheDocument();
});

it("renders disabled invite button when current user is a member but does not have rights to invite", async () => {
jest.spyOn(room, "getMyMembership").mockReturnValue("join");
jest.spyOn(room, "canInvite").mockReturnValue(false);

renderComponent();
await flushPromises();

// button rendered but disabled
expect(screen.getByText("Invite to this room")).toBeDisabled();
});

it("renders enabled invite button when current user is a member and has rights to invite", async () => {
jest.spyOn(room, "getMyMembership").mockReturnValue("join");
jest.spyOn(room, "canInvite").mockReturnValue(true);

renderComponent();
await flushPromises();

expect(screen.getByText("Invite to this room")).not.toBeDisabled();
});

it("opens room inviter on button click", async () => {
jest.spyOn(defaultDispatcher, "dispatch");
jest.spyOn(room, "getMyMembership").mockReturnValue("join");
jest.spyOn(room, "canInvite").mockReturnValue(true);

renderComponent();
await flushPromises();

fireEvent.click(screen.getByText("Invite to this room"));

expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "view_invite",
roomId,
});
});
});
});
});