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

Commit fa1bff6

Browse files
authored
Wire local room logic (#9078)
* Wire local room logic * Migrate to testling-lib; update test descriptions
1 parent 66f7c9f commit fa1bff6

File tree

14 files changed

+493
-11
lines changed

14 files changed

+493
-11
lines changed

src/Avatar.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { split } from "lodash";
2222

2323
import DMRoomMap from './utils/DMRoomMap';
2424
import { mediaFromMxc } from "./customisations/Media";
25+
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
2526

2627
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
2728
export function avatarUrlForMember(
@@ -142,7 +143,12 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
142143
if (room.isSpaceRoom()) return null;
143144

144145
// If the room is not a DM don't fallback to a member avatar
145-
if (!DMRoomMap.shared().getUserIdForRoomId(room.roomId)) return null;
146+
if (
147+
!DMRoomMap.shared().getUserIdForRoomId(room.roomId)
148+
&& !(isLocalRoom(room))
149+
) {
150+
return null;
151+
}
146152

147153
// If there are only two members in the DM use the avatar of the other member
148154
const otherMember = room.getAvatarFallbackMember();

src/components/structures/MatrixChat.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import VideoChannelStore from "../../stores/VideoChannelStore";
132132
import { IRoomStateEventsActionPayload } from "../../actions/MatrixActionCreators";
133133
import { UseCaseSelection } from '../views/elements/UseCaseSelection';
134134
import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig';
135+
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
135136

136137
// legacy export
137138
export { default as Views } from "../../Views";
@@ -890,7 +891,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
890891
}
891892

892893
// If we are redirecting to a Room Alias and it is for the room we already showing then replace history item
893-
const replaceLast = presentedId[0] === "#" && roomInfo.room_id === this.state.currentRoomId;
894+
let replaceLast = presentedId[0] === "#" && roomInfo.room_id === this.state.currentRoomId;
895+
896+
if (isLocalRoom(this.state.currentRoomId)) {
897+
// Replace local room history items
898+
replaceLast = true;
899+
}
894900

895901
if (roomInfo.room_id === this.state.currentRoomId) {
896902
// if we are re-viewing the same room then copy any state we already know

src/components/views/dialogs/spotlight/SpotlightDialog.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
9191
import { RoomResultContextMenus } from "./RoomResultContextMenus";
9292
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
9393
import { TooltipOption } from "./TooltipOption";
94+
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
9495

9596
const MAX_RECENT_SEARCHES = 10;
9697
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@@ -243,6 +244,9 @@ export const useWebSearchMetrics = (numResults: number, queryLength: number, via
243244

244245
const findVisibleRooms = (cli: MatrixClient) => {
245246
return cli.getVisibleRooms().filter(room => {
247+
// Do not show local rooms
248+
if (isLocalRoom(room)) return false;
249+
246250
// TODO we may want to put invites in their own list
247251
return room.getMyMembership() === "join" || room.getMyMembership() == "invite";
248252
});
@@ -395,7 +399,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
395399

396400
possibleResults.forEach(entry => {
397401
if (isRoomResult(entry)) {
398-
if (!entry.room.normalizedName.includes(normalizedQuery) &&
402+
if (!entry.room.normalizedName?.includes(normalizedQuery) &&
399403
!entry.room.getCanonicalAlias()?.toLowerCase().includes(lcQuery) &&
400404
!entry.query?.some(q => q.includes(lcQuery))
401405
) return; // bail, does not match query

src/components/views/messages/EncryptionEvent.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import EventTileBubble from "./EventTileBubble";
2424
import MatrixClientContext from "../../../contexts/MatrixClientContext";
2525
import DMRoomMap from "../../../utils/DMRoomMap";
2626
import { objectHasDiff } from "../../../utils/objects";
27+
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
2728

2829
interface IProps {
2930
mxEvent: MatrixEvent;
@@ -46,12 +47,15 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
4647
if (content.algorithm === ALGORITHM && isRoomEncrypted) {
4748
let subtitle: string;
4849
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
50+
const room = cli?.getRoom(roomId);
4951
if (prevContent.algorithm === ALGORITHM) {
5052
subtitle = _t("Some encryption parameters have been changed.");
5153
} else if (dmPartner) {
52-
const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
54+
const displayName = room.getMember(dmPartner)?.rawDisplayName || dmPartner;
5355
subtitle = _t("Messages here are end-to-end encrypted. " +
5456
"Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
57+
} else if (isLocalRoom(room)) {
58+
subtitle = _t("Messages in this chat will be end-to-end encrypted.");
5559
} else {
5660
subtitle = _t("Messages in this room are end-to-end encrypted. " +
5761
"When people join, you can verify them in their profile, just tap on their avatar.");

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,7 @@
21012101
"View Source": "View Source",
21022102
"Some encryption parameters have been changed.": "Some encryption parameters have been changed.",
21032103
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
2104+
"Messages in this chat will be end-to-end encrypted.": "Messages in this chat will be end-to-end encrypted.",
21042105
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
21052106
"Encryption enabled": "Encryption enabled",
21062107
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",

src/stores/TypingStore.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
import { MatrixClientPeg } from "../MatrixClientPeg";
1818
import SettingsStore from "../settings/SettingsStore";
19+
import { isLocalRoom } from "../utils/localRoom/isLocalRoom";
1920
import Timer from "../utils/Timer";
2021

2122
const TYPING_USER_TIMEOUT = 10000;
@@ -64,6 +65,9 @@ export default class TypingStore {
6465
* @param {boolean} isTyping Whether the user is typing or not.
6566
*/
6667
public setSelfTyping(roomId: string, threadId: string | null, isTyping: boolean): void {
68+
// No typing notifications for local rooms
69+
if (isLocalRoom(roomId)) return;
70+
6771
if (!SettingsStore.getValue('sendTypingNotifications')) return;
6872
if (SettingsStore.getValue('lowBandwidth')) return;
6973
// Disable typing notification for threads for the initial launch

src/stores/room-list/filters/VisibilityProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
1818

1919
import CallHandler from "../../../CallHandler";
2020
import { RoomListCustomisations } from "../../../customisations/RoomList";
21-
import { LocalRoom } from "../../../models/LocalRoom";
21+
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
2222
import VoipUserMapper from "../../../VoipUserMapper";
2323

2424
export class VisibilityProvider {
@@ -55,7 +55,7 @@ export class VisibilityProvider {
5555
return false;
5656
}
5757

58-
if (room instanceof LocalRoom) {
58+
if (isLocalRoom(room)) {
5959
// local rooms shouldn't show up anywhere
6060
return false;
6161
}

src/utils/localRoom/isLocalRoom.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 { Room } from "matrix-js-sdk/src/matrix";
18+
19+
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
20+
21+
export function isLocalRoom(roomOrID: Room|string): boolean {
22+
if (typeof roomOrID === "string") {
23+
return roomOrID.startsWith(LOCAL_ROOM_ID_PREFIX);
24+
}
25+
return roomOrID instanceof LocalRoom;
26+
}

test/Avatar-test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 { mocked } from "jest-mock";
18+
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
19+
20+
import { avatarUrlForRoom } from "../src/Avatar";
21+
import { Media, mediaFromMxc } from "../src/customisations/Media";
22+
import DMRoomMap from "../src/utils/DMRoomMap";
23+
24+
jest.mock("../src/customisations/Media", () => ({
25+
mediaFromMxc: jest.fn(),
26+
}));
27+
28+
const roomId = "!room:example.com";
29+
const avatarUrl1 = "https://example.com/avatar1";
30+
const avatarUrl2 = "https://example.com/avatar2";
31+
32+
describe("avatarUrlForRoom", () => {
33+
let getThumbnailOfSourceHttp: jest.Mock;
34+
let room: Room;
35+
let roomMember: RoomMember;
36+
let dmRoomMap: DMRoomMap;
37+
38+
beforeEach(() => {
39+
getThumbnailOfSourceHttp = jest.fn();
40+
mocked(mediaFromMxc).mockImplementation((): Media => {
41+
return {
42+
getThumbnailOfSourceHttp,
43+
} as unknown as Media;
44+
});
45+
room = {
46+
roomId,
47+
getMxcAvatarUrl: jest.fn(),
48+
isSpaceRoom: jest.fn(),
49+
getAvatarFallbackMember: jest.fn(),
50+
} as unknown as Room;
51+
dmRoomMap = {
52+
getUserIdForRoomId: jest.fn(),
53+
} as unknown as DMRoomMap;
54+
DMRoomMap.setShared(dmRoomMap);
55+
roomMember = {
56+
getMxcAvatarUrl: jest.fn(),
57+
} as unknown as RoomMember;
58+
});
59+
60+
it("should return null for a null room", () => {
61+
expect(avatarUrlForRoom(null, 128, 128)).toBeNull();
62+
});
63+
64+
it("should return the HTTP source if the room provides a MXC url", () => {
65+
mocked(room.getMxcAvatarUrl).mockReturnValue(avatarUrl1);
66+
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
67+
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
68+
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
69+
});
70+
71+
it("should return null for a space room", () => {
72+
mocked(room.isSpaceRoom).mockReturnValue(true);
73+
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
74+
});
75+
76+
it("should return null if the room is not a DM", () => {
77+
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue(null);
78+
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
79+
expect(dmRoomMap.getUserIdForRoomId).toHaveBeenCalledWith(roomId);
80+
});
81+
82+
it("should return null if there is no other member in the room", () => {
83+
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
84+
mocked(room.getAvatarFallbackMember).mockReturnValue(null);
85+
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
86+
});
87+
88+
it("should return null if the other member has no avatar URL", () => {
89+
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
90+
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
91+
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
92+
});
93+
94+
it("should return the other member's avatar URL", () => {
95+
mocked(dmRoomMap).getUserIdForRoomId.mockReturnValue("@user:example.com");
96+
mocked(room.getAvatarFallbackMember).mockReturnValue(roomMember);
97+
mocked(roomMember.getMxcAvatarUrl).mockReturnValue(avatarUrl2);
98+
getThumbnailOfSourceHttp.mockReturnValue(avatarUrl2);
99+
expect(avatarUrlForRoom(room, 128, 256, "crop")).toEqual(avatarUrl2);
100+
expect(getThumbnailOfSourceHttp).toHaveBeenCalledWith(128, 256, "crop");
101+
});
102+
});

test/components/views/dialogs/SpotlightDialog-test.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,25 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { mount } from "enzyme";
18-
import { IProtocol, IPublicRoomsChunkRoom, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
17+
import { mount, ReactWrapper } from "enzyme";
18+
import { mocked } from "jest-mock";
19+
import { IProtocol, IPublicRoomsChunkRoom, MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
1920
import { sleep } from "matrix-js-sdk/src/utils";
2021
import React from "react";
2122
import { act } from "react-dom/test-utils";
2223
import sanitizeHtml from "sanitize-html";
2324

2425
import SpotlightDialog, { Filter } from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
2526
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
26-
import { stubClient } from "../../../test-utils";
27+
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
28+
import DMRoomMap from "../../../../src/utils/DMRoomMap";
29+
import { mkRoom, stubClient } from "../../../test-utils";
30+
31+
jest.mock("../../../../src/utils/direct-messages", () => ({
32+
// @ts-ignore
33+
...jest.requireActual("../../../../src/utils/direct-messages"),
34+
startDmOnFirstMessage: jest.fn(),
35+
}));
2736

2837
interface IUserChunkMember {
2938
user_id: string;
@@ -110,10 +119,23 @@ describe("Spotlight Dialog", () => {
110119
guest_can_join: false,
111120
};
112121

122+
let testRoom: Room;
123+
let testLocalRoom: LocalRoom;
124+
125+
let mockedClient: MatrixClient;
126+
113127
beforeEach(() => {
114-
mockClient({ rooms: [testPublicRoom], users: [testPerson] });
128+
mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] });
129+
testRoom = mkRoom(mockedClient, "!test23:example.com");
130+
mocked(testRoom.getMyMembership).mockReturnValue("join");
131+
testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId());
132+
testLocalRoom.updateMyMembership("join");
133+
mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]);
134+
135+
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
136+
getUserIdForRoomId: jest.fn(),
137+
} as unknown as DMRoomMap);
115138
});
116-
117139
describe("should apply filters supplied via props", () => {
118140
it("without filter", async () => {
119141
const wrapper = mount(
@@ -289,4 +311,38 @@ describe("Spotlight Dialog", () => {
289311
wrapper.unmount();
290312
});
291313
});
314+
315+
describe("searching for rooms", () => {
316+
let wrapper: ReactWrapper;
317+
let options: ReactWrapper;
318+
319+
beforeAll(async () => {
320+
wrapper = mount(
321+
<SpotlightDialog
322+
initialText="test23"
323+
onFinished={() => null} />,
324+
);
325+
await act(async () => {
326+
await sleep(200);
327+
});
328+
wrapper.update();
329+
330+
const content = wrapper.find("#mx_SpotlightDialog_content");
331+
options = content.find("div.mx_SpotlightDialog_option");
332+
});
333+
334+
afterAll(() => {
335+
wrapper.unmount();
336+
});
337+
338+
it("should find Rooms", () => {
339+
expect(options.length).toBe(3);
340+
expect(options.first().text()).toContain(testRoom.name);
341+
});
342+
343+
it("should not find LocalRooms", () => {
344+
expect(options.length).toBe(3);
345+
expect(options.first().text()).not.toContain(testLocalRoom.name);
346+
});
347+
});
292348
});

0 commit comments

Comments
 (0)