1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-08-07 21:23:00 +03:00

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
This commit is contained in:
Michael Telatynski
2023-06-01 13:35:47 +01:00
committed by GitHub
parent ca53b11aa9
commit b6b9ce3c46
4 changed files with 490 additions and 165 deletions

View File

@@ -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";
@@ -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";
@@ -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();
@@ -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;
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,
};
const roomHierarchy = {
roomMap: new Map([
[root.roomId, hierarchyRoot],
[room1.roomId, hierarchyRoom1],
[room2.roomId, hierarchyRoom2],
]),
isSuggested: jest.fn(),
} as unknown as RoomHierarchy;
mocked(client.getRoomHierarchy).mockResolvedValue({
rooms: [hierarchyRoot, hierarchyRoom1, hierarchyRoom2, hierarchySpace1, hierarchyRoom3],
});
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 defaultProps = {
space: root,
showRoom: jest.fn(),
};
const getComponent = (props = {}): React.ReactElement => (
<MatrixClientContext.Provider value={client}>
<SpaceHierarchy {...defaultProps} {...props} />;
</MatrixClientContext.Provider>
);
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
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));
});
});
});

View File

@@ -1,161 +1,400 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SpaceHierarchy <HierarchyLevel /> renders 1`] = `
<div>
<li
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<DocumentFragment>
<div
class="mx_SearchBox mx_textinput"
>
<input
autocomplete="off"
class="mx_textinput_icon mx_textinput_search mx_SpaceHierarchy_search mx_textinput_icon mx_textinput_search"
data-testid="searchbox-input"
placeholder="Search names and descriptions"
type="text"
value=""
/>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
class="mx_AccessibleButton mx_SearchBox_closeButton"
role="button"
tabindex="-1"
/>
</div>
<div
class="mx_SpaceHierarchy_listHeader"
>
<h4
class="mx_SpaceHierarchy_listHeader_header"
>
Rooms and spaces
</h4>
<div
class="mx_SpaceHierarchy_listHeader_buttons"
>
<div
class="mx_SpaceHierarchy_roomTile_item"
aria-disabled="true"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_outline mx_AccessibleButton_disabled"
disabled=""
role="button"
tabindex="0"
>
Remove
</div>
<div
aria-disabled="true"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline mx_AccessibleButton_disabled"
disabled=""
role="button"
tabindex="0"
>
Mark as not suggested
</div>
</div>
</div>
<ul
aria-label="Space"
class="mx_SpaceHierarchy_list"
role="tree"
>
<li
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="0"
>
<div
class="mx_SpaceHierarchy_roomTile_avatar"
class="mx_SpaceHierarchy_roomTile_item"
>
<img
alt=""
class="mx_BaseAvatar mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="http://this.is.a.url/avatar.url/room.png"
style="width: 20px; height: 20px;"
/>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
<div
class="mx_SpaceHierarchy_roomTile_joined"
class="mx_SpaceHierarchy_roomTile_avatar"
>
Joined
<span
class="mx_BaseAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 13px; width: 20px; line-height: 20px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="data:image/png;base64,00"
style="width: 20px; height: 20px;"
/>
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
2 members
</div>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
class="mx_SpaceHierarchy_actions"
>
2 members
·
<span
dir="auto"
/>
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="-1"
>
View
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_abdefghi"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
<div
class="mx_Checkbox_background"
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_abdefghi"
tabindex="0"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</div>
</div>
</div>
</li>
<li
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="-1"
</li>
<li
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_SpaceHierarchy_roomTile_item"
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="-1"
>
<div
class="mx_SpaceHierarchy_roomTile_avatar"
class="mx_SpaceHierarchy_roomTile_item"
>
<img
alt=""
class="mx_BaseAvatar mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="http://this.is.a.url/avatar.url/room.png"
style="width: 20px; height: 20px;"
/>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
<div
class="mx_SpaceHierarchy_roomTile_joined"
class="mx_SpaceHierarchy_roomTile_avatar"
>
Joined
<span
class="mx_BaseAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 13px; width: 20px; line-height: 20px;"
>
U
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="data:image/png;base64,00"
style="width: 20px; height: 20px;"
/>
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
3 members
</div>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
class="mx_SpaceHierarchy_actions"
>
3 members
·
<span
dir="auto"
/>
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="-1"
>
View
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_abdefghi"
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<div
class="mx_Checkbox_background"
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_abdefghi"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</div>
</div>
</div>
</li>
</li>
<li
aria-expanded="true"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile mx_SpaceHierarchy_subspace"
role="button"
tabindex="-1"
>
<div
class="mx_SpaceHierarchy_roomTile_item"
>
<div
class="mx_SpaceHierarchy_roomTile_avatar"
>
<span
class="mx_BaseAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 13px; width: 20px; line-height: 20px;"
>
N
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="data:image/png;base64,00"
style="width: 20px; height: 20px;"
/>
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Nested space
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
1 member · 1 room
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="-1"
>
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_abdefghi"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</div>
<div
class="mx_SpaceHierarchy_subspace_toggle mx_SpaceHierarchy_subspace_toggle_shown"
/>
</div>
<div
class="mx_SpaceHierarchy_subspace_children"
role="group"
/>
</li>
<li
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
<div
class="mx_AccessibleButton mx_SpaceHierarchy_roomTile"
role="button"
tabindex="-1"
>
<div
class="mx_SpaceHierarchy_roomTile_item"
>
<div
class="mx_SpaceHierarchy_roomTile_avatar"
>
<span
class="mx_BaseAvatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 13px; width: 20px; line-height: 20px;"
>
N
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
loading="lazy"
src="data:image/png;base64,00"
style="width: 20px; height: 20px;"
/>
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Nested room
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
>
3 members
</div>
</div>
<div
class="mx_SpaceHierarchy_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="-1"
>
Join
</div>
<div
aria-describedby="mx_TooltipTarget_abdefghi"
class="mx_TextWithTooltip_target"
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
disabled=""
id="checkbox_abdefghi"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_abdefghi"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</div>
</div>
</div>
</li>
</ul>
;
</div>
</DocumentFragment>
`;