+
{ _t("Add") }
-
-
-
-
+ { canAddRooms &&
+
+ }
+ { canAddSubSpaces &&
+
+
+
+ }
>;
}
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 8c90c1ed18..e561951fae 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -191,6 +191,8 @@ const DmAuxButton = ({ tabIndex, dispatcher = defaultDispatcher }: IAuxButtonPro
title={_t("Start chat")}
/>;
}
+
+ return null;
};
const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index 6c2bee4a3c..a1dd62e735 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -70,6 +70,8 @@ import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from "../../../dispatcher/actions";
import { NotificationState } from "../../../stores/notifications/NotificationState";
import { ALTERNATE_KEY_NAME } from "../../../accessibility/KeyboardShortcuts";
+import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
+import { UIComponent } from "../../../settings/UIFeature";
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
const invites = useEventEmitterState
(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
@@ -235,6 +237,7 @@ const CreateSpaceButton = ({
role="treeitem"
>
(({
)) }
{ children }
-
+ {
+ shouldShowComponent(UIComponent.CreateSpaces) &&
+
+ }
+
;
});
diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts
index f033994214..9f149c4564 100644
--- a/src/settings/UIFeature.ts
+++ b/src/settings/UIFeature.ts
@@ -38,4 +38,5 @@ export enum UIFeature {
export enum UIComponent {
InviteUsers = "UIComponent.sendInvites",
CreateRooms = "UIComponent.roomCreation",
+ CreateSpaces = "UIComponent.spaceCreation",
}
diff --git a/test/components/views/context_menus/SpaceContextMenu-test.tsx b/test/components/views/context_menus/SpaceContextMenu-test.tsx
new file mode 100644
index 0000000000..d4c9f726b9
--- /dev/null
+++ b/test/components/views/context_menus/SpaceContextMenu-test.tsx
@@ -0,0 +1,222 @@
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import { mount } from 'enzyme';
+import { Room } from 'matrix-js-sdk';
+import { mocked } from 'jest-mock';
+import { act } from 'react-dom/test-utils';
+
+import '../../../skinned-sdk';
+import SpaceContextMenu from '../../../../src/components/views/context_menus/SpaceContextMenu';
+import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
+import { findByTestId } from '../../../utils/test-utils';
+import {
+ leaveSpace,
+ shouldShowSpaceSettings,
+ showCreateNewRoom,
+ showCreateNewSubspace,
+ showSpaceInvite,
+ showSpaceSettings,
+} from '../../../../src/utils/space';
+import { shouldShowComponent } from '../../../../src/customisations/helpers/UIComponents';
+import { UIComponent } from '../../../../src/settings/UIFeature';
+
+jest.mock('../../../../src/customisations/helpers/UIComponents', () => ({
+ shouldShowComponent: jest.fn(),
+}));
+
+jest.mock('../../../../src/utils/space', () => ({
+ leaveSpace: jest.fn(),
+ shouldShowSpaceSettings: jest.fn(),
+ showCreateNewRoom: jest.fn(),
+ showCreateNewSubspace: jest.fn(),
+ showSpaceInvite: jest.fn(),
+ showSpacePreferences: jest.fn(),
+ showSpaceSettings: jest.fn(),
+}));
+jest.mock('../../../../src/stores/spaces/SpaceStore', () => ({
+ spacesEnabled: true,
+}));
+
+describe('', () => {
+ const userId = '@test:server';
+ const mockClient = {
+ getUserId: jest.fn().mockReturnValue(userId),
+ };
+ const makeMockSpace = (props = {}) => ({
+ name: 'test space',
+ getJoinRule: jest.fn(),
+ canInvite: jest.fn(),
+ currentState: {
+ maySendStateEvent: jest.fn(),
+ },
+ client: mockClient,
+ getMyMembership: jest.fn(),
+ ...props,
+ }) as unknown as Room;
+ const defaultProps = {
+ space: makeMockSpace(),
+ onFinished: jest.fn(),
+ };
+ const getComponent = (props = {}) =>
+ mount(,
+ {
+ wrappingComponent: MatrixClientContext.Provider,
+ wrappingComponentProps: {
+ value: mockClient,
+ },
+ });
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ mockClient.getUserId.mockReturnValue(userId);
+ });
+
+ it('renders menu correctly', () => {
+ const component = getComponent();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('renders invite option when space is public', () => {
+ const space = makeMockSpace({
+ getJoinRule: jest.fn().mockReturnValue('public'),
+ });
+ const component = getComponent({ space });
+ expect(findByTestId(component, 'invite-option').length).toBeTruthy();
+ });
+ it('renders invite option when user is has invite rights for space', () => {
+ const space = makeMockSpace({
+ canInvite: jest.fn().mockReturnValue(true),
+ });
+ const component = getComponent({ space });
+ expect(space.canInvite).toHaveBeenCalledWith(userId);
+ expect(findByTestId(component, 'invite-option').length).toBeTruthy();
+ });
+ it('opens invite dialog when invite option is clicked', () => {
+ const space = makeMockSpace({
+ getJoinRule: jest.fn().mockReturnValue('public'),
+ });
+ const onFinished = jest.fn();
+ const component = getComponent({ space, onFinished });
+
+ act(() => {
+ findByTestId(component, 'invite-option').at(0).simulate('click');
+ });
+
+ expect(showSpaceInvite).toHaveBeenCalledWith(space);
+ expect(onFinished).toHaveBeenCalled();
+ });
+ it('renders space settings option when user has rights', () => {
+ mocked(shouldShowSpaceSettings).mockReturnValue(true);
+ const component = getComponent();
+ expect(shouldShowSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
+ expect(findByTestId(component, 'settings-option').length).toBeTruthy();
+ });
+ it('opens space settings when space settings option is clicked', () => {
+ mocked(shouldShowSpaceSettings).mockReturnValue(true);
+ const onFinished = jest.fn();
+ const component = getComponent({ onFinished });
+
+ act(() => {
+ findByTestId(component, 'settings-option').at(0).simulate('click');
+ });
+
+ expect(showSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
+ expect(onFinished).toHaveBeenCalled();
+ });
+ it('renders leave option when user does not have rights to see space settings', () => {
+ const component = getComponent();
+ expect(findByTestId(component, 'leave-option').length).toBeTruthy();
+ });
+ it('leaves space when leave option is clicked', () => {
+ const onFinished = jest.fn();
+ const component = getComponent({ onFinished });
+ act(() => {
+ findByTestId(component, 'leave-option').at(0).simulate('click');
+ });
+ expect(leaveSpace).toHaveBeenCalledWith(defaultProps.space);
+ expect(onFinished).toHaveBeenCalled();
+ });
+ describe('add children section', () => {
+ const space = makeMockSpace();
+ beforeEach(() => {
+ // set space to allow adding children to space
+ mocked(space.currentState.maySendStateEvent).mockReturnValue(true);
+ mocked(shouldShowComponent).mockReturnValue(true);
+ });
+ it('does not render section when user does not have permission to add children', () => {
+ mocked(space.currentState.maySendStateEvent).mockReturnValue(false);
+ const component = getComponent({ space });
+
+ expect(findByTestId(component, 'add-to-space-header').length).toBeFalsy();
+ expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
+ expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
+ });
+ it('does not render section when UIComponent customisations disable room and space creation', () => {
+ mocked(shouldShowComponent).mockReturnValue(false);
+ const component = getComponent({ space });
+
+ expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateRooms);
+ expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateSpaces);
+
+ expect(findByTestId(component, 'add-to-space-header').length).toBeFalsy();
+ expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
+ expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
+ });
+
+ it('renders section with add room button when UIComponent customisation allows CreateRoom', () => {
+ // only allow CreateRoom
+ mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateRooms);
+ const component = getComponent({ space });
+
+ expect(findByTestId(component, 'add-to-space-header').length).toBeTruthy();
+ expect(findByTestId(component, 'new-room-option').length).toBeTruthy();
+ expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
+ });
+
+ it('renders section with add space button when UIComponent customisation allows CreateSpace', () => {
+ // only allow CreateSpaces
+ mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateSpaces);
+ const component = getComponent({ space });
+
+ expect(findByTestId(component, 'add-to-space-header').length).toBeTruthy();
+ expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
+ expect(findByTestId(component, 'new-subspace-option').length).toBeTruthy();
+ });
+
+ it('opens create room dialog on add room button click', () => {
+ const onFinished = jest.fn();
+ const component = getComponent({ space, onFinished });
+
+ act(() => {
+ findByTestId(component, 'new-room-option').at(0).simulate('click');
+ });
+ expect(showCreateNewRoom).toHaveBeenCalledWith(space);
+ expect(onFinished).toHaveBeenCalled();
+ });
+ it('opens create space dialog on add space button click', () => {
+ const onFinished = jest.fn();
+ const component = getComponent({ space, onFinished });
+
+ act(() => {
+ findByTestId(component, 'new-subspace-option').at(0).simulate('click');
+ });
+ expect(showCreateNewSubspace).toHaveBeenCalledWith(space);
+ expect(onFinished).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/test/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap b/test/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap
new file mode 100644
index 0000000000..2fef194150
--- /dev/null
+++ b/test/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap
@@ -0,0 +1,500 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` renders menu correctly 1`] = `
+
+
+
+
+ }
+ >
+