Add more tests
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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" }));
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user