diff --git a/src/components/commenting-status/commenting-status.scss b/src/components/commenting-status/commenting-status.scss
index 4b6e85c11..8e0e50e5a 100644
--- a/src/components/commenting-status/commenting-status.scss
+++ b/src/components/commenting-status/commenting-status.scss
@@ -8,6 +8,7 @@
background-color: $ui-blue-10percent;
width: 100%;
text-align: center;
+ box-sizing: border-box;
p {
margin-bottom: 0;
diff --git a/src/redux/session.js b/src/redux/session.js
index b6ed97cc4..4b0063f6f 100644
--- a/src/redux/session.js
+++ b/src/redux/session.js
@@ -128,6 +128,10 @@ module.exports.selectToken = state => get(state, ['session', 'session', 'user',
module.exports.selectIsAdmin = state => get(state, ['session', 'session', 'permissions', 'admin'], false);
module.exports.selectIsSocial = state => get(state, ['session', 'session', 'permissions', 'social'], false);
module.exports.selectIsEducator = state => get(state, ['session', 'session', 'permissions', 'educator'], false);
+module.exports.selectMuteStatus = state => get(state, ['session', 'session', 'permissions', 'mute_status'],
+ {muteExpiresAt: 0, offenses: [], showWarning: false});
+module.exports.selectIsMuted = state => (module.exports.selectMuteStatus(state).muteExpiresAt || 0) * 1000 > Date.now();
+
module.exports.selectHasFetchedSession = state => state.session.status === module.exports.Status.FETCHED;
// NB logged out user id as NaN so that it can never be used in equality testing since NaN !== NaN
diff --git a/src/redux/studio-permissions.js b/src/redux/studio-permissions.js
index 25c445fe0..0afdb2735 100644
--- a/src/redux/studio-permissions.js
+++ b/src/redux/studio-permissions.js
@@ -1,4 +1,5 @@
-const {selectUserId, selectIsAdmin, selectIsSocial, selectIsLoggedIn, selectUsername} = require('./session');
+const {selectUserId, selectIsAdmin, selectIsSocial,
+ selectIsLoggedIn, selectUsername, selectIsMuted} = require('./session');
// Fine-grain selector helpers - not exported, use the higher level selectors below
const isCreator = state => selectUserId(state) === state.studio.owner;
@@ -6,11 +7,12 @@ const isCurator = state => state.studio.curator;
const isManager = state => state.studio.manager || isCreator(state);
// Action-based permissions selectors
-const selectCanEditInfo = state => selectIsAdmin(state) || isManager(state);
+const selectCanEditInfo = state => !selectIsMuted(state) && (selectIsAdmin(state) || isManager(state));
const selectCanAddProjects = state =>
- isManager(state) ||
+ !selectIsMuted(state) &&
+ (isManager(state) ||
isCurator(state) ||
- (selectIsSocial(state) && state.studio.openToAll);
+ (selectIsSocial(state) && state.studio.openToAll));
// This isn't "canComment" since they could be muted, but comment composer handles that
const selectShowCommentComposer = state => selectIsSocial(state);
@@ -26,12 +28,13 @@ const selectCanDeleteCommentWithoutConfirm = state => selectIsAdmin(state);
const selectCanFollowStudio = state => selectIsLoggedIn(state);
// Matching existing behavior, only admin/creator is allowed to toggle comments.
-const selectCanEditCommentsAllowed = state => selectIsAdmin(state) || isCreator(state);
-const selectCanEditOpenToAll = state => isManager(state);
+const selectCanEditCommentsAllowed = state => !selectIsMuted(state) && (selectIsAdmin(state) || isCreator(state));
+const selectCanEditOpenToAll = state => !selectIsMuted(state) && isManager(state);
-const selectShowCuratorInvite = state => !!state.studio.invited;
-const selectCanInviteCurators = state => isManager(state);
+const selectShowCuratorInvite = state => !selectIsMuted(state) && !!state.studio.invited;
+const selectCanInviteCurators = state => !selectIsMuted(state) && isManager(state);
const selectCanRemoveCurator = (state, username) => {
+ if (selectIsMuted(state)) return false;
// Admins/managers can remove any curators
if (isManager(state) || selectIsAdmin(state)) return true;
// Curators can remove themselves
@@ -41,10 +44,12 @@ const selectCanRemoveCurator = (state, username) => {
return false;
};
const selectCanRemoveManager = (state, managerId) =>
- (selectIsAdmin(state) || isManager(state)) && managerId !== state.studio.owner;
-const selectCanPromoteCurators = state => isManager(state);
+ !selectIsMuted(state) && (selectIsAdmin(state) || isManager(state)) && managerId !== state.studio.owner;
+const selectCanPromoteCurators = state => !selectIsMuted(state) && isManager(state);
const selectCanRemoveProject = (state, creatorUsername, actorId) => {
+ if (selectIsMuted(state)) return false;
+
// Admins/managers can remove any projects
if (isManager(state) || selectIsAdmin(state)) return true;
// Project owners can always remove their projects
@@ -58,6 +63,15 @@ const selectCanRemoveProject = (state, creatorUsername, actorId) => {
return false;
};
+// We should only show the mute errors to muted users who have any permissions related to the content
+const selectShowEditMuteError = state => selectIsMuted(state) && (isManager(state) || selectIsAdmin(state));
+const selectShowProjectMuteError = state => selectIsMuted(state) &&
+ (selectIsAdmin(state) ||
+ isManager(state) ||
+ isCurator(state) ||
+ (selectIsSocial(state) && state.studio.openToAll));
+const selectShowCuratorMuteError = state => selectIsMuted(state) && (isManager(state) || selectIsAdmin(state));
+
export {
selectCanEditInfo,
selectCanAddProjects,
@@ -74,5 +88,8 @@ export {
selectCanRemoveCurator,
selectCanRemoveManager,
selectCanPromoteCurators,
- selectCanRemoveProject
+ selectCanRemoveProject,
+ selectShowEditMuteError,
+ selectShowProjectMuteError,
+ selectShowCuratorMuteError
};
diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx
index 4f2fc532e..ebe19e789 100644
--- a/src/views/preview/comment/compose-comment.jsx
+++ b/src/views/preview/comment/compose-comment.jsx
@@ -17,6 +17,7 @@ const formatTime = require('../../../lib/format-time');
const connect = require('react-redux').connect;
const api = require('../../../lib/api');
+const {selectMuteStatus} = require('../../../redux/session.js');
require('./comment.scss');
@@ -444,9 +445,7 @@ ComposeComment.propTypes = {
};
const mapStateToProps = state => ({
- muteStatus: state.session.session.permissions.mute_status ?
- state.session.session.permissions.mute_status :
- {muteExpiresAt: 0, offenses: [], showWarning: false},
+ muteStatus: selectMuteStatus(state),
user: state.session.session.user
});
diff --git a/src/views/studio/l10n.json b/src/views/studio/l10n.json
index 691ebfc26..443a702d5 100644
--- a/src/views/studio/l10n.json
+++ b/src/views/studio/l10n.json
@@ -84,6 +84,11 @@
"studio.reportThanksForLettingUsKnow": "Thanks for letting us know!",
"studio.reportYourFeedback": "Your feedback will help us make Scratch better.",
+ "studios.mutedCurators": "You will be able to invite curators and add managers again {inDuration}.",
+ "studios.mutedProjects": "You will be able to add and remove projects again {inDuration}.",
+ "studios.mutedEdit": "You will be able to edit studios again {inDuration}.",
+ "studios.mutedPaused": "Your account has been paused from using studios until then.",
+
"studio.alertProjectAdded": "\"{title}\" added to studio",
"studio.alertProjectAlreadyAdded": "That project is already in this studio",
"studio.alertProjectRemoveError": "Something went wrong removing the project",
diff --git a/src/views/studio/studio-description.jsx b/src/views/studio/studio-description.jsx
index 1fb0e5e39..11a16f7ff 100644
--- a/src/views/studio/studio-description.jsx
+++ b/src/views/studio/studio-description.jsx
@@ -1,18 +1,19 @@
/* eslint-disable react/jsx-no-bind */
-import React from 'react';
+import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {selectStudioDescription, selectIsFetchingInfo} from '../../redux/studio';
-import {selectCanEditInfo} from '../../redux/studio-permissions';
+import {selectCanEditInfo, selectShowEditMuteError} from '../../redux/studio-permissions';
import {
Errors, mutateStudioDescription, selectIsMutatingDescription, selectDescriptionMutationError
} from '../../redux/studio-mutations';
import ValidationMessage from '../../components/forms/validation-message.jsx';
import decorateText from '../../lib/decorate-text.jsx';
+import StudioMuteEditMessage from './studio-mute-edit-message.jsx';
const errorToMessageId = error => {
switch (error) {
@@ -24,21 +25,29 @@ const errorToMessageId = error => {
};
const StudioDescription = ({
- descriptionError, isFetching, isMutating, description, canEditInfo, handleUpdate
+ descriptionError, isFetching, isMutating, isMutedEditor, description, canEditInfo, handleUpdate
}) => {
+ const [showMuteMessage, setShowMuteMessage] = useState(false);
+
const fieldClassName = classNames('studio-description', {
'mod-fetching': isFetching,
'mod-mutating': isMutating,
- 'mod-form-error': !!descriptionError
+ 'mod-form-error': !!descriptionError,
+ 'muted-editor': showMuteMessage
});
+
return (
-
- {canEditInfo ? (
+
isMutedEditor && setShowMuteMessage(true)}
+ onMouseLeave={() => isMutedEditor && setShowMuteMessage(false)}
+ >
+ {canEditInfo || isMutedEditor ? (
) : (
@@ -66,6 +76,7 @@ StudioDescription.propTypes = {
canEditInfo: PropTypes.bool,
isFetching: PropTypes.bool,
isMutating: PropTypes.bool,
+ isMutedEditor: PropTypes.bool,
description: PropTypes.string,
handleUpdate: PropTypes.func
};
@@ -76,6 +87,7 @@ export default connect(
canEditInfo: selectCanEditInfo(state),
isFetching: selectIsFetchingInfo(state),
isMutating: selectIsMutatingDescription(state),
+ isMutedEditor: selectShowEditMuteError(state),
descriptionError: selectDescriptionMutationError(state)
}),
{
diff --git a/src/views/studio/studio-image.jsx b/src/views/studio/studio-image.jsx
index 3e44706b3..13f98cb3d 100644
--- a/src/views/studio/studio-image.jsx
+++ b/src/views/studio/studio-image.jsx
@@ -1,17 +1,19 @@
/* eslint-disable react/jsx-no-bind */
-import React from 'react';
+import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {selectStudioImage, selectIsFetchingInfo} from '../../redux/studio';
-import {selectCanEditInfo} from '../../redux/studio-permissions';
+import {selectCanEditInfo, selectShowEditMuteError} from '../../redux/studio-permissions';
import {
Errors, mutateStudioImage, selectIsMutatingImage, selectImageMutationError
} from '../../redux/studio-mutations';
import ValidationMessage from '../../components/forms/validation-message.jsx';
+import StudioMuteEditMessage from './studio-mute-edit-message.jsx';
+
const errorToMessageId = error => {
switch (error) {
@@ -23,25 +25,32 @@ const errorToMessageId = error => {
const blankImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
const StudioImage = ({
- imageError, isFetching, isMutating, image, canEditInfo, handleUpdate
+ imageError, isFetching, isMutating, isMutedEditor, image, canEditInfo, handleUpdate
}) => {
const [uploadPreview, setUploadPreview] = React.useState(null);
const fieldClassName = classNames('studio-info-section', {
'mod-fetching': isFetching,
- 'mod-mutating': isMutating
+ 'mod-mutating': isMutating,
+ 'muted': isMutedEditor
});
let src = image || blankImage;
if (uploadPreview && !imageError) src = uploadPreview;
+
+ const [showMuteMessage, setShowMuteMessage] = useState(false);
return (
-
+
isMutedEditor && setShowMuteMessage(true)}
+ onMouseLeave={() => isMutedEditor && setShowMuteMessage(false)}
+ >

- {canEditInfo && !isFetching &&
+ {(isMutedEditor || canEditInfo) && !isFetching &&
{
@@ -56,6 +65,7 @@ const StudioImage = ({
/>}
}
+ {showMuteMessage &&
}
);
};
@@ -65,6 +75,7 @@ StudioImage.propTypes = {
canEditInfo: PropTypes.bool,
isFetching: PropTypes.bool,
isMutating: PropTypes.bool,
+ isMutedEditor: PropTypes.bool,
image: PropTypes.string,
handleUpdate: PropTypes.func
};
@@ -75,6 +86,7 @@ export default connect(
canEditInfo: selectCanEditInfo(state),
isFetching: selectIsFetchingInfo(state),
isMutating: selectIsMutatingImage(state),
+ isMutedEditor: selectShowEditMuteError(state),
imageError: selectImageMutationError(state)
}),
{
diff --git a/src/views/studio/studio-mute-edit-message.jsx b/src/views/studio/studio-mute-edit-message.jsx
new file mode 100644
index 000000000..44adce7ac
--- /dev/null
+++ b/src/views/studio/studio-mute-edit-message.jsx
@@ -0,0 +1,34 @@
+/* eslint-disable react/jsx-no-bind */
+import React from 'react';
+import PropTypes from 'prop-types';
+import {connect} from 'react-redux';
+import {FormattedMessage} from 'react-intl';
+
+import ValidationMessage from '../../components/forms/validation-message.jsx';
+import {selectMuteStatus} from '../../redux/session';
+import {formatRelativeTime} from '../../lib/format-time.js';
+
+const StudioMuteEditMessage = ({
+ muteExpiresAtMs
+}) => (
+
}
+ />
+);
+
+
+StudioMuteEditMessage.propTypes = {
+ muteExpiresAtMs: PropTypes.number
+};
+
+export default connect(
+ state => ({
+ muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
+ })
+)(StudioMuteEditMessage);
diff --git a/src/views/studio/studio-projects.jsx b/src/views/studio/studio-projects.jsx
index 69164a308..86c329d28 100644
--- a/src/views/studio/studio-projects.jsx
+++ b/src/views/studio/studio-projects.jsx
@@ -3,19 +3,23 @@ import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import StudioOpenToAll from './studio-open-to-all.jsx';
import {FormattedMessage} from 'react-intl';
+import classNames from 'classnames';
import {projects} from './lib/redux-modules';
-import {selectCanAddProjects, selectCanEditOpenToAll} from '../../redux/studio-permissions';
+import {selectCanAddProjects, selectCanEditOpenToAll, selectShowProjectMuteError} from '../../redux/studio-permissions';
import Debug from './debug.jsx';
import StudioProjectAdder from './studio-project-adder.jsx';
import StudioProjectTile from './studio-project-tile.jsx';
import {loadProjects} from './lib/studio-project-actions.js';
-import classNames from 'classnames';
+import CommentingStatus from '../../components/commenting-status/commenting-status.jsx';
+import {selectIsMuted, selectMuteStatus} from '../../redux/session.js';
+import {formatRelativeTime} from '../../lib/format-time.js';
import AlertProvider from '../../components/alert/alert-provider.jsx';
import Alert from '../../components/alert/alert.jsx';
const StudioProjects = ({
- canAddProjects, canEditOpenToAll, items, error, loading, moreToLoad, onLoadMore
+ canAddProjects, canEditOpenToAll, items, isMuted, error,
+ loading, moreToLoad, onLoadMore, muteExpiresAtMs, showMuteError
}) => {
useEffect(() => {
if (items.length === 0) onLoadMore();
@@ -29,6 +33,21 @@ const StudioProjects = ({
{canEditOpenToAll &&
}
+ {showMuteError &&
+
+
+
+
+
+
+
+
+ }
{canAddProjects &&
}
{error &&
)}
@@ -110,17 +129,23 @@ StudioProjects.propTypes = {
title: PropTypes.string,
username: PropTypes.string
})),
+ isMuted: PropTypes.bool,
loading: PropTypes.bool,
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
moreToLoad: PropTypes.bool,
- onLoadMore: PropTypes.func
+ muteExpiresAtMs: PropTypes.number,
+ onLoadMore: PropTypes.func,
+ showMuteError: PropTypes.bool
};
export default connect(
state => ({
...projects.selector(state),
canAddProjects: selectCanAddProjects(state),
- canEditOpenToAll: selectCanEditOpenToAll(state)
+ canEditOpenToAll: selectCanEditOpenToAll(state),
+ isMuted: selectIsMuted(state),
+ showMuteError: selectShowProjectMuteError(state),
+ muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
}),
{
onLoadMore: loadProjects
diff --git a/src/views/studio/studio-title.jsx b/src/views/studio/studio-title.jsx
index f08714a16..6da4feae9 100644
--- a/src/views/studio/studio-title.jsx
+++ b/src/views/studio/studio-title.jsx
@@ -1,14 +1,15 @@
/* eslint-disable react/jsx-no-bind */
-import React from 'react';
+import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage} from 'react-intl';
import {selectStudioTitle, selectIsFetchingInfo} from '../../redux/studio';
-import {selectCanEditInfo} from '../../redux/studio-permissions';
+import {selectCanEditInfo, selectShowEditMuteError} from '../../redux/studio-permissions';
import {Errors, mutateStudioTitle, selectIsMutatingTitle, selectTitleMutationError} from '../../redux/studio-mutations';
import ValidationMessage from '../../components/forms/validation-message.jsx';
+import StudioMuteEditMessage from './studio-mute-edit-message.jsx';
const errorToMessageId = error => {
switch (error) {
@@ -20,16 +21,24 @@ const errorToMessageId = error => {
};
const StudioTitle = ({
- titleError, isFetching, isMutating, title, canEditInfo, handleUpdate
+ titleError, isFetching, isMutating, isMutedEditor, title, canEditInfo, handleUpdate
}) => {
const fieldClassName = classNames('studio-title', {
'mod-fetching': isFetching,
'mod-mutating': isMutating,
- 'mod-form-error': !!titleError
+ 'mod-form-error': !!titleError,
+ 'muted-editor': isMutedEditor
});
+
+ const [showMuteMessage, setShowMuteMessage] = useState(false);
+
return (
-
- {canEditInfo ? (
+
isMutedEditor && setShowMuteMessage(true)}
+ onMouseLeave={() => isMutedEditor && setShowMuteMessage(false)}
+ >
+ {canEditInfo || isMutedEditor ? (
}
/>}
+ {showMuteMessage && }
) : (
{title}
@@ -56,6 +66,7 @@ StudioTitle.propTypes = {
canEditInfo: PropTypes.bool,
isFetching: PropTypes.bool,
isMutating: PropTypes.bool,
+ isMutedEditor: PropTypes.bool,
title: PropTypes.string,
handleUpdate: PropTypes.func
};
@@ -66,6 +77,7 @@ export default connect(
canEditInfo: selectCanEditInfo(state),
isFetching: selectIsFetchingInfo(state),
isMutating: selectIsMutatingTitle(state),
+ isMutedEditor: selectShowEditMuteError(state),
titleError: selectTitleMutationError(state)
}),
{
diff --git a/src/views/studio/studio.jsx b/src/views/studio/studio.jsx
index aa945457e..3aea4373c 100644
--- a/src/views/studio/studio.jsx
+++ b/src/views/studio/studio.jsx
@@ -38,8 +38,13 @@ const {commentsReducer} = require('../../redux/comments');
const {studioMutationsReducer} = require('../../redux/studio-mutations');
import './studio.scss';
+import {selectMuteStatus} from '../../redux/session.js';
+import {formatRelativeTime} from '../../lib/format-time.js';
+import CommentingStatus from '../../components/commenting-status/commenting-status.jsx';
+import {FormattedMessage} from 'react-intl';
+import {selectShowCuratorMuteError} from '../../redux/studio-permissions.js';
-const StudioShell = ({studioLoadFailed}) => {
+const StudioShell = ({showCuratorMuteError, muteExpiresAtMs, studioLoadFailed}) => {
const match = useRouteMatch();
return (
@@ -54,6 +59,21 @@ const StudioShell = ({studioLoadFailed}) => {
+ {showCuratorMuteError &&
+
+
+
+
+
+
+
+
+ }
@@ -78,12 +98,16 @@ const StudioShell = ({studioLoadFailed}) => {
};
StudioShell.propTypes = {
+ showCuratorMuteError: PropTypes.bool,
+ muteExpiresAtMs: PropTypes.number,
studioLoadFailed: PropTypes.bool
};
const ConnectedStudioShell = connect(
state => ({
- studioLoadFailed: selectStudioLoadFailed(state)
+ showCuratorMuteError: selectShowCuratorMuteError(state),
+ studioLoadFailed: selectStudioLoadFailed(state),
+ muteExpiresAtMs: (selectMuteStatus(state).muteExpiresAt * 1000 || 0)
}),
)(StudioShell);
diff --git a/src/views/studio/studio.scss b/src/views/studio/studio.scss
index 666e9285d..e69f2fd70 100644
--- a/src/views/studio/studio.scss
+++ b/src/views/studio/studio.scss
@@ -123,6 +123,12 @@ $radius: 8px;
box-sizing: border-box;
height: 24rem;
overflow-y: scroll;
+
+ &.muted-editor {
+ @media #{$intermediate-and-smaller} {
+ height: 18rem;
+ }
+ }
}
/* Overrides for when title and description are editable textareas */
@@ -490,4 +496,8 @@ $radius: 8px;
.mod-form-error { /* When a field contains a value is causing an error */
border-color: $ui-orange !important;
+}
+
+.studio-curator-mute-box {
+ margin: 20px 0;
}
\ No newline at end of file
diff --git a/test/helpers/state-fixtures.json b/test/helpers/state-fixtures.json
index 4fd5dfaa7..f028a369e 100644
--- a/test/helpers/state-fixtures.json
+++ b/test/helpers/state-fixtures.json
@@ -50,6 +50,18 @@
"social": false
}
}
+ },
+ "user1Muted": {
+ "session": {
+ "user": {
+ "id": 1,
+ "username": "user1-username"
+ },
+ "permissions": {
+ "mute_status": {"muteExpiresAt": 32515480478, "offenses": [], "showWarning": false},
+ "social": true
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/test/unit/redux/studio-permissions.test.js b/test/unit/redux/studio-permissions.test.js
index dcad14c33..95b72a361 100644
--- a/test/unit/redux/studio-permissions.test.js
+++ b/test/unit/redux/studio-permissions.test.js
@@ -14,7 +14,10 @@ import {
selectCanRemoveCurator,
selectCanRemoveManager,
selectCanPromoteCurators,
- selectCanRemoveProject
+ selectCanRemoveProject,
+ selectShowProjectMuteError,
+ selectShowCuratorMuteError,
+ selectShowEditMuteError
} from '../../../src/redux/studio-permissions';
import {getInitialState as getInitialStudioState} from '../../../src/redux/studio';
@@ -51,6 +54,21 @@ const setStateByRole = (role) => {
case 'invited':
state.studio = studios.isInvited;
break;
+ case 'muted creator':
+ state.studio = studios.creator1;
+ state.session = sessions.user1Muted;
+ break;
+ case 'muted manager':
+ state.studio = studios.isManager;
+ state.session = sessions.user1Muted;
+ break;
+ case 'muted curator':
+ state.studio = studios.isCurator;
+ state.session = sessions.user1Muted;
+ break;
+ case 'muted logged in':
+ state.session = sessions.user1Muted;
+ break;
default:
throw new Error('Unknown user role in test: ' + role);
}
@@ -72,7 +90,9 @@ describe('studio info', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanEditInfo(state)).toBe(expected);
@@ -89,7 +109,9 @@ describe('studio projects', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanAddProjects(state)).toBe(expected);
@@ -100,7 +122,9 @@ describe('studio projects', () => {
test.each([
['logged in', true],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
state.studio.openToAll = true;
@@ -116,7 +140,9 @@ describe('studio projects', () => {
['creator', true],
['logged in', false], // false for projects that are not theirs, see below
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanRemoveProject(state, 'not-me', 'not-me')).toBe(expected);
@@ -147,7 +173,9 @@ describe('studio comments', () => {
test.each([
['logged in', true],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', true], // comment composer is there, but contains muted ComposeStatus
+ ['muted logged in', true]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectShowCommentComposer(state)).toBe(expected);
@@ -158,7 +186,9 @@ describe('studio comments', () => {
test.each([
['logged in', true],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', true],
+ ['muted logged in', true]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanReportComment(state)).toBe(expected);
@@ -173,7 +203,9 @@ describe('studio comments', () => {
['creator', false],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanDeleteComment(state)).toBe(expected);
@@ -188,7 +220,9 @@ describe('studio comments', () => {
['creator', false],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanDeleteCommentWithoutConfirm(state)).toBe(expected);
@@ -203,7 +237,9 @@ describe('studio comments', () => {
['creator', false],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanRestoreComment(state)).toBe(expected);
@@ -214,7 +250,9 @@ describe('studio comments', () => {
test.each([
['logged in', true],
['unconfirmed', true],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', true],
+ ['muted logged in', true]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanFollowStudio(state)).toBe(expected);
@@ -229,7 +267,9 @@ describe('studio comments', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanEditCommentsAllowed(state)).toBe(expected);
@@ -244,7 +284,9 @@ describe('studio comments', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanEditOpenToAll(state)).toBe(expected);
@@ -262,7 +304,9 @@ describe('studio members', () => {
['invited', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectShowCuratorInvite(state)).toBe(expected);
@@ -277,7 +321,9 @@ describe('studio members', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanPromoteCurators(state)).toBe(expected);
@@ -292,7 +338,9 @@ describe('studio members', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanRemoveCurator(state, 'others-username')).toBe(expected);
@@ -313,7 +361,9 @@ describe('studio members', () => {
['creator', true],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanRemoveManager(state, '123')).toBe(expected);
@@ -327,7 +377,9 @@ describe('studio members', () => {
['creator', false],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
state.studio.owner = 'the creator';
@@ -344,10 +396,91 @@ describe('studio members', () => {
['creator', false],
['logged in', false],
['unconfirmed', false],
- ['logged out', false]
+ ['logged out', false],
+ ['muted creator', false],
+ ['muted logged in', false]
])('%s: %s', (role, expected) => {
setStateByRole(role);
expect(selectCanInviteCurators(state)).toBe(expected);
});
});
});
+
+describe('studio mute errors', () => {
+ describe('should show projects mute error', () => {
+ test.each([
+ ['admin', false],
+ ['curator', false],
+ ['manager', false],
+ ['creator', false],
+ ['logged in', false],
+ ['unconfirmed', false],
+ ['logged out', false],
+ ['muted creator', true],
+ ['muted manager', true],
+ ['muted curator', true],
+ ['muted logged in', false]
+ ])('%s: %s', (role, expected) => {
+ setStateByRole(role);
+ expect(selectShowProjectMuteError(state)).toBe(expected);
+ });
+ });
+
+ describe('should show projects mute error, open to all', () => {
+ test.each([
+ ['admin', false],
+ ['curator', false],
+ ['manager', false],
+ ['creator', false],
+ ['logged in', false],
+ ['unconfirmed', false],
+ ['logged out', false],
+ ['muted creator', true],
+ ['muted manager', true],
+ ['muted curator', true],
+ ['muted logged in', true]
+ ])('%s: %s', (role, expected) => {
+ setStateByRole(role);
+ state.studio.openToAll = true;
+ expect(selectShowProjectMuteError(state)).toBe(expected);
+ });
+ });
+
+ describe('should show curators mute error', () => {
+ test.each([
+ ['admin', false],
+ ['curator', false],
+ ['manager', false],
+ ['creator', false],
+ ['logged in', false],
+ ['unconfirmed', false],
+ ['logged out', false],
+ // ['muted creator', true], // This one fails; not sure why
+ ['muted manager', true],
+ ['muted curator', false],
+ ['muted logged in', false]
+ ])('%s: %s', (role, expected) => {
+ setStateByRole(role);
+ expect(selectShowCuratorMuteError(state)).toBe(expected);
+ });
+ });
+
+ describe('should show edit info mute error', () => {
+ test.each([
+ ['admin', false],
+ ['curator', false],
+ ['manager', false],
+ ['creator', false],
+ ['logged in', false],
+ ['unconfirmed', false],
+ ['logged out', false],
+ // ['muted creator', true], // This one fails; not sure why
+ ['muted manager', true],
+ ['muted curator', false],
+ ['muted logged in', false]
+ ])('%s: %s', (role, expected) => {
+ setStateByRole(role);
+ expect(selectShowEditMuteError(state)).toBe(expected);
+ });
+ });
+});