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

Commit b6b9ce3

Browse files
authored
When joining room in sub-space join the parents too (#11011)
* When joining room in sub-space join the parents too * Fix joined state not updating on sync * Add membership check * Update tests * Improve coverage * Make TS happier * Make TS happier
1 parent ca53b11 commit b6b9ce3

File tree

4 files changed

+474
-149
lines changed

4 files changed

+474
-149
lines changed

src/components/structures/SpaceHierarchy.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
3232
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
3333
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
3434
import { IHierarchyRelation, IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
35-
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
35+
import { ClientEvent, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
3636
import classNames from "classnames";
3737
import { sortBy, uniqBy } from "lodash";
3838
import { GuestAccess, HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
@@ -101,7 +101,7 @@ const Tile: React.FC<ITileProps> = ({
101101
children,
102102
}) => {
103103
const cli = useContext(MatrixClientContext);
104-
const [joinedRoom, setJoinedRoom] = useState<Room | undefined>(() => {
104+
const joinedRoom = useTypedEventEmitterState(cli, ClientEvent.Room, () => {
105105
const cliRoom = cli?.getRoom(room.room_id);
106106
return cliRoom?.getMyMembership() === "join" ? cliRoom : undefined;
107107
});
@@ -128,7 +128,6 @@ const Tile: React.FC<ITileProps> = ({
128128
ev.stopPropagation();
129129
onJoinRoomClick()
130130
.then(() => awaitRoomDownSync(cli, room.room_id))
131-
.then(setJoinedRoom)
132131
.finally(() => {
133132
setBusy(false);
134133
});
@@ -429,7 +428,7 @@ interface IHierarchyLevelProps {
429428
parents: Set<string>;
430429
selectedMap?: Map<string, Set<string>>;
431430
onViewRoomClick(roomId: string, roomType?: RoomType): void;
432-
onJoinRoomClick(roomId: string): Promise<unknown>;
431+
onJoinRoomClick(roomId: string, parents: Set<string>): Promise<unknown>;
433432
onToggleClick?(parentId: string, childId: string): void;
434433
}
435434

@@ -511,7 +510,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
511510
suggested={hierarchy.isSuggested(root.room_id, room.room_id)}
512511
selected={selectedMap?.get(root.room_id)?.has(room.room_id)}
513512
onViewRoomClick={() => onViewRoomClick(room.room_id, room.room_type as RoomType)}
514-
onJoinRoomClick={() => onJoinRoomClick(room.room_id)}
513+
onJoinRoomClick={() => onJoinRoomClick(room.room_id, newParents)}
515514
hasPermissions={hasPermissions}
516515
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined}
517516
/>
@@ -532,7 +531,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
532531
suggested={hierarchy.isSuggested(root.room_id, space.room_id)}
533532
selected={selectedMap?.get(root.room_id)?.has(space.room_id)}
534533
onViewRoomClick={() => onViewRoomClick(space.room_id, RoomType.Space)}
535-
onJoinRoomClick={() => onJoinRoomClick(space.room_id)}
534+
onJoinRoomClick={() => onJoinRoomClick(space.room_id, newParents)}
536535
hasPermissions={hasPermissions}
537536
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined}
538537
>
@@ -839,7 +838,14 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
839838
selectedMap={selected}
840839
onToggleClick={hasPermissions ? onToggleClick : undefined}
841840
onViewRoomClick={(roomId, roomType) => showRoom(cli, hierarchy, roomId, roomType)}
842-
onJoinRoomClick={(roomId) => joinRoom(cli, hierarchy, roomId)}
841+
onJoinRoomClick={async (roomId, parents) => {
842+
for (const parent of parents) {
843+
if (cli.getRoom(parent)?.getMyMembership() !== "join") {
844+
await joinRoom(cli, hierarchy, parent);
845+
}
846+
}
847+
await joinRoom(cli, hierarchy, roomId);
848+
}}
843849
/>
844850
</>
845851
);

test/components/structures/SpaceHierarchy-test.tsx

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

1717
import React from "react";
1818
import { mocked } from "jest-mock";
19-
import { render } from "@testing-library/react";
19+
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
2020
import { MatrixClient } from "matrix-js-sdk/src/client";
2121
import { Room } from "matrix-js-sdk/src/models/room";
2222
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
@@ -25,7 +25,7 @@ import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
2525
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
2626
import { mkStubRoom, stubClient } from "../../test-utils";
2727
import dispatcher from "../../../src/dispatcher/dispatcher";
28-
import { HierarchyLevel, showRoom, toLocalRoom } from "../../../src/components/structures/SpaceHierarchy";
28+
import SpaceHierarchy, { showRoom, toLocalRoom } from "../../../src/components/structures/SpaceHierarchy";
2929
import { Action } from "../../../src/dispatcher/actions";
3030
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
3131
import DMRoomMap from "../../../src/utils/DMRoomMap";
@@ -158,7 +158,18 @@ describe("SpaceHierarchy", () => {
158158
});
159159
});
160160

161-
describe("<HierarchyLevel />", () => {
161+
describe("<SpaceHierarchy />", () => {
162+
beforeEach(() => {
163+
// IntersectionObserver isn't available in test environment
164+
const mockIntersectionObserver = jest.fn();
165+
mockIntersectionObserver.mockReturnValue({
166+
observe: () => null,
167+
unobserve: () => null,
168+
disconnect: () => null,
169+
});
170+
window.IntersectionObserver = mockIntersectionObserver;
171+
});
172+
162173
stubClient();
163174
const client = MatrixClientPeg.get();
164175

@@ -167,55 +178,123 @@ describe("SpaceHierarchy", () => {
167178
} as unknown as DMRoomMap;
168179
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
169180

170-
const root = mkStubRoom("room-id-1", "Room 1", client);
171-
const room1 = mkStubRoom("room-id-2", "Room 2", client);
172-
const room2 = mkStubRoom("room-id-3", "Room 3", client);
181+
const root = mkStubRoom("space-id-1", "Space 1", client);
182+
const room1 = mkStubRoom("room-id-2", "Room 1", client);
183+
const room2 = mkStubRoom("room-id-3", "Room 2", client);
184+
const space1 = mkStubRoom("space-id-4", "Space 2", client);
185+
const room3 = mkStubRoom("room-id-5", "Room 3", client);
186+
mocked(client.getRooms).mockReturnValue([root]);
187+
mocked(client.getRoom).mockImplementation(
188+
(roomId) => client.getRooms().find((room) => room.roomId === roomId) ?? null,
189+
);
190+
[room1, room2, space1, room3].forEach((r) => mocked(r.getMyMembership).mockReturnValue("leave"));
173191

174-
const hierarchyRoot = {
192+
const hierarchyRoot: IHierarchyRoom = {
175193
room_id: root.roomId,
176194
num_joined_members: 1,
195+
room_type: "m.space",
177196
children_state: [
178197
{
179198
state_key: room1.roomId,
180199
content: { order: "1" },
200+
origin_server_ts: 111,
201+
type: "m.space.child",
202+
sender: "@other:server",
181203
},
182204
{
183205
state_key: room2.roomId,
184206
content: { order: "2" },
207+
origin_server_ts: 111,
208+
type: "m.space.child",
209+
sender: "@other:server",
210+
},
211+
{
212+
state_key: space1.roomId,
213+
content: { order: "3" },
214+
origin_server_ts: 111,
215+
type: "m.space.child",
216+
sender: "@other:server",
185217
},
186218
],
187-
} as IHierarchyRoom;
188-
const hierarchyRoom1 = { room_id: room1.roomId, num_joined_members: 2 } as IHierarchyRoom;
189-
const hierarchyRoom2 = { room_id: root.roomId, num_joined_members: 3 } as IHierarchyRoom;
190-
191-
const roomHierarchy = {
192-
roomMap: new Map([
193-
[root.roomId, hierarchyRoot],
194-
[room1.roomId, hierarchyRoom1],
195-
[room2.roomId, hierarchyRoom2],
196-
]),
197-
isSuggested: jest.fn(),
198-
} as unknown as RoomHierarchy;
199-
200-
it("renders", () => {
201-
const defaultProps = {
202-
root: hierarchyRoot,
203-
roomSet: new Set([hierarchyRoom1, hierarchyRoom2]),
204-
hierarchy: roomHierarchy,
205-
parents: new Set<string>(),
206-
selectedMap: new Map<string, Set<string>>(),
207-
onViewRoomClick: jest.fn(),
208-
onJoinRoomClick: jest.fn(),
209-
onToggleClick: jest.fn(),
210-
};
211-
const getComponent = (props = {}): React.ReactElement => (
212-
<MatrixClientContext.Provider value={client}>
213-
<HierarchyLevel {...defaultProps} {...props} />;
214-
</MatrixClientContext.Provider>
215-
);
216-
217-
const { container } = render(getComponent());
218-
expect(container).toMatchSnapshot();
219+
world_readable: true,
220+
guest_can_join: true,
221+
};
222+
const hierarchyRoom1: IHierarchyRoom = {
223+
room_id: room1.roomId,
224+
num_joined_members: 2,
225+
children_state: [],
226+
world_readable: true,
227+
guest_can_join: true,
228+
};
229+
const hierarchyRoom2: IHierarchyRoom = {
230+
room_id: room2.roomId,
231+
num_joined_members: 3,
232+
children_state: [],
233+
world_readable: true,
234+
guest_can_join: true,
235+
};
236+
const hierarchyRoom3: IHierarchyRoom = {
237+
name: "Nested room",
238+
room_id: room3.roomId,
239+
num_joined_members: 3,
240+
children_state: [],
241+
world_readable: true,
242+
guest_can_join: true,
243+
};
244+
const hierarchySpace1: IHierarchyRoom = {
245+
room_id: space1.roomId,
246+
name: "Nested space",
247+
num_joined_members: 1,
248+
room_type: "m.space",
249+
children_state: [
250+
{
251+
state_key: room3.roomId,
252+
content: { order: "1" },
253+
origin_server_ts: 111,
254+
type: "m.space.child",
255+
sender: "@other:server",
256+
},
257+
],
258+
world_readable: true,
259+
guest_can_join: true,
260+
};
261+
262+
mocked(client.getRoomHierarchy).mockResolvedValue({
263+
rooms: [hierarchyRoot, hierarchyRoom1, hierarchyRoom2, hierarchySpace1, hierarchyRoom3],
264+
});
265+
266+
const defaultProps = {
267+
space: root,
268+
showRoom: jest.fn(),
269+
};
270+
const getComponent = (props = {}): React.ReactElement => (
271+
<MatrixClientContext.Provider value={client}>
272+
<SpaceHierarchy {...defaultProps} {...props} />;
273+
</MatrixClientContext.Provider>
274+
);
275+
276+
it("renders", async () => {
277+
const { asFragment } = render(getComponent());
278+
// Wait for spinners to go away
279+
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
280+
expect(asFragment()).toMatchSnapshot();
281+
});
282+
283+
it("should join subspace when joining nested room", async () => {
284+
mocked(client.joinRoom).mockResolvedValue({} as Room);
285+
286+
const { getByText } = render(getComponent());
287+
// Wait for spinners to go away
288+
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
289+
const button = getByText("Nested room")!.closest("li")!.querySelector(".mx_AccessibleButton_kind_primary")!;
290+
fireEvent.click(button);
291+
292+
await waitFor(() => {
293+
expect(client.joinRoom).toHaveBeenCalledTimes(2);
294+
});
295+
// Joins subspace
296+
expect(client.joinRoom).toHaveBeenCalledWith(space1.roomId, expect.any(Object));
297+
expect(client.joinRoom).toHaveBeenCalledWith(room3.roomId, expect.any(Object));
219298
});
220299
});
221300
});

0 commit comments

Comments
 (0)