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

Commit b45b933

Browse files
authored
Merge pull request #10010 from matrix-org/johannes/latest-room-in-space
Ensure room is actually in space hierarchy when resolving its latest version
2 parents 509459f + d3f3782 commit b45b933

File tree

3 files changed

+297
-6
lines changed

3 files changed

+297
-6
lines changed

src/components/structures/SpaceHierarchy.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,18 @@ interface IHierarchyLevelProps {
413413
onToggleClick?(parentId: string, childId: string): void;
414414
}
415415

416-
const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom): IHierarchyRoom => {
416+
export const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom, hierarchy: RoomHierarchy): IHierarchyRoom => {
417417
const history = cli.getRoomUpgradeHistory(room.room_id, true);
418-
const cliRoom = history[history.length - 1];
418+
419+
// Pick latest room that is actually part of the hierarchy
420+
let cliRoom = null;
421+
for (let idx = history.length - 1; idx >= 0; --idx) {
422+
if (hierarchy.roomMap.get(history[idx].roomId)) {
423+
cliRoom = history[idx];
424+
break;
425+
}
426+
}
427+
419428
if (cliRoom) {
420429
return {
421430
...room,
@@ -424,7 +433,7 @@ const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom): IHierarchyRoom =>
424433
name: cliRoom.name,
425434
topic: cliRoom.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent().topic,
426435
avatar_url: cliRoom.getMxcAvatarUrl(),
427-
canonical_alias: cliRoom.getCanonicalAlias(),
436+
canonical_alias: cliRoom.getCanonicalAlias() ?? undefined,
428437
aliases: cliRoom.getAltAliases(),
429438
world_readable:
430439
cliRoom.currentState.getStateEvents(EventType.RoomHistoryVisibility, "")?.getContent()
@@ -461,7 +470,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
461470
(result, ev: IHierarchyRelation) => {
462471
const room = hierarchy.roomMap.get(ev.state_key);
463472
if (room && roomSet.has(room)) {
464-
result[room.room_type === RoomType.Space ? 0 : 1].push(toLocalRoom(cli, room));
473+
result[room.room_type === RoomType.Space ? 0 : 1].push(toLocalRoom(cli, room, hierarchy));
465474
}
466475
return result;
467476
},

test/components/structures/SpaceHierarchy-test.tsx

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

17+
import React from "react";
18+
import { render } from "@testing-library/react";
1719
import { MatrixClient } from "matrix-js-sdk/src/client";
1820
import { Room } from "matrix-js-sdk/src/models/room";
1921
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
22+
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
2023

2124
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
22-
import { stubClient } from "../../test-utils";
25+
import { mkStubRoom, stubClient } from "../../test-utils";
2326
import dispatcher from "../../../src/dispatcher/dispatcher";
24-
import { showRoom } from "../../../src/components/structures/SpaceHierarchy";
27+
import { HierarchyLevel, showRoom, toLocalRoom } from "../../../src/components/structures/SpaceHierarchy";
2528
import { Action } from "../../../src/dispatcher/actions";
29+
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
30+
import DMRoomMap from "../../../src/utils/DMRoomMap";
31+
32+
// Fake random strings to give a predictable snapshot for checkbox IDs
33+
jest.mock("matrix-js-sdk/src/randomstring", () => {
34+
return {
35+
randomString: () => "abdefghi",
36+
};
37+
});
2638

2739
describe("SpaceHierarchy", () => {
2840
describe("showRoom", () => {
@@ -67,4 +79,115 @@ describe("SpaceHierarchy", () => {
6779
});
6880
});
6981
});
82+
83+
describe("toLocalRoom", () => {
84+
stubClient();
85+
const client = MatrixClientPeg.get();
86+
const roomV1 = mkStubRoom("room-id-1", "Room V1", client);
87+
const roomV2 = mkStubRoom("room-id-2", "Room V2", client);
88+
const roomV3 = mkStubRoom("room-id-3", "Room V3", client);
89+
jest.spyOn(client, "getRoomUpgradeHistory").mockReturnValue([roomV1, roomV2, roomV3]);
90+
91+
it("grabs last room that is in hierarchy when latest version is in hierarchy", () => {
92+
const hierarchy = {
93+
roomMap: new Map([
94+
[roomV1.roomId, { room_id: roomV1.roomId } as IHierarchyRoom],
95+
[roomV2.roomId, { room_id: roomV2.roomId } as IHierarchyRoom],
96+
[roomV3.roomId, { room_id: roomV3.roomId } as IHierarchyRoom],
97+
]),
98+
} as RoomHierarchy;
99+
const localRoomV1 = toLocalRoom(client, { room_id: roomV1.roomId } as IHierarchyRoom, hierarchy);
100+
expect(localRoomV1.room_id).toEqual(roomV3.roomId);
101+
const localRoomV2 = toLocalRoom(client, { room_id: roomV2.roomId } as IHierarchyRoom, hierarchy);
102+
expect(localRoomV2.room_id).toEqual(roomV3.roomId);
103+
const localRoomV3 = toLocalRoom(client, { room_id: roomV3.roomId } as IHierarchyRoom, hierarchy);
104+
expect(localRoomV3.room_id).toEqual(roomV3.roomId);
105+
});
106+
107+
it("grabs last room that is in hierarchy when latest version is *not* in hierarchy", () => {
108+
const hierarchy = {
109+
roomMap: new Map([
110+
[roomV1.roomId, { room_id: roomV1.roomId } as IHierarchyRoom],
111+
[roomV2.roomId, { room_id: roomV2.roomId } as IHierarchyRoom],
112+
]),
113+
} as RoomHierarchy;
114+
const localRoomV1 = toLocalRoom(client, { room_id: roomV1.roomId } as IHierarchyRoom, hierarchy);
115+
expect(localRoomV1.room_id).toEqual(roomV2.roomId);
116+
const localRoomV2 = toLocalRoom(client, { room_id: roomV2.roomId } as IHierarchyRoom, hierarchy);
117+
expect(localRoomV2.room_id).toEqual(roomV2.roomId);
118+
const localRoomV3 = toLocalRoom(client, { room_id: roomV3.roomId } as IHierarchyRoom, hierarchy);
119+
expect(localRoomV3.room_id).toEqual(roomV2.roomId);
120+
});
121+
122+
it("returns specified room when none of the versions is in hierarchy", () => {
123+
const hierarchy = { roomMap: new Map([]) } as RoomHierarchy;
124+
const localRoomV1 = toLocalRoom(client, { room_id: roomV1.roomId } as IHierarchyRoom, hierarchy);
125+
expect(localRoomV1.room_id).toEqual(roomV1.roomId);
126+
const localRoomV2 = toLocalRoom(client, { room_id: roomV2.roomId } as IHierarchyRoom, hierarchy);
127+
expect(localRoomV2.room_id).toEqual(roomV2.roomId);
128+
const localRoomV3 = toLocalRoom(client, { room_id: roomV3.roomId } as IHierarchyRoom, hierarchy);
129+
expect(localRoomV3.room_id).toEqual(roomV3.roomId);
130+
});
131+
});
132+
133+
describe("<HierarchyLevel />", () => {
134+
stubClient();
135+
const client = MatrixClientPeg.get();
136+
137+
const dmRoomMap = {
138+
getUserIdForRoomId: jest.fn(),
139+
} as unknown as DMRoomMap;
140+
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
141+
142+
const root = mkStubRoom("room-id-1", "Room 1", client);
143+
const room1 = mkStubRoom("room-id-2", "Room 2", client);
144+
const room2 = mkStubRoom("room-id-3", "Room 3", client);
145+
146+
const hierarchyRoot = {
147+
room_id: root.roomId,
148+
num_joined_members: 1,
149+
children_state: [
150+
{
151+
state_key: room1.roomId,
152+
content: { order: "1" },
153+
},
154+
{
155+
state_key: room2.roomId,
156+
content: { order: "2" },
157+
},
158+
],
159+
} as IHierarchyRoom;
160+
const hierarchyRoom1 = { room_id: room1.roomId, num_joined_members: 2 } as IHierarchyRoom;
161+
const hierarchyRoom2 = { room_id: root.roomId, num_joined_members: 3 } as IHierarchyRoom;
162+
163+
const roomHierarchy = {
164+
roomMap: new Map([
165+
[root.roomId, hierarchyRoot],
166+
[room1.roomId, hierarchyRoom1],
167+
[room2.roomId, hierarchyRoom2],
168+
]),
169+
isSuggested: jest.fn(),
170+
} as unknown as RoomHierarchy;
171+
172+
it("renders", () => {
173+
const defaultProps = {
174+
root: hierarchyRoot,
175+
roomSet: new Set([hierarchyRoom1, hierarchyRoom2]),
176+
hierarchy: roomHierarchy,
177+
parents: new Set<string>(),
178+
selectedMap: new Map<string, Set<string>>(),
179+
onViewRoomClick: jest.fn(),
180+
onJoinRoomClick: jest.fn(),
181+
onToggleClick: jest.fn(),
182+
};
183+
const getComponent = (props = {}): React.ReactElement => (
184+
<MatrixClientContext.Provider value={client}>
185+
<HierarchyLevel {...defaultProps} {...props} />;
186+
</MatrixClientContext.Provider>
187+
);
188+
189+
const { container } = render(getComponent());
190+
expect(container).toMatchSnapshot();
191+
});
192+
});
70193
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`SpaceHierarchy <HierarchyLevel /> renders 1`] = `
4+
<div>
5+
<li
6+
class="mx_SpaceHierarchy_roomTileWrapper"
7+
role="treeitem"
8+
>
9+
<div
10+
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
11+
role="button"
12+
tabindex="-1"
13+
>
14+
<div
15+
class="mx_SpaceHierarchy_roomTile_item"
16+
>
17+
<div
18+
class="mx_SpaceHierarchy_roomTile_avatar"
19+
>
20+
<img
21+
alt=""
22+
class="mx_BaseAvatar mx_BaseAvatar_image"
23+
data-testid="avatar-img"
24+
src="http://this.is.a.url/avatar.url/room.png"
25+
style="width: 20px; height: 20px;"
26+
/>
27+
</div>
28+
<div
29+
class="mx_SpaceHierarchy_roomTile_name"
30+
>
31+
Unnamed Room
32+
<div
33+
class="mx_SpaceHierarchy_roomTile_joined"
34+
>
35+
Joined
36+
</div>
37+
</div>
38+
<div
39+
class="mx_SpaceHierarchy_roomTile_info"
40+
>
41+
2 members
42+
·
43+
<span
44+
dir="auto"
45+
/>
46+
</div>
47+
</div>
48+
<div
49+
class="mx_SpaceHierarchy_actions"
50+
>
51+
<div
52+
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
53+
role="button"
54+
tabindex="-1"
55+
>
56+
View
57+
</div>
58+
<span
59+
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
60+
>
61+
<input
62+
id="checkbox_abdefghi"
63+
tabindex="-1"
64+
type="checkbox"
65+
/>
66+
<label
67+
for="checkbox_abdefghi"
68+
>
69+
<div
70+
class="mx_Checkbox_background"
71+
>
72+
<div
73+
class="mx_Checkbox_checkmark"
74+
/>
75+
</div>
76+
</label>
77+
</span>
78+
</div>
79+
</div>
80+
</li>
81+
<li
82+
class="mx_SpaceHierarchy_roomTileWrapper"
83+
role="treeitem"
84+
>
85+
<div
86+
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
87+
role="button"
88+
tabindex="-1"
89+
>
90+
<div
91+
class="mx_SpaceHierarchy_roomTile_item"
92+
>
93+
<div
94+
class="mx_SpaceHierarchy_roomTile_avatar"
95+
>
96+
<img
97+
alt=""
98+
class="mx_BaseAvatar mx_BaseAvatar_image"
99+
data-testid="avatar-img"
100+
src="http://this.is.a.url/avatar.url/room.png"
101+
style="width: 20px; height: 20px;"
102+
/>
103+
</div>
104+
<div
105+
class="mx_SpaceHierarchy_roomTile_name"
106+
>
107+
Unnamed Room
108+
<div
109+
class="mx_SpaceHierarchy_roomTile_joined"
110+
>
111+
Joined
112+
</div>
113+
</div>
114+
<div
115+
class="mx_SpaceHierarchy_roomTile_info"
116+
>
117+
3 members
118+
·
119+
<span
120+
dir="auto"
121+
/>
122+
</div>
123+
</div>
124+
<div
125+
class="mx_SpaceHierarchy_actions"
126+
>
127+
<div
128+
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
129+
role="button"
130+
tabindex="-1"
131+
>
132+
View
133+
</div>
134+
<span
135+
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
136+
>
137+
<input
138+
id="checkbox_abdefghi"
139+
tabindex="-1"
140+
type="checkbox"
141+
/>
142+
<label
143+
for="checkbox_abdefghi"
144+
>
145+
<div
146+
class="mx_Checkbox_background"
147+
>
148+
<div
149+
class="mx_Checkbox_checkmark"
150+
/>
151+
</div>
152+
</label>
153+
</span>
154+
</div>
155+
</div>
156+
</li>
157+
;
158+
</div>
159+
`;

0 commit comments

Comments
 (0)