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

Commit 8cd715c

Browse files
authored
Prevent soft crash around room list header context menu when space changes (#8289)
1 parent 59fda52 commit 8cd715c

File tree

3 files changed

+148
-2
lines changed

3 files changed

+148
-2
lines changed

src/components/views/rooms/RoomListHeader.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
140140
}
141141
});
142142

143+
const canShowMainMenu = activeSpace || spaceKey === MetaSpace.Home;
144+
145+
useEffect(() => {
146+
if (mainMenuDisplayed && !canShowMainMenu) {
147+
// Space changed under us and we no longer has a main menu to draw
148+
closeMainMenu();
149+
}
150+
}, [closeMainMenu, canShowMainMenu, mainMenuDisplayed]);
151+
143152
// we pass null for the queryLength to inhibit the metrics hook for when there is no filterCondition
144153
useWebSearchMetrics(count, filterCondition ? filterCondition.search.length : null, false);
145154

@@ -168,7 +177,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
168177
const canShowPlusMenu = canCreateRooms || canExploreRooms || activeSpace;
169178

170179
let contextMenu: JSX.Element;
171-
if (mainMenuDisplayed) {
180+
if (mainMenuDisplayed && mainMenuHandle.current) {
172181
let ContextMenuComponent;
173182
if (activeSpace) {
174183
ContextMenuComponent = SpaceContextMenu;
@@ -364,7 +373,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
364373
.join("\n");
365374

366375
let contextMenuButton: JSX.Element = <div className="mx_RoomListHeader_contextLessTitle">{ title }</div>;
367-
if (activeSpace || spaceKey === MetaSpace.Home) {
376+
if (canShowMainMenu) {
368377
contextMenuButton = <ContextMenuTooltipButton
369378
inputRef={mainMenuHandle}
370379
onClick={openMainMenu}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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 { mount } from 'enzyme';
19+
import { MatrixClient } from 'matrix-js-sdk/src/client';
20+
import { act } from "react-dom/test-utils";
21+
22+
import SpaceStore from "../../../../src/stores/spaces/SpaceStore";
23+
import { MetaSpace } from "../../../../src/stores/spaces";
24+
import RoomListHeader from "../../../../src/components/views/rooms/RoomListHeader";
25+
import * as testUtils from "../../../test-utils";
26+
import { createTestClient, mkSpace } from "../../../test-utils";
27+
import DMRoomMap from "../../../../src/utils/DMRoomMap";
28+
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
29+
import SettingsStore from "../../../../src/settings/SettingsStore";
30+
import { SettingLevel } from "../../../../src/settings/SettingLevel";
31+
32+
describe("RoomListHeader", () => {
33+
let client: MatrixClient;
34+
35+
beforeEach(() => {
36+
client = createTestClient();
37+
});
38+
39+
it("renders a main menu for the home space", () => {
40+
act(() => {
41+
SpaceStore.instance.setActiveSpace(MetaSpace.Home);
42+
});
43+
44+
const wrapper = mount(<MatrixClientContext.Provider value={client}>
45+
<RoomListHeader />
46+
</MatrixClientContext.Provider>);
47+
48+
expect(wrapper.text()).toBe("Home");
49+
act(() => {
50+
wrapper.find('[aria-label="Home options"]').hostNodes().simulate("click");
51+
});
52+
wrapper.update();
53+
54+
const menu = wrapper.find(".mx_IconizedContextMenu");
55+
const items = menu.find(".mx_IconizedContextMenu_item").hostNodes();
56+
expect(items).toHaveLength(1);
57+
expect(items.at(0).text()).toBe("Show all rooms");
58+
});
59+
60+
it("renders a main menu for spaces", async () => {
61+
const testSpace = mkSpace(client, "!space:server");
62+
testSpace.name = "Test Space";
63+
client.getRoom = () => testSpace;
64+
65+
const getUserIdForRoomId = jest.fn();
66+
const getDMRoomsForUserId = jest.fn();
67+
// @ts-ignore
68+
DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId };
69+
70+
await testUtils.setupAsyncStoreWithClient(SpaceStore.instance, client);
71+
act(() => {
72+
SpaceStore.instance.setActiveSpace(testSpace.roomId);
73+
});
74+
75+
const wrapper = mount(<MatrixClientContext.Provider value={client}>
76+
<RoomListHeader />
77+
</MatrixClientContext.Provider>);
78+
79+
expect(wrapper.text()).toBe("Test Space");
80+
act(() => {
81+
wrapper.find('[aria-label="Test Space menu"]').hostNodes().simulate("click");
82+
});
83+
wrapper.update();
84+
85+
const menu = wrapper.find(".mx_IconizedContextMenu");
86+
const items = menu.find(".mx_IconizedContextMenu_item").hostNodes();
87+
expect(items).toHaveLength(6);
88+
expect(items.at(0).text()).toBe("Space home");
89+
expect(items.at(1).text()).toBe("Manage & explore rooms");
90+
expect(items.at(2).text()).toBe("Preferences");
91+
expect(items.at(3).text()).toBe("Settings");
92+
expect(items.at(4).text()).toBe("Room");
93+
expect(items.at(4).text()).toBe("Room");
94+
});
95+
96+
it("closes menu if space changes from under it", async () => {
97+
await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, {
98+
[MetaSpace.Home]: true,
99+
[MetaSpace.Favourites]: true,
100+
});
101+
102+
const testSpace = mkSpace(client, "!space:server");
103+
testSpace.name = "Test Space";
104+
client.getRoom = () => testSpace;
105+
106+
const getUserIdForRoomId = jest.fn();
107+
const getDMRoomsForUserId = jest.fn();
108+
// @ts-ignore
109+
DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId };
110+
111+
await testUtils.setupAsyncStoreWithClient(SpaceStore.instance, client);
112+
act(() => {
113+
SpaceStore.instance.setActiveSpace(testSpace.roomId);
114+
});
115+
116+
const wrapper = mount(<MatrixClientContext.Provider value={client}>
117+
<RoomListHeader />
118+
</MatrixClientContext.Provider>);
119+
120+
expect(wrapper.text()).toBe("Test Space");
121+
act(() => {
122+
wrapper.find('[aria-label="Test Space menu"]').hostNodes().simulate("click");
123+
});
124+
wrapper.update();
125+
126+
act(() => {
127+
SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
128+
});
129+
wrapper.update();
130+
131+
expect(wrapper.text()).toBe("Favourites");
132+
133+
const menu = wrapper.find(".mx_IconizedContextMenu");
134+
expect(menu).toHaveLength(0);
135+
});
136+
});

test/test-utils/test-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
379379
getJoinRule: jest.fn().mockReturnValue("invite"),
380380
loadMembersIfNeeded: jest.fn(),
381381
client,
382+
canInvite: jest.fn(),
382383
} as unknown as Room;
383384
}
384385

0 commit comments

Comments
 (0)