Add more tests

This commit is contained in:
R Midhun Suresh
2024-12-23 01:36:02 +05:30
parent cca636af56
commit 1b359aef5a
5 changed files with 108 additions and 50 deletions

View File

@@ -115,6 +115,7 @@ export interface MemberListViewState {
search: (searchQuery: string) => void;
isPresenceEnabled: boolean;
shouldShowInvite: boolean;
shouldShowSearch: boolean;
isLoading: boolean;
canInvite: boolean;
onInviteButtonClick: (ev: ButtonEvent) => void;
@@ -178,13 +179,16 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
[sdkContext.memberListStore],
);
// Determines whether the rendered invite button is enabled or disabled
const getCanUserInviteToThisRoom = useCallback((): boolean => !!room && canInviteTo(room), [room]);
const [canInvite, setCanInvite] = useState<boolean>(getCanUserInviteToThisRoom());
const shouldShowInvite = useMemo(() => {
return room?.getMyMembership() == KnownMembership.Join && shouldShowComponent(UIComponent.InviteUsers);
}, [room]);
// Determines whether the invite button should be shown or not.
const getShouldShowInvite = useCallback(
(): boolean => room?.getMyMembership() === KnownMembership.Join && shouldShowComponent(UIComponent.InviteUsers),
[room],
);
const [shouldShowInvite, setShouldShowInvite] = useState<boolean>(getShouldShowInvite());
const onInviteButtonClick = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebRightPanelMemberListInviteButton", ev);
@@ -219,6 +223,8 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
if (membership === KnownMembership.Join && oldMembership !== KnownMembership.Join) {
// we just joined the room, load the member list
loadMembers();
const newShouldShowInvite = getShouldShowInvite();
setShouldShowInvite(newShouldShowInvite);
}
});
@@ -251,6 +257,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState {
isPresenceEnabled,
isLoading,
onInviteButtonClick,
shouldShowSearch: memberCount >= 20,
canInvite,
};
}

View File

@@ -37,6 +37,51 @@ const OptionalTooltip: React.FC<TooltipProps> = ({ canInvite, children }) => {
return <Tooltip description={_t("member_list|invite_button_no_perms_tooltip")}>{children}</Tooltip>;
};
const InviteButton: React.FC<Props> = ({ vm }) => {
const shouldShowInvite = vm.shouldShowInvite;
const shouldShowSearch = vm.shouldShowSearch;
const disabled = !vm.canInvite;
if (!shouldShowInvite) {
// In this case, invite button should not be rendered.
return null;
}
if (shouldShowSearch) {
/// When rendered alongside a search box, the invite button is just an icon.
return (
<OptionalTooltip canInvite={vm.canInvite}>
<Button
className="mx_MemberListHeaderView_invite_small"
kind="primary"
onClick={vm.onInviteButtonClick}
size="sm"
iconOnly={true}
Icon={InviteIcon}
disabled={disabled}
aria-label={_t("action|invite")}
/>
</OptionalTooltip>
);
}
// Without a search box, invite button is a full size button.
return (
<OptionalTooltip canInvite={vm.canInvite}>
<Button
kind="secondary"
size="sm"
Icon={UserAddIcon}
className="mx_MemberListHeaderView_invite_large"
disabled={!vm.canInvite}
onClick={vm.onInviteButtonClick}
>
{_t("action|invite")}
</Button>
</OptionalTooltip>
);
};
/**
* This should be:
* A loading text with spinner while the memberlist loads.
@@ -67,49 +112,37 @@ function getHeaderLabelJSX(vm: MemberListViewState): React.ReactNode {
*/
const MemberListHeaderView: React.FC<Props> = (props: Props) => {
const vm = props.vm;
const memberCount = vm.memberCount;
const contentJSX =
memberCount < 20 ? (
<OptionalTooltip canInvite={vm.canInvite}>
<Button
kind="secondary"
size="sm"
Icon={UserAddIcon}
className="mx_MemberListHeaderView_invite_large"
disabled={!vm.canInvite}
onClick={vm.onInviteButtonClick}
>
{_t("action|invite")}
</Button>
</OptionalTooltip>
) : (
<>
let contentJSX: React.ReactNode;
if (vm.shouldShowSearch) {
// When we need to show the search box
contentJSX = (
<Flex justify="center" className="mx_MemberListHeaderView_container">
<Search
className="mx_MemberListHeaderView_search mx_no_textinput"
name="searchMembers"
placeholder={_t("member_list|filter_placeholder")}
onChange={(e) => vm.search((e as React.ChangeEvent<HTMLInputElement>).target.value)}
/>
<OptionalTooltip canInvite={vm.canInvite}>
<Button
className="mx_MemberListHeaderView_invite_small"
kind="primary"
onClick={vm.onInviteButtonClick}
size="sm"
iconOnly={true}
Icon={InviteIcon}
disabled={!vm.canInvite}
/>
</OptionalTooltip>
</>
<InviteButton vm={vm} />
</Flex>
);
} else if (!vm.shouldShowSearch && vm.shouldShowInvite) {
// When we don't need to show the search box but still need an invite button
contentJSX = (
<Flex justify="center" className="mx_MemberListHeaderView_container">
<InviteButton vm={vm} />
</Flex>
);
} else {
// No search box and no invite icon, so nothing to render!
contentJSX = null;
}
return (
<Flex className="mx_MemberListHeaderView" as="header" align="center" justify="space-between" direction="column">
{!vm.isLoading && (
<Flex justify="center" className="mx_MemberListHeaderView_container">
{contentJSX}
</Flex>
)}
{!vm.isLoading && contentJSX}
<Text as="div" size="sm" weight="semibold" className="mx_MemberListHeaderView_label">
{getHeaderLabelJSX(vm)}
</Text>

View File

@@ -56,7 +56,6 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
ariaLabelledBy="memberlist-panel-tab"
role="tabpanel"
header={_t("common|people")}
// footer={footer}
onClose={props.onClose}
>
<Flex align="stretch" direction="column" className="mx_MemberListView_container">

View File

@@ -8,8 +8,8 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fireEvent, screen } from "jest-matrix-react";
import { RoomMember, User } from "matrix-js-sdk/src/matrix";
import { act, fireEvent, screen } from "jest-matrix-react";
import { RoomMember, User, RoomEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { mocked } from "jest-mock";
@@ -32,10 +32,29 @@ jest.mock("react-virtualized", () => {
jest.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockReturnValue(1500);
jest.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockReturnValue(1500);
describe("Does not render invite button in memberlist header", () => {
it("Does not render invite button when user is not a member", async () => {
await renderMemberList(true, (room) => room.updateMyMembership(KnownMembership.Leave));
expect(screen.queryByRole("button", { name: "Invite" })).toBeNull();
});
it("does not render invite button UI customisation hides invites", async () => {
mocked(shouldShowComponent).mockReturnValue(false);
const { client, memberListRoom } = await renderMemberList(true);
// Needs this specific event...
act(() => {
client.emit(RoomEvent.MyMembership, memberListRoom, KnownMembership.Join, KnownMembership.Invite);
});
await new Promise((r) => setTimeout(r, 1000));
expect(screen.queryByRole("button", { name: "Invite" })).toBeNull();
});
});
describe("MemberListHeaderView", () => {
let rendered: Rendered;
beforeEach(async function () {
mocked(shouldShowComponent).mockReturnValue(true);
rendered = await renderMemberList(true);
});
@@ -70,15 +89,10 @@ describe("MemberListHeaderView", () => {
jest.restoreAllMocks();
});
it("Does not render invite button when user is not a member", async () => {});
it("does not render invite button UI customisation hides invites", async () => {});
it("Renders disabled invite button when current user is a member but does not have rights to invite", async () => {
const { memberListRoom, reRender } = rendered;
jest.spyOn(memberListRoom, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(memberListRoom, "canInvite").mockReturnValue(false);
mocked(shouldShowComponent).mockReturnValue(true);
await reRender();
expect(screen.getByRole("button", { name: "Invite" })).toHaveAttribute("aria-disabled", "true");
});
@@ -87,7 +101,6 @@ describe("MemberListHeaderView", () => {
const { memberListRoom, reRender } = rendered;
jest.spyOn(memberListRoom, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(memberListRoom, "canInvite").mockReturnValue(true);
mocked(shouldShowComponent).mockReturnValue(true);
await reRender();
expect(screen.getByRole("button", { name: "Invite" })).not.toHaveAttribute("aria-disabled", "true");
});
@@ -95,9 +108,7 @@ describe("MemberListHeaderView", () => {
it("Opens room inviter on button click", async () => {
const { memberListRoom, reRender } = rendered;
jest.spyOn(defaultDispatcher, "dispatch");
jest.spyOn(memberListRoom, "getMyMembership").mockReturnValue(KnownMembership.Join);
jest.spyOn(memberListRoom, "canInvite").mockReturnValue(true);
mocked(shouldShowComponent).mockReturnValue(true);
await reRender();
fireEvent.click(screen.getByRole("button", { name: "Invite" }));

View File

@@ -22,6 +22,7 @@ import MatrixClientContext from "../../../../../../src/contexts/MatrixClientCont
export function createRoom(client: MatrixClient, opts = {}) {
const roomId = "!" + Math.random().toString().slice(2, 10) + ":domain";
const room = new Room(roomId, client, client.getUserId()!);
room.updateMyMembership(KnownMembership.Join);
if (opts) {
Object.assign(room, opts);
}
@@ -38,7 +39,11 @@ export type Rendered = {
reRender: () => Promise<void>;
};
export async function renderMemberList(enablePresence: boolean, usersPerLevel: number = 2): Promise<Rendered> {
export async function renderMemberList(
enablePresence: boolean,
roomSetup?: (room: Room) => void,
usersPerLevel: number = 2,
): Promise<Rendered> {
TestUtils.stubClient();
const client = MatrixClientPeg.safeGet();
client.hasLazyLoadMembersEnabled = () => false;
@@ -47,6 +52,9 @@ export async function renderMemberList(enablePresence: boolean, usersPerLevel: n
const memberListRoom = createRoom(client);
expect(memberListRoom.roomId).toBeTruthy();
// Give the test an opportunity to make changes to room before first render
roomSetup?.(memberListRoom);
// Make users
const adminUsers = [];
const moderatorUsers = [];