Merge branch 'develop' into johannes/latest-room-in-space

This commit is contained in:
Johannes Marbach 2023-02-02 20:21:17 +01:00
commit f3fd027db4
40 changed files with 948 additions and 868 deletions

View file

@ -20,16 +20,22 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 24px; height: 24px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
class="mx_BaseAvatar_initial"
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 24px; height: 24px;"
/>
</span>
</div>
</div>
@ -113,16 +119,22 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 24px; height: 24px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
class="mx_BaseAvatar_initial"
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 24px; height: 24px;"
/>
</span>
</div>
</div>
@ -203,17 +215,23 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
aria-live="off"
class="mx_AccessibleButton mx_BaseAvatar"
role="button"
style="width: 52px; height: 52px;"
tabindex="0"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
class="mx_BaseAvatar_initial"
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 52px; height: 52px;"
/>
</span>
<h2>
@user:example.com
@ -296,16 +314,22 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 24px; height: 24px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
class="mx_BaseAvatar_initial"
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 24px; height: 24px;"
/>
</span>
</div>
</div>
@ -386,17 +410,23 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
aria-live="off"
class="mx_AccessibleButton mx_BaseAvatar"
role="button"
style="width: 52px; height: 52px;"
tabindex="0"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
class="mx_BaseAvatar_initial"
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 52px; height: 52px;"
/>
</span>
<h2>
@user:example.com
@ -551,16 +581,22 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 24px; height: 24px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 24px; height: 24px; font-size: 15.600000000000001px; line-height: 24px;"
class="mx_BaseAvatar_initial"
style="font-size: 15.600000000000001px; width: 24px; line-height: 24px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 24px; height: 24px;"
/>
</span>
</div>
</div>
@ -636,17 +672,23 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
aria-live="off"
class="mx_AccessibleButton mx_BaseAvatar"
role="button"
style="width: 52px; height: 52px;"
tabindex="0"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 52px; height: 52px; font-size: 33.800000000000004px; line-height: 52px;"
class="mx_BaseAvatar_initial"
style="font-size: 33.800000000000004px; width: 52px; line-height: 52px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 52px; height: 52px;"
/>
</span>
<h2>
@user:example.com

View file

@ -20,16 +20,22 @@ exports[`<UserMenu> when rendered should render as expected 1`] = `
<span
class="mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar"
role="presentation"
style="width: 32px; height: 32px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(54, 139, 214); width: 32px; height: 32px; font-size: 20.8px; line-height: 32px;"
class="mx_BaseAvatar_initial"
style="font-size: 20.8px; width: 32px; line-height: 32px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 32px; height: 32px;"
/>
</span>
</div>
</div>

View file

@ -1,201 +0,0 @@
/*
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 { fireEvent, render } from "@testing-library/react";
import { ClientEvent, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import React from "react";
import { act } from "react-dom/test-utils";
import { SyncState } from "matrix-js-sdk/src/sync";
import type { MatrixClient } from "matrix-js-sdk/src/client";
import RoomContext from "../../../../src/contexts/RoomContext";
import { getRoomContext } from "../../../test-utils/room";
import { stubClient } from "../../../test-utils/test-utils";
import BaseAvatar from "../../../../src/components/views/avatars/BaseAvatar";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
type Props = React.ComponentPropsWithoutRef<typeof BaseAvatar>;
describe("<BaseAvatar />", () => {
let client: MatrixClient;
let room: Room;
let member: RoomMember;
function getComponent(props: Partial<Props>) {
return (
<MatrixClientContext.Provider value={client}>
<RoomContext.Provider value={getRoomContext(room, {})}>
<BaseAvatar name="" {...props} />
</RoomContext.Provider>
</MatrixClientContext.Provider>
);
}
function failLoadingImg(container: HTMLElement): void {
const img = container.querySelector<HTMLImageElement>("img")!;
expect(img).not.toBeNull();
act(() => {
fireEvent.error(img);
});
}
function emitReconnect(): void {
act(() => {
client.emit(ClientEvent.Sync, SyncState.Prepared, SyncState.Reconnecting);
});
}
beforeEach(() => {
client = stubClient();
room = new Room("!room:example.com", client, client.getUserId() ?? "", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
member = new RoomMember(room.roomId, "@bob:example.org");
jest.spyOn(room, "getMember").mockReturnValue(member);
});
it("renders with minimal properties", () => {
const { container } = render(getComponent({}));
expect(container.querySelector(".mx_BaseAvatar")).not.toBeNull();
});
it("matches snapshot (avatar)", () => {
const { container } = render(
getComponent({
name: "CoolUser22",
title: "Hover title",
url: "https://example.com/images/avatar.gif",
className: "mx_SomethingArbitrary",
}),
);
expect(container).toMatchSnapshot();
});
it("matches snapshot (avatar + click)", () => {
const { container } = render(
getComponent({
name: "CoolUser22",
title: "Hover title",
url: "https://example.com/images/avatar.gif",
className: "mx_SomethingArbitrary",
onClick: () => {},
}),
);
expect(container).toMatchSnapshot();
});
it("matches snapshot (no avatar)", () => {
const { container } = render(
getComponent({
name: "xX_Element_User_Xx",
title: ":kiss:",
defaultToInitialLetter: true,
className: "big-and-bold",
}),
);
expect(container).toMatchSnapshot();
});
it("matches snapshot (no avatar + click)", () => {
const { container } = render(
getComponent({
name: "xX_Element_User_Xx",
title: ":kiss:",
defaultToInitialLetter: true,
className: "big-and-bold",
onClick: () => {},
}),
);
expect(container).toMatchSnapshot();
});
it("uses fallback images", () => {
const images = [...Array(10)].map((_, i) => `https://example.com/images/${i}.webp`);
const { container } = render(
getComponent({
url: images[0],
urls: images.slice(1),
}),
);
for (const image of images) {
expect(container.querySelector("img")!.src).toBe(image);
failLoadingImg(container);
}
});
it("re-renders on reconnect", () => {
const primary = "https://example.com/image.jpeg";
const fallback = "https://example.com/fallback.png";
const { container } = render(
getComponent({
url: primary,
urls: [fallback],
}),
);
failLoadingImg(container);
expect(container.querySelector("img")!.src).toBe(fallback);
emitReconnect();
expect(container.querySelector("img")!.src).toBe(primary);
});
it("renders with an image", () => {
const url = "https://example.com/images/small/avatar.gif?size=realBig";
const { container } = render(getComponent({ url }));
const img = container.querySelector("img");
expect(img!.src).toBe(url);
});
it("renders the initial letter", () => {
const { container } = render(getComponent({ name: "Yellow", defaultToInitialLetter: true }));
const avatar = container.querySelector<HTMLSpanElement>(".mx_BaseAvatar_initial")!;
expect(avatar.innerHTML).toBe("Y");
});
it.each([{}, { name: "CoolUser22" }, { name: "XxElement_FanxX", defaultToInitialLetter: true }])(
"includes a click handler",
(props: Partial<Props>) => {
const onClick = jest.fn();
const { container } = render(
getComponent({
...props,
onClick,
}),
);
act(() => {
fireEvent.click(container.querySelector(".mx_BaseAvatar")!);
});
expect(onClick).toHaveBeenCalled();
},
);
});

View file

@ -1,5 +1,5 @@
/*
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
Copyright 2022 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.
@ -14,25 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { fireEvent, getByTestId, render } from "@testing-library/react";
import { getByTestId, render, waitFor } from "@testing-library/react";
import { mocked } from "jest-mock";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import React from "react";
import { act } from "react-dom/test-utils";
import MemberAvatar from "../../../../src/components/views/avatars/MemberAvatar";
import RoomContext from "../../../../src/contexts/RoomContext";
import { mediaFromMxc } from "../../../../src/customisations/Media";
import { ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUserPayload";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { getRoomContext } from "../../../test-utils/room";
import { stubClient } from "../../../test-utils/test-utils";
import { Action } from "../../../../src/dispatcher/actions";
type Props = React.ComponentPropsWithoutRef<typeof MemberAvatar>;
describe("MemberAvatar", () => {
const ROOM_ID = "roomId";
@ -41,7 +35,7 @@ describe("MemberAvatar", () => {
let room: Room;
let member: RoomMember;
function getComponent(props: Partial<Props>) {
function getComponent(props) {
return (
<RoomContext.Provider value={getRoomContext(room, {})}>
<MemberAvatar member={null} width={35} height={35} {...props} />
@ -50,7 +44,10 @@ describe("MemberAvatar", () => {
}
beforeEach(() => {
mockClient = stubClient();
jest.clearAllMocks();
stubClient();
mockClient = mocked(MatrixClientPeg.get());
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
pendingEventOrdering: PendingEventOrdering.Detached,
@ -58,77 +55,22 @@ describe("MemberAvatar", () => {
member = new RoomMember(ROOM_ID, "@bob:example.org");
jest.spyOn(room, "getMember").mockReturnValue(member);
});
it("supports 'null' members", () => {
const { container } = render(getComponent({ member: null }));
expect(container.querySelector("img")).not.toBeNull();
});
it("matches the snapshot", () => {
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("http://placekitten.com/400/400");
const { container } = render(
getComponent({
member,
fallbackUserId: "Fallback User ID",
title: "Hover title",
style: {
color: "pink",
},
}),
);
expect(container).toMatchSnapshot();
});
it("shows an avatar for useOnlyCurrentProfiles", () => {
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("http://placekitten.com/400/400");
SettingsStore.setValue("useOnlyCurrentProfiles", null, SettingLevel.DEVICE, true);
it("shows an avatar for useOnlyCurrentProfiles", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
return settingName === "useOnlyCurrentProfiles";
});
const { container } = render(getComponent({}));
const avatar = getByTestId<HTMLImageElement>(container, "avatar-img");
expect(avatar).toBeInTheDocument();
expect(avatar.getAttribute("src")).not.toBe("");
});
it("uses the member's configured avatar", () => {
const mxcUrl = "mxc://example.com/avatars/user.tiff";
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue(mxcUrl);
const { container } = render(getComponent({ member }));
const img = container.querySelector("img");
expect(img).not.toBeNull();
expect(img!.src).toBe(mediaFromMxc(mxcUrl).srcHttp);
});
it("uses a fallback when the member has no avatar", () => {
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue(undefined);
const { container } = render(getComponent({ member }));
const img = container.querySelector(".mx_BaseAvatar_image");
expect(img).not.toBeNull();
});
it("dispatches on click", () => {
const { container } = render(getComponent({ member, viewUserOnClick: true }));
const spy = jest.spyOn(defaultDispatcher, "dispatch");
act(() => {
fireEvent.click(container.querySelector(".mx_BaseAvatar")!);
let avatar: HTMLElement;
await waitFor(() => {
avatar = getByTestId(container, "avatar-img");
expect(avatar).toBeInTheDocument();
});
expect(spy).toHaveBeenCalled();
const [payload] = spy.mock.lastCall!;
expect(payload).toStrictEqual<ViewUserPayload>({
action: Action.ViewUser,
member,
push: false,
});
expect(avatar!.getAttribute("src")).not.toBe("");
});
});

View file

@ -39,7 +39,7 @@ describe("RoomAvatar", () => {
const dmRoomMap = new DMRoomMap(client);
jest.spyOn(dmRoomMap, "getUserIdForRoomId");
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
jest.spyOn(AvatarModule, "getColorForString");
jest.spyOn(AvatarModule, "defaultAvatarUrlForString");
});
afterAll(() => {
@ -48,14 +48,14 @@ describe("RoomAvatar", () => {
afterEach(() => {
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset();
mocked(AvatarModule.getColorForString).mockClear();
mocked(AvatarModule.defaultAvatarUrlForString).mockClear();
});
it("should render as expected for a Room", () => {
const room = new Room("!room:example.com", client, client.getSafeUserId());
room.name = "test room";
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(room.roomId);
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(room.roomId);
});
it("should render as expected for a DM room", () => {
@ -64,7 +64,7 @@ describe("RoomAvatar", () => {
room.name = "DM room";
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(userId);
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
});
it("should render as expected for a LocalRoom", () => {
@ -73,6 +73,6 @@ describe("RoomAvatar", () => {
localRoom.name = "local test room";
localRoom.targets.push(new DirectoryMember({ user_id: userId }));
expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
expect(AvatarModule.getColorForString).toHaveBeenCalledWith(userId);
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
});
});

View file

@ -1,72 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<BaseAvatar /> matches snapshot (avatar + click) 1`] = `
<div>
<img
alt="Avatar"
class="mx_AccessibleButton mx_BaseAvatar mx_BaseAvatar_image mx_SomethingArbitrary"
data-testid="avatar-img"
role="button"
src="https://example.com/images/avatar.gif"
style="width: 40px; height: 40px;"
tabindex="0"
title="Hover title"
/>
</div>
`;
exports[`<BaseAvatar /> matches snapshot (avatar) 1`] = `
<div>
<img
alt=""
class="mx_BaseAvatar mx_BaseAvatar_image mx_SomethingArbitrary"
data-testid="avatar-img"
src="https://example.com/images/avatar.gif"
style="width: 40px; height: 40px;"
title="Hover title"
/>
</div>
`;
exports[`<BaseAvatar /> matches snapshot (no avatar + click) 1`] = `
<div>
<span
aria-label="Avatar"
aria-live="off"
class="mx_AccessibleButton mx_BaseAvatar big-and-bold"
role="button"
style="width: 40px; height: 40px;"
tabindex="0"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(13, 189, 139); width: 40px; height: 40px; font-size: 26px; line-height: 40px;"
title=":kiss:"
>
X
</span>
</span>
</div>
`;
exports[`<BaseAvatar /> matches snapshot (no avatar) 1`] = `
<div>
<span
class="mx_BaseAvatar big-and-bold"
role="presentation"
style="width: 40px; height: 40px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(13, 189, 139); width: 40px; height: 40px; font-size: 26px; line-height: 40px;"
title=":kiss:"
>
X
</span>
</span>
</div>
`;

View file

@ -1,14 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MemberAvatar matches the snapshot 1`] = `
<div>
<img
alt=""
class="mx_BaseAvatar mx_BaseAvatar_image"
data-testid="avatar-img"
src="http://this.is.a.url//placekitten.com/400/400"
style="color: pink; width: 35px; height: 35px;"
title="Hover title"
/>
</div>
`;

View file

@ -5,16 +5,22 @@ exports[`RoomAvatar should render as expected for a DM room 1`] = `
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(13, 189, 139); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
D
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
/>
</span>
</div>
`;
@ -24,16 +30,22 @@ exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
L
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
/>
</span>
</div>
`;
@ -43,16 +55,22 @@ exports[`RoomAvatar should render as expected for a Room 1`] = `
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
T
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
/>
</span>
</div>
`;

View file

@ -13,17 +13,23 @@ exports[`<BeaconMarker /> renders marker when beacon has location 1`] = `
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
title="@alice:server"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
A
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
title="@alice:server"
/>
</span>
</div>
</div>

View file

@ -52,10 +52,35 @@ describe("<RoomCreate />", () => {
content: {},
event_id: "$create",
});
const predecessorEvent = new MatrixEvent({
type: EventType.RoomPredecessor,
state_key: "",
sender: userId,
room_id: roomId,
content: {
predecessor_room_id: "old_room_id_from_predecessor",
},
event_id: "$create",
});
const predecessorEventWithEventId = new MatrixEvent({
type: EventType.RoomPredecessor,
state_key: "",
sender: userId,
room_id: roomId,
content: {
predecessor_room_id: "old_room_id_from_predecessor",
last_known_event_id: "tombstone_event_id_from_predecessor",
},
event_id: "$create",
});
stubClient();
const client = mocked(MatrixClientPeg.get());
const room = new Room(roomId, client, userId);
upsertRoomStateEvents(room, [createEvent]);
const roomJustCreate = new Room(roomId, client, userId);
upsertRoomStateEvents(roomJustCreate, [createEvent]);
const roomCreateAndPredecessor = new Room(roomId, client, userId);
upsertRoomStateEvents(roomCreateAndPredecessor, [createEvent, predecessorEvent]);
const roomCreateAndPredecessorWithEventId = new Room(roomId, client, userId);
upsertRoomStateEvents(roomCreateAndPredecessorWithEventId, [createEvent, predecessorEventWithEventId]);
const roomNoPredecessors = new Room(roomId, client, userId);
upsertRoomStateEvents(roomNoPredecessors, [createEventWithoutPredecessor]);
@ -81,12 +106,12 @@ describe("<RoomCreate />", () => {
}
it("Renders as expected", () => {
const roomCreate = renderRoomCreate(room);
const roomCreate = renderRoomCreate(roomJustCreate);
expect(roomCreate.asFragment()).toMatchSnapshot();
});
it("Links to the old version of the room", () => {
renderRoomCreate(room);
renderRoomCreate(roomJustCreate);
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
"href",
"https://matrix.to/#/old_room_id/tombstone_event_id",
@ -99,7 +124,7 @@ describe("<RoomCreate />", () => {
});
it("Opens the old room on click", async () => {
renderRoomCreate(room);
renderRoomCreate(roomJustCreate);
const link = screen.getByText("Click here to see older messages.");
await act(() => userEvent.click(link));
@ -115,4 +140,48 @@ describe("<RoomCreate />", () => {
}),
);
});
it("Ignores m.predecessor if labs flag is off", () => {
renderRoomCreate(roomCreateAndPredecessor);
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
"href",
"https://matrix.to/#/old_room_id/tombstone_event_id",
);
});
describe("When feature_dynamic_room_predecessors = true", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === "feature_dynamic_room_predecessors",
);
});
afterEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReset();
});
it("Uses the create event if there is no m.predecessor", () => {
renderRoomCreate(roomJustCreate);
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
"href",
"https://matrix.to/#/old_room_id/tombstone_event_id",
);
});
it("Uses m.predecessor when it's there", () => {
renderRoomCreate(roomCreateAndPredecessor);
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
"href",
"https://matrix.to/#/old_room_id_from_predecessor",
);
});
it("Links to the event in the room if event ID is provided", () => {
renderRoomCreate(roomCreateAndPredecessorWithEventId);
expect(screen.getByText("Click here to see older messages.")).toHaveAttribute(
"href",
"https://matrix.to/#/old_room_id_from_predecessor/tombstone_event_id_from_predecessor",
);
});
});
});

View file

@ -0,0 +1,182 @@
/*
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, fireEvent } from "@testing-library/react";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import RoomSummaryCard from "../../../../src/components/views/right_panel/RoomSummaryCard";
import ShareDialog from "../../../../src/components/views/dialogs/ShareDialog";
import ExportDialog from "../../../../src/components/views/dialogs/ExportDialog";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import * as settingsHooks from "../../../../src/hooks/useSettings";
import Modal from "../../../../src/Modal";
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore";
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
import { PollHistoryDialog } from "../../../../src/components/views/dialogs/polls/PollHistoryDialog";
describe("<RoomSummaryCard />", () => {
const userId = "@alice:domain.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
isRoomEncrypted: jest.fn(),
getRoom: jest.fn(),
});
const roomId = "!room:domain.org";
const room = new Room(roomId, mockClient, userId);
const roomCreateEvent = new MatrixEvent({
type: "m.room.create",
room_id: roomId,
sender: userId,
content: {
creator: userId,
room_version: "5",
},
state_key: "",
});
room.currentState.setStateEvents([roomCreateEvent]);
const defaultProps = {
room,
onClose: jest.fn(),
};
const getComponent = (props = {}) =>
render(<RoomSummaryCard {...defaultProps} {...props} />, {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
),
});
const modalSpy = jest.spyOn(Modal, "createDialog");
const dispatchSpy = jest.spyOn(defaultDispatcher, "dispatch");
const rightPanelCardSpy = jest.spyOn(RightPanelStore.instance, "pushCard");
const featureEnabledSpy = jest.spyOn(settingsHooks, "useFeatureEnabled");
beforeEach(() => {
jest.clearAllMocks();
DMRoomMap.makeShared();
mockClient.getRoom.mockReturnValue(room);
jest.spyOn(room, "isElementVideoRoom").mockRestore();
jest.spyOn(room, "isCallRoom").mockRestore();
featureEnabledSpy.mockReset().mockReturnValue(false);
});
it("renders the room summary", () => {
const { container } = getComponent();
expect(container).toMatchSnapshot();
});
it("opens room members list on button click", () => {
const { getByText } = getComponent();
fireEvent.click(getByText("People"));
expect(rightPanelCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.RoomMemberList }, true);
});
it("opens room file panel on button click", () => {
const { getByText } = getComponent();
fireEvent.click(getByText("Files"));
expect(rightPanelCardSpy).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true);
});
it("opens room export dialog on button click", () => {
const { getByText } = getComponent();
fireEvent.click(getByText("Export chat"));
expect(modalSpy).toHaveBeenCalledWith(ExportDialog, { room });
});
it("opens share room dialog on button click", () => {
const { getByText } = getComponent();
fireEvent.click(getByText("Share room"));
expect(modalSpy).toHaveBeenCalledWith(ShareDialog, { target: room });
});
it("opens room settings on button click", () => {
const { getByText } = getComponent();
fireEvent.click(getByText("Room settings"));
expect(dispatchSpy).toHaveBeenCalledWith({ action: "open_room_settings" });
});
describe("pinning", () => {
it("renders pins options when pinning feature is enabled", () => {
featureEnabledSpy.mockImplementation((feature) => feature === "feature_pinning");
const { getByText } = getComponent();
expect(getByText("Pinned")).toBeInTheDocument();
});
});
describe("poll history", () => {
it("renders poll history option when feature is enabled", () => {
featureEnabledSpy.mockImplementation((feature) => feature === "feature_poll_history");
const { getByText } = getComponent();
expect(getByText("Polls history")).toBeInTheDocument();
});
it("opens poll history dialog on button click", () => {
featureEnabledSpy.mockImplementation((feature) => feature === "feature_poll_history");
const { getByText } = getComponent();
fireEvent.click(getByText("Polls history"));
expect(modalSpy).toHaveBeenCalledWith(PollHistoryDialog, { roomId });
});
});
describe("video rooms", () => {
it("does not render irrelevant options for element video room", () => {
jest.spyOn(room, "isElementVideoRoom").mockReturnValue(true);
featureEnabledSpy.mockImplementation(
(feature) => feature === "feature_video_rooms" || feature === "feature_pinning",
);
const { queryByText } = getComponent();
// options not rendered
expect(queryByText("Files")).not.toBeInTheDocument();
expect(queryByText("Pinned")).not.toBeInTheDocument();
expect(queryByText("Export chat")).not.toBeInTheDocument();
});
it("does not render irrelevant options for element call room", () => {
jest.spyOn(room, "isCallRoom").mockReturnValue(true);
featureEnabledSpy.mockImplementation(
(feature) =>
feature === "feature_element_call_video_rooms" ||
feature === "feature_video_rooms" ||
feature === "feature_pinning",
);
const { queryByText } = getComponent();
// options not rendered
expect(queryByText("Files")).not.toBeInTheDocument();
expect(queryByText("Pinned")).not.toBeInTheDocument();
expect(queryByText("Export chat")).not.toBeInTheDocument();
});
});
});

View file

@ -0,0 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RoomSummaryCard /> renders the room summary 1`] = `
<div>
<div
class="mx_BaseCard mx_RoomSummaryCard"
>
<div
class="mx_BaseCard_header"
>
<div
class="mx_AccessibleButton mx_BaseCard_close"
data-testid="base-card-close-button"
role="button"
tabindex="0"
title="Close"
/>
<div
class="mx_RoomSummaryCard_avatar"
role="presentation"
>
<span
class="mx_BaseAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 35.1px; width: 54px; line-height: 54px;"
>
!
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 54px; height: 54px;"
/>
</span>
<div
class="mx_TextWithTooltip_target mx_RoomSummaryCard_e2ee"
tabindex="0"
/>
</div>
<h2
title="!room:domain.org"
>
!room:domain.org
</h2>
<div
class="mx_RoomSummaryCard_alias"
title=""
/>
</div>
<div
class="mx_AutoHideScrollbar"
tabindex="-1"
>
<div
class="mx_BaseCard_Group mx_RoomSummaryCard_aboutGroup"
>
<h1>
About
</h1>
<div
class="mx_AccessibleButton mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_people"
role="button"
tabindex="0"
>
People
<span
class="mx_BaseCard_Button_sublabel"
>
0
</span>
</div>
<div
class="mx_AccessibleButton mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_files"
role="button"
tabindex="0"
>
Files
</div>
<div
class="mx_AccessibleButton mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_export"
role="button"
tabindex="0"
>
Export chat
</div>
<div
class="mx_AccessibleButton mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_share"
role="button"
tabindex="0"
>
Share room
</div>
<div
class="mx_AccessibleButton mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_settings"
role="button"
tabindex="0"
>
Room settings
</div>
</div>
<div
class="mx_BaseCard_Group mx_RoomSummaryCard_appsGroup"
>
<h1>
Widgets
</h1>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
role="button"
tabindex="0"
>
Add widgets, bridges & bots
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -72,7 +72,7 @@ describe("RoomHeader (Enzyme)", () => {
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image).toBeTruthy();
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a room with 2 people", () => {
@ -86,7 +86,7 @@ describe("RoomHeader (Enzyme)", () => {
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image).toBeTruthy();
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a room with >2 people", () => {
@ -100,7 +100,7 @@ describe("RoomHeader (Enzyme)", () => {
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image).toBeTruthy();
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the room avatar in a DM with only ourselves", () => {
@ -114,7 +114,7 @@ describe("RoomHeader (Enzyme)", () => {
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image).toBeTruthy();
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("shows the user avatar in a DM with 2 people", () => {
@ -148,7 +148,7 @@ describe("RoomHeader (Enzyme)", () => {
// And there is no image avatar (because it's not set on this room)
const image = findImg(rendered, ".mx_BaseAvatar_image");
expect(image).toBeTruthy();
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it("renders call buttons normally", () => {

View file

@ -161,16 +161,22 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a dm roo
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
R
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
/>
</span>
</p>
<p>
@ -230,16 +236,22 @@ exports[`<RoomPreviewBar /> with an invite without an invited email for a non-dm
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 36px; height: 36px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 36px; height: 36px; font-size: 23.400000000000002px; line-height: 36px;"
class="mx_BaseAvatar_initial"
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
>
R
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 36px; height: 36px;"
/>
</span>
</p>
<p>

View file

@ -15,16 +15,22 @@ exports[`RoomTile should render the room 1`] = `
<span
class="mx_BaseAvatar"
role="presentation"
style="width: 32px; height: 32px;"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_image mx_BaseAvatar_initial"
data-testid="avatar-img"
style="background-color: rgb(172, 59, 168); width: 32px; height: 32px; font-size: 20.8px; line-height: 32px;"
class="mx_BaseAvatar_initial"
style="font-size: 20.8px; width: 32px; line-height: 32px;"
>
!
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 32px; height: 32px;"
/>
</span>
</div>
<div