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

When joining room in sub-space join the parents too #11011

Merged
merged 11 commits into from
Jun 1, 2023
20 changes: 13 additions & 7 deletions src/components/structures/SpaceHierarchy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { IHierarchyRelation, IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { ClientEvent, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import classNames from "classnames";
import { sortBy, uniqBy } from "lodash";
import { GuestAccess, HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
Expand Down Expand Up @@ -101,7 +101,7 @@ const Tile: React.FC<ITileProps> = ({
children,
}) => {
const cli = useContext(MatrixClientContext);
const [joinedRoom, setJoinedRoom] = useState<Room | undefined>(() => {
const joinedRoom = useTypedEventEmitterState(cli, ClientEvent.Room, () => {
const cliRoom = cli?.getRoom(room.room_id);
return cliRoom?.getMyMembership() === "join" ? cliRoom : undefined;
});
Expand All @@ -128,7 +128,6 @@ const Tile: React.FC<ITileProps> = ({
ev.stopPropagation();
onJoinRoomClick()
.then(() => awaitRoomDownSync(cli, room.room_id))
.then(setJoinedRoom)
.finally(() => {
setBusy(false);
});
Expand Down Expand Up @@ -429,7 +428,7 @@ interface IHierarchyLevelProps {
parents: Set<string>;
selectedMap?: Map<string, Set<string>>;
onViewRoomClick(roomId: string, roomType?: RoomType): void;
onJoinRoomClick(roomId: string): Promise<unknown>;
onJoinRoomClick(roomId: string, parents: Set<string>): Promise<unknown>;
onToggleClick?(parentId: string, childId: string): void;
}

Expand Down Expand Up @@ -511,7 +510,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
suggested={hierarchy.isSuggested(root.room_id, room.room_id)}
selected={selectedMap?.get(root.room_id)?.has(room.room_id)}
onViewRoomClick={() => onViewRoomClick(room.room_id, room.room_type as RoomType)}
onJoinRoomClick={() => onJoinRoomClick(room.room_id)}
onJoinRoomClick={() => onJoinRoomClick(room.room_id, newParents)}
hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined}
/>
Expand All @@ -532,7 +531,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
suggested={hierarchy.isSuggested(root.room_id, space.room_id)}
selected={selectedMap?.get(root.room_id)?.has(space.room_id)}
onViewRoomClick={() => onViewRoomClick(space.room_id, RoomType.Space)}
onJoinRoomClick={() => onJoinRoomClick(space.room_id)}
onJoinRoomClick={() => onJoinRoomClick(space.room_id, newParents)}
hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined}
>
Expand Down Expand Up @@ -839,7 +838,14 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
selectedMap={selected}
onToggleClick={hasPermissions ? onToggleClick : undefined}
onViewRoomClick={(roomId, roomType) => showRoom(cli, hierarchy, roomId, roomType)}
onJoinRoomClick={(roomId) => joinRoom(cli, hierarchy, roomId)}
onJoinRoomClick={async (roomId, parents) => {
for (const parent of parents) {
if (cli.getRoom(parent)?.getMyMembership() !== "join") {
await joinRoom(cli, hierarchy, parent);
}
}
await joinRoom(cli, hierarchy, roomId);
}}
/>
</>
);
Expand Down
157 changes: 118 additions & 39 deletions test/components/structures/SpaceHierarchy-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

import React from "react";
import { mocked } from "jest-mock";
import { render } from "@testing-library/react";
import { fireEvent, render, screen, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomHierarchy } from "matrix-js-sdk/src/room-hierarchy";
Expand All @@ -25,7 +25,7 @@ import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { mkStubRoom, stubClient } from "../../test-utils";
import dispatcher from "../../../src/dispatcher/dispatcher";
import { HierarchyLevel, showRoom, toLocalRoom } from "../../../src/components/structures/SpaceHierarchy";
import SpaceHierarchy, { showRoom, toLocalRoom } from "../../../src/components/structures/SpaceHierarchy";
import { Action } from "../../../src/dispatcher/actions";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import DMRoomMap from "../../../src/utils/DMRoomMap";
Expand Down Expand Up @@ -158,7 +158,18 @@ describe("SpaceHierarchy", () => {
});
});

describe("<HierarchyLevel />", () => {
describe("<SpaceHierarchy />", () => {
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
});
window.IntersectionObserver = mockIntersectionObserver;
});

stubClient();
const client = MatrixClientPeg.get();

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

const root = mkStubRoom("room-id-1", "Room 1", client);
const room1 = mkStubRoom("room-id-2", "Room 2", client);
const room2 = mkStubRoom("room-id-3", "Room 3", client);
const root = mkStubRoom("space-id-1", "Space 1", client);
const room1 = mkStubRoom("room-id-2", "Room 1", client);
const room2 = mkStubRoom("room-id-3", "Room 2", client);
const space1 = mkStubRoom("space-id-4", "Space 2", client);
const room3 = mkStubRoom("room-id-5", "Room 3", client);
mocked(client.getRooms).mockReturnValue([root]);
mocked(client.getRoom).mockImplementation(
(roomId) => client.getRooms().find((room) => room.roomId === roomId) ?? null,
);
[room1, room2, space1, room3].forEach((r) => mocked(r.getMyMembership).mockReturnValue("leave"));

const hierarchyRoot = {
const hierarchyRoot: IHierarchyRoom = {
room_id: root.roomId,
num_joined_members: 1,
room_type: "m.space",
children_state: [
{
state_key: room1.roomId,
content: { order: "1" },
origin_server_ts: 111,
type: "m.space.child",
sender: "@other:server",
},
{
state_key: room2.roomId,
content: { order: "2" },
origin_server_ts: 111,
type: "m.space.child",
sender: "@other:server",
},
{
state_key: space1.roomId,
content: { order: "3" },
origin_server_ts: 111,
type: "m.space.child",
sender: "@other:server",
},
],
} as IHierarchyRoom;
const hierarchyRoom1 = { room_id: room1.roomId, num_joined_members: 2 } as IHierarchyRoom;
const hierarchyRoom2 = { room_id: root.roomId, num_joined_members: 3 } as IHierarchyRoom;

const roomHierarchy = {
roomMap: new Map([
[root.roomId, hierarchyRoot],
[room1.roomId, hierarchyRoom1],
[room2.roomId, hierarchyRoom2],
]),
isSuggested: jest.fn(),
} as unknown as RoomHierarchy;

it("renders", () => {
const defaultProps = {
root: hierarchyRoot,
roomSet: new Set([hierarchyRoom1, hierarchyRoom2]),
hierarchy: roomHierarchy,
parents: new Set<string>(),
selectedMap: new Map<string, Set<string>>(),
onViewRoomClick: jest.fn(),
onJoinRoomClick: jest.fn(),
onToggleClick: jest.fn(),
};
const getComponent = (props = {}): React.ReactElement => (
<MatrixClientContext.Provider value={client}>
<HierarchyLevel {...defaultProps} {...props} />;
</MatrixClientContext.Provider>
);

const { container } = render(getComponent());
expect(container).toMatchSnapshot();
world_readable: true,
guest_can_join: true,
};
const hierarchyRoom1: IHierarchyRoom = {
room_id: room1.roomId,
num_joined_members: 2,
children_state: [],
world_readable: true,
guest_can_join: true,
};
const hierarchyRoom2: IHierarchyRoom = {
room_id: room2.roomId,
num_joined_members: 3,
children_state: [],
world_readable: true,
guest_can_join: true,
};
const hierarchyRoom3: IHierarchyRoom = {
name: "Nested room",
room_id: room3.roomId,
num_joined_members: 3,
children_state: [],
world_readable: true,
guest_can_join: true,
};
const hierarchySpace1: IHierarchyRoom = {
room_id: space1.roomId,
name: "Nested space",
num_joined_members: 1,
room_type: "m.space",
children_state: [
{
state_key: room3.roomId,
content: { order: "1" },
origin_server_ts: 111,
type: "m.space.child",
sender: "@other:server",
},
],
world_readable: true,
guest_can_join: true,
};

mocked(client.getRoomHierarchy).mockResolvedValue({
rooms: [hierarchyRoot, hierarchyRoom1, hierarchyRoom2, hierarchySpace1, hierarchyRoom3],
});

const defaultProps = {
space: root,
showRoom: jest.fn(),
};
const getComponent = (props = {}): React.ReactElement => (
<MatrixClientContext.Provider value={client}>
<SpaceHierarchy {...defaultProps} {...props} />;
</MatrixClientContext.Provider>
);

it("renders", async () => {
const { asFragment } = render(getComponent());
// Wait for spinners to go away
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
expect(asFragment()).toMatchSnapshot();
});

it("should join subspace when joining nested room", async () => {
mocked(client.joinRoom).mockResolvedValue({} as Room);

const { getByText } = render(getComponent());
// Wait for spinners to go away
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
const button = getByText("Nested room")!.closest("li")!.querySelector(".mx_AccessibleButton_kind_primary")!;
fireEvent.click(button);

await waitFor(() => {
expect(client.joinRoom).toHaveBeenCalledTimes(2);
});
// Joins subspace
expect(client.joinRoom).toHaveBeenCalledWith(space1.roomId, expect.any(Object));
expect(client.joinRoom).toHaveBeenCalledWith(room3.roomId, expect.any(Object));
});
});
});
Loading