Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
scratch-www
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
xpstem
scratch-www
Commits
2005b237
Unverified
Commit
2005b237
authored
Nov 10, 2020
by
picklesrus
Committed by
GitHub
Nov 10, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4616 from picklesrus/mute-behavior
Skeleton of code to show mute modal and comment status.
parents
b8b0f5d1
e5f97d1f
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
288 additions
and
68 deletions
+288
-68
src/views/preview/comment/compose-comment.jsx
src/views/preview/comment/compose-comment.jsx
+155
-68
test/unit/components/compose-comment.test.jsx
test/unit/components/compose-comment.test.jsx
+133
-0
No files found.
src/views/preview/comment/compose-comment.jsx
View file @
2005b237
...
@@ -10,6 +10,8 @@ const FlexRow = require('../../../components/flex-row/flex-row.jsx');
...
@@ -10,6 +10,8 @@ const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const
Avatar
=
require
(
'
../../../components/avatar/avatar.jsx
'
);
const
Avatar
=
require
(
'
../../../components/avatar/avatar.jsx
'
);
const
InplaceInput
=
require
(
'
../../../components/forms/inplace-input.jsx
'
);
const
InplaceInput
=
require
(
'
../../../components/forms/inplace-input.jsx
'
);
const
Button
=
require
(
'
../../../components/forms/button.jsx
'
);
const
Button
=
require
(
'
../../../components/forms/button.jsx
'
);
const
CommentingStatus
=
require
(
'
../../../components/commenting-status/commenting-status.jsx
'
);
const
MuteModal
=
require
(
'
../../../components/modal/mute/modal.jsx
'
);
const
connect
=
require
(
'
react-redux
'
).
connect
;
const
connect
=
require
(
'
react-redux
'
).
connect
;
...
@@ -24,7 +26,8 @@ const MAX_COMMENT_LENGTH = 500;
...
@@ -24,7 +26,8 @@ const MAX_COMMENT_LENGTH = 500;
const
ComposeStatus
=
keyMirror
({
const
ComposeStatus
=
keyMirror
({
EDITING
:
null
,
EDITING
:
null
,
SUBMITTING
:
null
,
SUBMITTING
:
null
,
REJECTED
:
null
REJECTED
:
null
,
REJECTED_MUTE
:
null
});
});
class
ComposeComment
extends
React
.
Component
{
class
ComposeComment
extends
React
.
Component
{
...
@@ -33,13 +36,17 @@ class ComposeComment extends React.Component {
...
@@ -33,13 +36,17 @@ class ComposeComment extends React.Component {
bindAll
(
this
,
[
bindAll
(
this
,
[
'
handlePost
'
,
'
handlePost
'
,
'
handleCancel
'
,
'
handleCancel
'
,
'
handleInput
'
'
handleInput
'
,
'
handleMuteClose
'
,
'
handleMuteOpen
'
]);
]);
this
.
state
=
{
this
.
state
=
{
message
:
''
,
message
:
''
,
status
:
ComposeStatus
.
EDITING
,
status
:
ComposeStatus
.
EDITING
,
error
:
null
,
error
:
null
,
appealId
:
null
appealId
:
null
,
muteOpen
:
false
};
};
}
}
handleInput
(
event
)
{
handleInput
(
event
)
{
...
@@ -67,13 +74,24 @@ class ComposeComment extends React.Component {
...
@@ -67,13 +74,24 @@ class ComposeComment extends React.Component {
if
(
err
||
res
.
statusCode
!==
200
)
{
if
(
err
||
res
.
statusCode
!==
200
)
{
body
=
{
rejected
:
'
error
'
};
body
=
{
rejected
:
'
error
'
};
}
}
if
(
body
.
rejected
&&
this
.
state
.
status
===
ComposeStatus
.
SUBMITTING
)
{
if
(
body
.
rejected
&&
this
.
state
.
status
===
ComposeStatus
.
SUBMITTING
)
{
let
muteOpen
=
false
;
let
muteExpiresAt
=
0
;
let
rejectedStatus
=
ComposeStatus
.
REJECTED
;
if
(
body
.
status
&&
body
.
status
.
mute_status
)
{
muteExpiresAt
=
body
.
status
.
mute_status
.
muteExpiresAt
;
rejectedStatus
=
ComposeStatus
.
REJECTED_MUTE
;
if
(
this
.
shouldShowMuteModal
(
body
.
status
.
mute_status
.
offenses
))
{
muteOpen
=
true
;
}
}
// Note: does not reset the message state
// Note: does not reset the message state
this
.
setState
({
this
.
setState
({
status
:
ComposeStatus
.
REJECTED
,
status
:
rejectedStatus
,
error
:
body
.
rejected
,
error
:
body
.
rejected
,
appealId
:
body
.
appealId
appealId
:
body
.
appealId
,
muteOpen
:
muteOpen
,
muteExpiresAt
:
muteExpiresAt
});
});
return
;
return
;
}
}
...
@@ -92,6 +110,44 @@ class ComposeComment extends React.Component {
...
@@ -92,6 +110,44 @@ class ComposeComment extends React.Component {
this
.
props
.
onAddComment
(
body
);
this
.
props
.
onAddComment
(
body
);
});
});
}
}
handleMuteClose
()
{
this
.
setState
({
muteOpen
:
false
});
}
handleMuteOpen
()
{
this
.
setState
({
muteOpen
:
true
});
}
shouldShowMuteModal
(
offensesList
)
{
// We should show the mute modal whne the user is newly muted or hasn't seen it for a while.
// We don't want to show it more than about once a week.
// A newly muted user has only 1 offense and it happened in the last coulpe of minutes.
// If a user has more than 1 offense, it means that they have have been muted in the
// last week.
// Assumption: The offenses list is ordered by time with the most recent at the end.
// This check is here just in case we somehow get bad data back from a backend.
if
(
!
offensesList
)
{
return
false
;
}
const
numOffenses
=
offensesList
.
length
;
// This isn't intended to be called if there are no offenses, but
// say no just in case.
if
(
numOffenses
===
0
)
{
return
false
;
}
const
mostRecent
=
offensesList
[
numOffenses
-
1
];
const
creationTimeMinutesAgo
=
(
Date
.
now
()
-
(
mostRecent
.
createdAt
*
1000
))
/
(
60
*
1000
);
return
creationTimeMinutesAgo
<
2
&&
numOffenses
===
1
;
}
handleCancel
()
{
handleCancel
()
{
this
.
setState
({
this
.
setState
({
message
:
''
,
message
:
''
,
...
@@ -103,6 +159,25 @@ class ComposeComment extends React.Component {
...
@@ -103,6 +159,25 @@ class ComposeComment extends React.Component {
}
}
render
()
{
render
()
{
return
(
return
(
<
React
.
Fragment
>
{
this
.
state
.
status
===
ComposeStatus
.
REJECTED_MUTE
?
(
<
FlexRow
className=
"comment"
>
<
CommentingStatus
>
<
p
>
Scratch thinks your comment was disrespectful.
</
p
>
<
p
>
For the next
{
this
.
state
.
muteExpiresAt
}
you
won
'
t be able to post comments.
Once
{
this
.
state
.
muteExpiresAt
}
have passed,
you will be able to comment again.
</
p
>
<
p
className=
"bottom-text"
>
For more information,
<
a
href=
"#comment"
onClick=
{
this
.
handleMuteOpen
}
>
click here
</
a
>
.
</
p
>
</
CommentingStatus
>
</
FlexRow
>
)
:
null
}
<
div
<
div
className=
"flex-row comment"
className=
"flex-row comment"
>
>
...
@@ -110,7 +185,7 @@ class ComposeComment extends React.Component {
...
@@ -110,7 +185,7 @@ class ComposeComment extends React.Component {
<
Avatar
src=
{
this
.
props
.
user
.
thumbnailUrl
}
/>
<
Avatar
src=
{
this
.
props
.
user
.
thumbnailUrl
}
/>
</
a
>
</
a
>
<
FlexRow
className=
"compose-comment column"
>
<
FlexRow
className=
"compose-comment column"
>
{
this
.
state
.
error
?
(
{
this
.
state
.
error
&&
this
.
state
.
status
!==
ComposeStatus
.
REJECTED_MUTE
?
(
<
FlexRow
className=
"compose-error-row"
>
<
FlexRow
className=
"compose-error-row"
>
<
div
className=
"compose-error-tip"
>
<
div
className=
"compose-error-tip"
>
<
FormattedMessage
<
FormattedMessage
...
@@ -166,7 +241,19 @@ class ComposeComment extends React.Component {
...
@@ -166,7 +241,19 @@ class ComposeComment extends React.Component {
</
FlexRow
>
</
FlexRow
>
</
Formsy
>
</
Formsy
>
</
FlexRow
>
</
FlexRow
>
{
this
.
state
.
muteOpen
?
(
<
MuteModal
isOpen
showCloseButton
useStandardSizes
className=
"mod-mute"
shouldCloseOnOverlayClick=
{
false
}
timeMuted=
{
this
.
state
.
muteExpiresAt
}
onRequestClose=
{
this
.
handleMuteClose
}
/>
)
:
null
}
</
div
>
</
div
>
</
React
.
Fragment
>
);
);
}
}
}
}
...
...
test/unit/components/compose-comment.test.jsx
0 → 100644
View file @
2005b237
const
React
=
require
(
'
react
'
);
const
{
shallowWithIntl
}
=
require
(
'
../../helpers/intl-helpers.jsx
'
);
const
ComposeComment
=
require
(
'
../../../src/views/preview/comment/compose-comment.jsx
'
);
import
configureStore
from
'
redux-mock-store
'
;
describe
(
'
Compose Comment test
'
,
()
=>
{
const
mockStore
=
configureStore
();
const
defaultProps
=
()
=>
({
user
:
{
thumbnailUrl
:
'
scratch.mit.edu
'
,
username
:
'
auser
'
}
});
let
store
;
beforeEach
(()
=>
{
store
=
mockStore
({
session
:
{
session
:
{
user
:
{}
}
}
});
});
const
getComposeCommentWrapper
=
props
=>
{
const
wrapper
=
shallowWithIntl
(
<
ComposeComment
{
...
defaultProps
()}
{
...
props
}
/>
,
{
context
:
{
store
}}
);
return
wrapper
.
dive
();
// unwrap redux connect(injectIntl(JoinFlow))
};
test
(
'
Modal & Comment status do not show
'
,
()
=>
{
const
component
=
getComposeCommentWrapper
({});
// Comment compsoe box is there
expect
(
component
.
find
(
'
FlexRow.compose-comment
'
).
exists
()).
toEqual
(
true
);
// No error message
expect
(
component
.
find
(
'
FlexRow.compose-error-row
'
).
exists
()).
toEqual
(
false
);
expect
(
component
.
find
(
'
MuteModal
'
).
exists
()).
toEqual
(
false
);
expect
(
component
.
find
(
'
CommentingStatus
'
).
exists
()).
toEqual
(
false
);
});
test
(
'
Error messages shows when comment rejected
'
,
()
=>
{
const
component
=
getComposeCommentWrapper
({});
const
commentInstance
=
component
.
instance
();
commentInstance
.
setState
({
error
:
'
isFlood
'
});
component
.
update
();
expect
(
component
.
find
(
'
FlexRow.compose-error-row
'
).
exists
()).
toEqual
(
true
);
});
test
(
'
No error message shows when comment rejected because user muted
'
,
()
=>
{
const
component
=
getComposeCommentWrapper
({});
const
commentInstance
=
component
.
instance
();
commentInstance
.
setState
({
error
:
'
isMuted
'
,
status
:
'
REJECTED_MUTE
'
});
component
.
update
();
expect
(
component
.
find
(
'
FlexRow.compose-error-row
'
).
exists
()).
toEqual
(
false
);
});
test
(
'
Comment Status shows when state is REJECTED_MUTE
'
,
()
=>
{
const
component
=
getComposeCommentWrapper
({});
const
commentInstance
=
component
.
instance
();
commentInstance
.
setState
({
status
:
'
REJECTED_MUTE
'
});
component
.
update
();
expect
(
component
.
find
(
'
FlexRow.compose-comment
'
).
exists
()).
toEqual
(
true
);
expect
(
component
.
find
(
'
MuteModal
'
).
exists
()).
toEqual
(
false
);
expect
(
component
.
find
(
'
CommentingStatus
'
).
exists
()).
toEqual
(
true
);
});
test
(
'
Mute Modal shows when muteOpen is true
'
,
()
=>
{
const
component
=
getComposeCommentWrapper
({});
const
commentInstance
=
component
.
instance
();
commentInstance
.
setState
({
muteOpen
:
true
});
component
.
update
();
expect
(
component
.
find
(
'
FlexRow.compose-comment
'
).
exists
()).
toEqual
(
true
);
expect
(
component
.
find
(
'
MuteModal
'
).
exists
()).
toEqual
(
true
);
});
test
(
'
shouldShowMuteModal is false when list is undefined
'
,
()
=>
{
const
commentInstance
=
getComposeCommentWrapper
({}).
instance
();
expect
(
commentInstance
.
shouldShowMuteModal
()).
toBe
(
false
);
});
test
(
'
shouldShowMuteModal is false when list empty
'
,
()
=>
{
const
offenses
=
[];
const
commentInstance
=
getComposeCommentWrapper
({}).
instance
();
expect
(
commentInstance
.
shouldShowMuteModal
(
offenses
)).
toBe
(
false
);
});
test
(
'
shouldShowMuteModal is true when only 1 recent offesnse
'
,
()
=>
{
const
offenses
=
[];
const
realDateNow
=
Date
.
now
.
bind
(
global
.
Date
);
global
.
Date
.
now
=
()
=>
0
;
// Since Date.now mocked to 0 above, we just need a small number to make
// it look like it was created < 2 minutes ago.
const
offense
=
{
expiresAt
:
'
1000
'
,
createdAt
:
'
-60
'
// ~1 ago min given shouldShowMuteModal's conversions,
};
offenses
.
push
(
offense
);
const
commentInstance
=
getComposeCommentWrapper
({}).
instance
();
expect
(
commentInstance
.
shouldShowMuteModal
(
offenses
)).
toBe
(
true
);
global
.
Date
.
now
=
realDateNow
;
});
test
(
'
shouldShowMuteModal is false when multiple offenses, even if 1 is recent
'
,
()
=>
{
const
offenses
=
[];
const
realDateNow
=
Date
.
now
.
bind
(
global
.
Date
);
global
.
Date
.
now
=
()
=>
0
;
// Since Date.now mocked to 0 above, we just need a small number to make
// it look like it was created more than 2 minutes ago.
let
offense
=
{
expiresAt
:
'
1000
'
,
createdAt
:
'
-119
'
// just shy of two min ago
};
offenses
.
push
(
offense
);
offense
.
createdAt
=
'
-180
'
;
// 3 minutes ago;
offenses
.
push
(
offense
);
const
commentInstance
=
getComposeCommentWrapper
({}).
instance
();
expect
(
commentInstance
.
shouldShowMuteModal
(
offenses
)).
toBe
(
false
);
global
.
Date
.
now
=
realDateNow
;
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment