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

Commit 72e95f1

Browse files
committed
Ensure 1:1 DM rooms always have the same avatar colour
1 parent cb1af0d commit 72e95f1

File tree

3 files changed

+168
-5
lines changed

3 files changed

+168
-5
lines changed

src/components/views/avatars/RoomAvatar.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as Avatar from "../../../Avatar";
2929
import DMRoomMap from "../../../utils/DMRoomMap";
3030
import { mediaFromMxc } from "../../../customisations/Media";
3131
import { IOOBData } from "../../../stores/ThreepidInviteStore";
32+
import { LocalRoom } from "../../../models/LocalRoom";
3233

3334
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
3435
// Room may be left unset here, but if it is,
@@ -117,13 +118,26 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
117118
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
118119
};
119120

121+
private get roomIdName(): string {
122+
const room = this.props.room;
123+
124+
if (room) {
125+
const dmMapUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
126+
// If the room is a DM, we use the other user's ID for the color hash
127+
// in order to match the room avatar with their avatar
128+
if (dmMapUserId) return dmMapUserId;
129+
130+
if (room instanceof LocalRoom && room.targets.length === 1) {
131+
return room.targets[0].userId;
132+
}
133+
}
134+
135+
return this.props.room.roomId || this.props.oobData.roomId;
136+
}
137+
120138
public render() {
121139
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
122-
123140
const roomName = room?.name ?? oobData.name;
124-
// If the room is a DM, we use the other user's ID for the color hash
125-
// in order to match the room avatar with their avatar
126-
const idName = room ? DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId : oobData.roomId;
127141

128142
return (
129143
<BaseAvatar
@@ -132,7 +146,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
132146
mx_RoomAvatar_isSpaceRoom: (room?.getType() ?? this.props.oobData?.roomType) === RoomType.Space,
133147
})}
134148
name={roomName}
135-
idName={idName}
149+
idName={this.roomIdName}
136150
urls={this.state.urls}
137151
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
138152
/>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { render } from "@testing-library/react";
19+
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
20+
import { mocked } from "jest-mock";
21+
22+
import RoomAvatar from "../../../../src/components/views/avatars/RoomAvatar";
23+
import { stubClient } from "../../../test-utils";
24+
import DMRoomMap from "../../../../src/utils/DMRoomMap";
25+
import { LocalRoom } from "../../../../src/models/LocalRoom";
26+
import * as AvatarModule from "../../../../src/Avatar";
27+
import { DirectoryMember } from "../../../../src/utils/direct-messages";
28+
29+
describe("RoomAvatar", () => {
30+
let client: MatrixClient;
31+
32+
beforeAll(() => {
33+
client = stubClient();
34+
const dmRoomMap = new DMRoomMap(client);
35+
jest.spyOn(dmRoomMap, "getUserIdForRoomId");
36+
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
37+
jest.spyOn(AvatarModule, "defaultAvatarUrlForString");
38+
});
39+
40+
afterAll(() => {
41+
jest.restoreAllMocks();
42+
});
43+
44+
afterEach(() => {
45+
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset();
46+
mocked(AvatarModule.defaultAvatarUrlForString).mockClear();
47+
});
48+
49+
it("should render as expected for a Room", () => {
50+
const room = new Room("!room:example.com", client, client.getSafeUserId());
51+
room.name = "test room";
52+
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
53+
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(room.roomId);
54+
});
55+
56+
it("should render as expected for a DM room", () => {
57+
const userId = "@dm_user@example.com";
58+
const room = new Room("!room:example.com", client, client.getSafeUserId());
59+
room.name = "DM room";
60+
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReturnValue(userId);
61+
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
62+
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
63+
});
64+
65+
it("should render as expected for a LocalRoom", () => {
66+
const userId = "@local_room_user@example.com";
67+
const localRoom = new LocalRoom("!room:example.com", client, client.getSafeUserId());
68+
localRoom.name = "local test room";
69+
localRoom.targets.push(new DirectoryMember({ user_id: userId }));
70+
expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
71+
expect(AvatarModule.defaultAvatarUrlForString).toHaveBeenCalledWith(userId);
72+
});
73+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`RoomAvatar should render as expected for a DM room 1`] = `
4+
<div>
5+
<span
6+
class="mx_BaseAvatar"
7+
role="presentation"
8+
>
9+
<span
10+
aria-hidden="true"
11+
class="mx_BaseAvatar_initial"
12+
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
13+
>
14+
D
15+
</span>
16+
<img
17+
alt=""
18+
aria-hidden="true"
19+
class="mx_BaseAvatar_image"
20+
data-testid="avatar-img"
21+
src="data:image/png;base64,00"
22+
style="width: 36px; height: 36px;"
23+
/>
24+
</span>
25+
</div>
26+
`;
27+
28+
exports[`RoomAvatar should render as expected for a LocalRoom 1`] = `
29+
<div>
30+
<span
31+
class="mx_BaseAvatar"
32+
role="presentation"
33+
>
34+
<span
35+
aria-hidden="true"
36+
class="mx_BaseAvatar_initial"
37+
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
38+
>
39+
L
40+
</span>
41+
<img
42+
alt=""
43+
aria-hidden="true"
44+
class="mx_BaseAvatar_image"
45+
data-testid="avatar-img"
46+
src="data:image/png;base64,00"
47+
style="width: 36px; height: 36px;"
48+
/>
49+
</span>
50+
</div>
51+
`;
52+
53+
exports[`RoomAvatar should render as expected for a Room 1`] = `
54+
<div>
55+
<span
56+
class="mx_BaseAvatar"
57+
role="presentation"
58+
>
59+
<span
60+
aria-hidden="true"
61+
class="mx_BaseAvatar_initial"
62+
style="font-size: 23.400000000000002px; width: 36px; line-height: 36px;"
63+
>
64+
T
65+
</span>
66+
<img
67+
alt=""
68+
aria-hidden="true"
69+
class="mx_BaseAvatar_image"
70+
data-testid="avatar-img"
71+
src="data:image/png;base64,00"
72+
style="width: 36px; height: 36px;"
73+
/>
74+
</span>
75+
</div>
76+
`;

0 commit comments

Comments
 (0)