Add face pile to rooms (#11356)

* Add face pile to rooms

* Migrate FacePile to use Compound

* Fix CI

* Use FacePile component in room header

* Add facepile tests

* Make dead code CI happy

* Lint

* Fix tests

* Fix CSS selectors

* Update room face pile snapshot

* Use useMemo instead of useState and useEffect

* Remove unused imports

* Update snapshot

* Update snapshot
This commit is contained in:
Germain 2023-08-30 18:55:02 +01:00 committed by GitHub
parent af268b4a03
commit dc70ea5059
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 445 additions and 131 deletions

View file

@ -189,10 +189,6 @@ limitations under the License.
.mx_FacePile {
display: inline-block;
.mx_FacePile_faces {
cursor: pointer;
}
}
.mx_SpaceRoomView_landing_inviteButton,

View file

@ -14,53 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_FacePile {
display: flex;
align-items: center;
.mx_FacePile_more {
position: relative;
border-radius: 100%;
width: 30px;
height: 30px;
background-color: $spacePanel-bg-color;
.mx_FacePile_faces {
display: inline-flex;
flex-direction: row-reverse;
vertical-align: middle;
margin: 0 -1px; /* to cancel out the border on the edges */
/* Overlap the children */
> * + * {
margin-right: -8px;
}
.mx_BaseAvatar {
border: 1px solid var(--facepile-background, $background);
}
.mx_FacePile_more {
position: relative;
border-radius: 100%;
width: 30px;
height: 30px;
background-color: $spacePanel-bg-color;
&::before {
content: "";
z-index: 1;
position: absolute;
top: 0;
left: 0;
height: inherit;
width: inherit;
background: $tertiary-content;
mask-position: center;
mask-size: 20px;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/element-icons/room/ellipsis.svg");
}
}
}
.mx_FacePile_summary {
margin-left: 12px;
font: var(--cpd-font-body-md-regular);
line-height: $font-24px;
color: $tertiary-content;
&::before {
content: "";
z-index: 1;
position: absolute;
top: 0;
left: 0;
height: inherit;
width: inherit;
background: $tertiary-content;
mask-position: center;
mask-size: 20px;
mask-repeat: no-repeat;
mask-image: url("$(res)/img/element-icons/room/ellipsis.svg");
}
}
.mx_FacePile_summary {
margin-left: 12px;
font: var(--cpd-font-body-md-regular);
line-height: $font-24px;
color: $tertiary-content;
}

View file

@ -23,6 +23,7 @@ limitations under the License.
.mx_RoomHeader_info {
cursor: pointer;
flex: 1;
}
.mx_RoomHeader_topic {
@ -45,3 +46,19 @@ limitations under the License.
height: calc($font-13px * 1.5);
opacity: 1;
}
.mx_RoomHeader .mx_FacePile {
color: $secondary-content;
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
border-radius: 9999px;
padding: var(--cpd-space-1-5x);
cursor: pointer;
user-select: none;
&:hover {
color: $primary-content;
background: var(--cpd-color-bg-subtle-primary);
}
}

View file

@ -16,27 +16,27 @@ limitations under the License.
import React, { FC, HTMLAttributes, ReactNode } from "react";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { AvatarStack, Tooltip } from "@vector-im/compound-web";
import MemberAvatar from "../avatars/MemberAvatar";
import TooltipTarget from "./TooltipTarget";
import TextWithTooltip from "./TextWithTooltip";
interface IProps extends HTMLAttributes<HTMLSpanElement> {
members: RoomMember[];
size: string;
overflow: boolean;
tooltip?: ReactNode;
tooltipLabel?: string;
tooltipShortcut?: string;
children?: ReactNode;
}
const FacePile: FC<IProps> = ({ members, size, overflow, tooltip, children, ...props }) => {
const FacePile: FC<IProps> = ({ members, size, overflow, tooltipLabel, tooltipShortcut, children, ...props }) => {
const faces = members.map(
tooltip
tooltipLabel
? (m) => <MemberAvatar key={m.userId} member={m} size={size} hideTitle />
: (m) => (
<TooltipTarget key={m.userId} label={m.name}>
<Tooltip key={m.userId} label={m.name} shortcut={tooltipShortcut}>
<MemberAvatar member={m} size={size} viewUserOnClick={!props.onClick} hideTitle />
</TooltipTarget>
</Tooltip>
),
);
@ -47,18 +47,20 @@ const FacePile: FC<IProps> = ({ members, size, overflow, tooltip, children, ...p
</>
);
return (
<div {...props} className="mx_FacePile">
{tooltip ? (
<TextWithTooltip class="mx_FacePile_faces" tooltip={tooltip}>
{pileContents}
</TextWithTooltip>
) : (
<div className="mx_FacePile_faces">{pileContents}</div>
)}
const content = (
<div className="mx_FacePile">
<AvatarStack>{pileContents}</AvatarStack>
{children}
</div>
);
return tooltipLabel ? (
<Tooltip label={tooltipLabel} shortcut={tooltipShortcut}>
{content}
</Tooltip>
) : (
content
);
};
export default FacePile;

View file

@ -63,21 +63,21 @@ const RoomFacePile: FC<IProps> = ({ room, onlyKnownUsers = true, numShown = DEFA
.reverse()
.join(", ");
const tooltip = (
<div>
<div className="mx_Tooltip_title">
{props.onClick ? _t("View all %(count)s members", { count }) : _t("%(count)s members", { count })}
</div>
<div className="mx_Tooltip_sub">
{isJoined
? _t("Including you, %(commaSeparatedMembers)s", { commaSeparatedMembers })
: _t("Including %(commaSeparatedMembers)s", { commaSeparatedMembers })}
</div>
</div>
);
return (
<FacePile members={shownMembers} size="28px" overflow={members.length > numShown} tooltip={tooltip} {...props}>
<FacePile
members={shownMembers}
size="28px"
overflow={members.length > numShown}
tooltipLabel={
props.onClick ? _t("View all %(count)s members", { count }) : _t("%(count)s members", { count })
}
tooltipShortcut={
isJoined
? _t("Including you, %(commaSeparatedMembers)s", { commaSeparatedMembers })
: _t("Including %(commaSeparatedMembers)s", { commaSeparatedMembers })
}
{...props}
>
{onlyKnownUsers && (
<span className="mx_FacePile_summary">
{_t("%(count)s people you know have already joined", { count: members.length })}

View file

@ -21,14 +21,18 @@ import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/v
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { EventType } from "matrix-js-sdk/src/matrix";
import type { Room } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
import { useRoomName } from "../../../hooks/useRoomName";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { useTopic } from "../../../hooks/room/useTopic";
import { useAccountData } from "../../../hooks/useAccountData";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMembers";
import { _t, getCurrentLanguage } from "../../../languageHandler";
import { Flex } from "../../utils/Flex";
import { Box } from "../../utils/Box";
import { useRoomCallStatus } from "../../../hooks/room/useRoomCallStatus";
@ -41,6 +45,7 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
import SdkConfig from "../../../SdkConfig";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import FacePile from "../elements/FacePile";
/**
* A helper to transform a notification color to the what the Compound Icon Button
@ -67,9 +72,24 @@ function showOrHidePanel(phase: RightPanelPhases): void {
}
export default function RoomHeader({ room }: { room: Room }): JSX.Element {
const client = useMatrixClientContext();
const roomName = useRoomName(room);
const roomTopic = useTopic(room);
const members = useRoomMembers(room);
const memberCount = useRoomMemberCount(room);
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
const isDirectMessage = useMemo(() => {
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
if (dmRoomList.includes(room?.roomId ?? "")) {
return true;
}
}
return false;
}, [directRoomsList, room?.roomId]);
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
const groupCallsEnabled = useFeatureEnabled("feature_group_calls");
@ -119,10 +139,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
gap="var(--cpd-space-3x)"
className="mx_RoomHeader light-panel"
onClick={() => {
const rightPanel = RightPanelStore.instance;
rightPanel.isOpen
? rightPanel.togglePanel(null)
: rightPanel.setCard({ phase: RightPanelPhases.RoomSummary });
showOrHidePanel(RightPanelPhases.RoomSummary);
}}
>
<DecoratedRoomAvatar room={room} size="40px" displayBadge={false} />
@ -170,7 +187,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
onClick={() => {
showOrHidePanel(RightPanelPhases.ThreadPanel);
}}
title={_t("Threads")}
title={_t("common|threads")}
>
<ThreadsIcon />
</IconButton>
@ -184,6 +201,27 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
<NotificationsIcon />
</IconButton>
</Flex>
{!isDirectMessage && (
<BodyText
as="div"
size="sm"
weight="medium"
aria-label={_t("%(count)s members", { count: memberCount })}
onClick={(e: React.MouseEvent) => {
showOrHidePanel(RightPanelPhases.RoomMemberList);
e.stopPropagation();
}}
>
<FacePile
className="mx_RoomHeader_members"
members={members.slice(0, 3)}
size="20px"
overflow={false}
>
{memberCount.toLocaleString(getCurrentLanguage())}
</FacePile>
</BodyText>
)}
</Flex>
);
}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { useCallback, useState } from "react";
import { ClientEvent, MatrixClient, MatrixEvent, Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { useTypedEventEmitter } from "./useEventEmitter";
@ -38,17 +38,20 @@ export const useAccountData = <T extends {}>(cli: MatrixClient, eventType: strin
};
// Hook to simplify listening to Matrix room account data
export const useRoomAccountData = <T extends {}>(room: Room, eventType: string): T => {
const [value, setValue] = useState<T | undefined>(() => tryGetContent<T>(room.getAccountData(eventType)));
// Currently not used, commenting out otherwise the dead code CI is unhappy.
// But this code is valid and probably will be needed.
const handler = useCallback(
(event) => {
if (event.getType() !== eventType) return;
setValue(event.getContent());
},
[eventType],
);
useTypedEventEmitter(room, RoomEvent.AccountData, handler);
// export const useRoomAccountData = <T extends {}>(room: Room, eventType: string): T => {
// const [value, setValue] = useState<T | undefined>(() => tryGetContent<T>(room.getAccountData(eventType)));
return value || ({} as T);
};
// const handler = useCallback(
// (event) => {
// if (event.getType() !== eventType) return;
// setValue(event.getContent());
// },
// [eventType],
// );
// useTypedEventEmitter(room, RoomEvent.AccountData, handler);
// return value || ({} as T);
// };

View file

@ -1821,16 +1821,15 @@
"Room %(name)s": "Room %(name)s",
"Recently visited rooms": "Recently visited rooms",
"No recently visited rooms": "No recently visited rooms",
"Threads": "Threads",
"%(count)s members": {
"other": "%(count)s members",
"one": "%(count)s member"
},
"Video room": "Video room",
"Public space": "Public space",
"Public room": "Public room",
"Private space": "Private space",
"Private room": "Private room",
"%(count)s members": {
"other": "%(count)s members",
"one": "%(count)s member"
},
"Start new chat": "Start new chat",
"Invite to space": "Invite to space",
"You do not have permissions to invite people to this space": "You do not have permissions to invite people to this space",

View file

@ -23,7 +23,7 @@ describe("<FacePile />", () => {
const member = mkRoomMember("123", "456", "join");
const { asFragment } = render(
<FacePile members={[member]} size="36px" overflow={false} tooltip={<>tooltip</>} />,
<FacePile members={[member]} size="36px" overflow={false} tooltipLabel="tooltip" />,
);
expect(asFragment()).toMatchSnapshot();

View file

@ -0,0 +1,38 @@
/*
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 { render } from "@testing-library/react";
import React from "react";
import { mkRoom, mkRoomMember, stubClient, withClientContextRenderOptions } from "../../../test-utils";
import RoomFacePile from "../../../../src/components/views/elements/RoomFacePile";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
describe("<RoomFacePile />", () => {
it("renders", () => {
const cli = stubClient();
DMRoomMap.makeShared(cli);
const room = mkRoom(cli, "!123");
jest.spyOn(room, "getJoinedMembers").mockReturnValue([mkRoomMember(room.roomId, "@bob:example.org", "join")]);
const { asFragment } = render(
<RoomFacePile onlyKnownUsers={false} room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -4,11 +4,10 @@ exports[`<FacePile /> renders with a tooltip 1`] = `
<DocumentFragment>
<div
class="mx_FacePile"
data-state="closed"
>
<div
aria-describedby="mx_TooltipTarget_vY7Q4uEh"
class="mx_TextWithTooltip_target mx_FacePile_faces"
tabindex="0"
class="_stacked-avatars_2lhia_116"
>
<span
class="_avatar_2lhia_17 mx_BaseAvatar _avatar-imageless_2lhia_56"

View file

@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RoomFacePile /> renders 1`] = `
<DocumentFragment>
<div
class="mx_FacePile"
data-state="closed"
>
<div
class="_stacked-avatars_2lhia_116"
>
<span
class="_avatar_2lhia_17 mx_BaseAvatar _avatar-imageless_2lhia_56"
data-color="8"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 28px;"
title=""
>
b
</span>
</div>
</div>
</DocumentFragment>
`;

View file

@ -15,12 +15,12 @@ limitations under the License.
*/
import React from "react";
import { getAllByTitle, getByText, getByTitle, render, screen } from "@testing-library/react";
import { Room, EventType, MatrixEvent, PendingEventOrdering, MatrixCall } from "matrix-js-sdk/src/matrix";
import userEvent from "@testing-library/user-event";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import { EventType, MatrixEvent, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix";
import { getAllByTitle, getByLabelText, getByText, getByTitle, render, screen } from "@testing-library/react";
import { stubClient } from "../../../test-utils";
import { mkEvent, stubClient, withClientContextRenderOptions } from "../../../test-utils";
import RoomHeader from "../../../../src/components/views/rooms/RoomHeader";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -33,7 +33,7 @@ import dispatcher from "../../../../src/dispatcher/dispatcher";
import { CallStore } from "../../../../src/stores/CallStore";
import { Call, ElementCall } from "../../../../src/models/Call";
describe("Roomeader", () => {
describe("RoomHeader", () => {
let room: Room;
const ROOM_ID = "!1:example.org";
@ -57,7 +57,10 @@ describe("Roomeader", () => {
});
it("renders the room header", () => {
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(container).toHaveTextContent(ROOM_ID);
});
@ -75,26 +78,129 @@ describe("Roomeader", () => {
});
await room.addLiveEvents([roomTopic]);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(container).toHaveTextContent(TOPIC);
});
it("opens the room summary", async () => {
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
await userEvent.click(getByText(container, ROOM_ID));
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomSummary });
});
it("does not show the face pile for DMs", () => {
const client = MatrixClientPeg.get()!;
jest.spyOn(client, "getAccountData").mockReturnValue(
mkEvent({
event: true,
type: EventType.Direct,
user: client.getSafeUserId(),
content: {
"user@example.com": [room.roomId],
},
}),
);
room.getJoinedMembers = jest.fn().mockReturnValue([
{
userId: "@me:example.org",
name: "Member",
rawDisplayName: "Member",
roomId: room.roomId,
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
]);
const { asFragment } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(asFragment()).toMatchSnapshot();
});
it("shows a face pile for rooms", async () => {
const members = [
{
userId: "@me:example.org",
name: "Member",
rawDisplayName: "Member",
roomId: room.roomId,
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
{
userId: "@you:example.org",
name: "Member",
rawDisplayName: "Member",
roomId: room.roomId,
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
{
userId: "@them:example.org",
name: "Member",
rawDisplayName: "Member",
roomId: room.roomId,
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
{
userId: "@bot:example.org",
name: "Bot user",
rawDisplayName: "Bot user",
roomId: room.roomId,
membership: "join",
getAvatarUrl: () => "mxc://avatar.url/image.png",
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
];
room.currentState.setJoinedMemberCount(members.length);
room.getJoinedMembers = jest.fn().mockReturnValue(members);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(container).toHaveTextContent("4");
const facePile = getByLabelText(container, "4 members");
expect(facePile).toHaveTextContent("4");
await userEvent.click(facePile);
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomMemberList });
});
it("opens the thread panel", async () => {
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
await userEvent.click(getByTitle(container, "Threads"));
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel });
});
it("opens the notifications panel", async () => {
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
await userEvent.click(getByTitle(container, "Notifications"));
expect(setCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.NotificationPanel });
@ -103,7 +209,10 @@ describe("Roomeader", () => {
describe("groups call disabled", () => {
it("you can't call if you're alone", () => {
mockRoomMembers(room, 1);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
for (const button of getAllByTitle(container, "There's no one here to call")) {
expect(button).toBeDisabled();
}
@ -111,7 +220,10 @@ describe("Roomeader", () => {
it("you can call when you're two in the room", async () => {
mockRoomMembers(room, 2);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
const voiceButton = getByTitle(container, "Voice call");
const videoButton = getByTitle(container, "Video call");
expect(voiceButton).not.toBeDisabled();
@ -132,7 +244,10 @@ describe("Roomeader", () => {
// The JS-SDK does not export the class `MatrixCall` only the type
{} as MatrixCall,
);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
for (const button of getAllByTitle(container, "Ongoing call")) {
expect(button).toBeDisabled();
}
@ -141,7 +256,10 @@ describe("Roomeader", () => {
it("can calls in large rooms if able to edit widgets", () => {
mockRoomMembers(room, 10);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(getByTitle(container, "Voice call")).not.toBeDisabled();
expect(getByTitle(container, "Video call")).not.toBeDisabled();
@ -150,7 +268,10 @@ describe("Roomeader", () => {
it("disable calls in large rooms by default", () => {
mockRoomMembers(room, 10);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(getByTitle(container, "You do not have permission to start voice calls")).toBeDisabled();
expect(getByTitle(container, "You do not have permission to start video calls")).toBeDisabled();
});
@ -166,7 +287,10 @@ describe("Roomeader", () => {
// allow element calls
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(screen.queryByTitle("Voice call")).toBeNull();
@ -187,7 +311,10 @@ describe("Roomeader", () => {
jest.spyOn(CallStore.instance, "getCall").mockReturnValue({} as Call);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
expect(getByTitle(container, "Ongoing call")).toBeDisabled();
});
@ -197,7 +324,10 @@ describe("Roomeader", () => {
// The JS-SDK does not export the class `MatrixCall` only the type
{} as MatrixCall,
);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
for (const button of getAllByTitle(container, "Ongoing call")) {
expect(button).toBeDisabled();
}
@ -205,7 +335,10 @@ describe("Roomeader", () => {
it("can't call if you have no friends", () => {
mockRoomMembers(room, 1);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
for (const button of getAllByTitle(container, "There's no one here to call")) {
expect(button).toBeDisabled();
}
@ -213,7 +346,10 @@ describe("Roomeader", () => {
it("calls using legacy or jitsi", async () => {
mockRoomMembers(room, 2);
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
const voiceButton = getByTitle(container, "Voice call");
const videoButton = getByTitle(container, "Video call");
@ -236,7 +372,10 @@ describe("Roomeader", () => {
return false;
});
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
const voiceButton = getByTitle(container, "Voice call");
const videoButton = getByTitle(container, "Video call");
@ -260,7 +399,10 @@ describe("Roomeader", () => {
return false;
});
const { container } = render(<RoomHeader room={room} />);
const { container } = render(
<RoomHeader room={room} />,
withClientContextRenderOptions(MatrixClientPeg.get()!),
);
const voiceButton = getByTitle(container, "Voice call");
const videoButton = getByTitle(container, "Video call");

View file

@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RoomHeader does not show the face pile for DMs 1`] = `
<DocumentFragment>
<header
class="mx_Flex mx_RoomHeader light-panel"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x);"
>
<div
class="mx_DecoratedRoomAvatar"
>
<span
class="_avatar_2lhia_17 mx_BaseAvatar _avatar-imageless_2lhia_56"
data-color="7"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 40px;"
title=""
>
!
</span>
</div>
<div
class="mx_Box mx_RoomHeader_info mx_Box--flex"
style="--mx-box-flex: 1;"
>
<div
aria-level="1"
class="_font-body-lg-semibold_1g2sj_89"
dir="auto"
role="heading"
title="!1:example.org"
>
!1:example.org
</div>
</div>
<nav
class="mx_Flex"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-2x);"
>
<button
class="_icon-button_yvmcf_17"
disabled=""
style="--cpd-icon-button-size: 32px;"
title="There's no one here to call"
>
<div />
</button>
<button
class="_icon-button_yvmcf_17"
disabled=""
style="--cpd-icon-button-size: 32px;"
title="There's no one here to call"
>
<div />
</button>
<button
class="_icon-button_yvmcf_17"
style="--cpd-icon-button-size: 32px;"
title="Threads"
>
<div />
</button>
<button
class="_icon-button_yvmcf_17"
style="--cpd-icon-button-size: 32px;"
title="Notifications"
>
<div />
</button>
</nav>
</header>
</DocumentFragment>
`;