diff --git a/client/coral-admin/src/components/CommentAnimatedEdit.js b/client/coral-admin/src/components/CommentAnimatedEdit.js index 43fbf9fbf..12280b204 100644 --- a/client/coral-admin/src/components/CommentAnimatedEdit.js +++ b/client/coral-admin/src/components/CommentAnimatedEdit.js @@ -4,7 +4,7 @@ import { CSSTransitionGroup } from 'react-transition-group'; import styles from './CommentAnimatedEdit.css'; import PropTypes from 'prop-types'; -const CommentBodyHighlighter = ({ children, body }) => { +const CommentAnimatedEdit = ({ children, body }) => { return ( { ); }; -CommentBodyHighlighter.propTypes = { +CommentAnimatedEdit.propTypes = { children: PropTypes.node, body: PropTypes.string, }; -export default CommentBodyHighlighter; +export default CommentAnimatedEdit; diff --git a/client/coral-admin/src/components/CommentBodyHighlighter.js b/client/coral-admin/src/components/CommentFormatter.js similarity index 63% rename from client/coral-admin/src/components/CommentBodyHighlighter.js rename to client/coral-admin/src/components/CommentFormatter.js index 9e381bf0b..8874d123f 100644 --- a/client/coral-admin/src/components/CommentBodyHighlighter.js +++ b/client/coral-admin/src/components/CommentFormatter.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { matchLinks } from '../utils'; import memoize from 'lodash/memoize'; @@ -62,16 +63,47 @@ function markLinks(body) { return content; } -export default ({ suspectWords, bannedWords, body, ...rest }) => { - // First highlight links. - const content = markLinks(body).map((element, index) => { - // Keep highlighted links. - if (typeof element !== 'string') { - return element; - } +const CommentFormatter = ({ + body, + suspectWords, + bannedWords, + className = 'comment', + ...rest +}) => { + // Breaking the body by line break + const textbreaks = body.split('\n'); - // Highlight suspect and banned phrase inside this part of text. - return markPhrases(element, suspectWords, bannedWords, index); - }); - return
{content}
; + return ( + + {textbreaks.map((line, i) => { + const content = markLinks(line).map((element, index) => { + // Keep highlighted links. + if (typeof element !== 'string') { + return element; + } + + // Highlight suspect and banned phrase inside this part of text. + return markPhrases(element, suspectWords, bannedWords, index); + }); + + return ( + + {content} + {i !== textbreaks.length - 1 && ( +
+ )} +
+ ); + })} +
+ ); }; + +CommentFormatter.propTypes = { + className: PropTypes.string, + bannedWords: PropTypes.array, + suspectWords: PropTypes.array, + body: PropTypes.string, +}; + +export default CommentFormatter; diff --git a/client/coral-admin/src/components/UserDetailComment.js b/client/coral-admin/src/components/UserDetailComment.js index b1cc486ec..2d98edcb3 100644 --- a/client/coral-admin/src/components/UserDetailComment.js +++ b/client/coral-admin/src/components/UserDetailComment.js @@ -5,7 +5,7 @@ import { Link } from 'react-router'; import { Icon } from 'coral-ui'; import CommentDetails from './CommentDetails'; import styles from './UserDetailComment.css'; -import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter'; +import CommentFormatter from 'coral-admin/src/components/CommentFormatter'; import IfHasLink from 'coral-admin/src/components/IfHasLink'; import cn from 'classnames'; import CommentAnimatedEdit from './CommentAnimatedEdit'; @@ -78,11 +78,12 @@ class UserDetailComment extends React.Component {
- {' '} + className="talk-admin-user-detail-comment" + />
- {' '} + /> { } expect(data.createComment).to.have.property('errors').null; expect(data.createComment).to.have.property('comment').not.null; + expect(data.createComment.comment).to.have.property('id').not + .null; } } ); diff --git a/test/server/graph/queries/asset.js b/test/server/graph/queries/asset.js index af19e4f69..f976bd37d 100644 --- a/test/server/graph/queries/asset.js +++ b/test/server/graph/queries/asset.js @@ -34,12 +34,14 @@ describe('graph.queries.asset', () => { username: 'usernameC', }, ]); - comments = await CommentsService.publicCreate( - [0, 0, 1, 1].map(idx => ({ - author_id: users[idx].id, - asset_id: assets[idx].id, - body: `hello there! ${String(Math.random()).slice(2)}`, - })) + comments = await Promise.all( + [0, 0, 1, 1].map(idx => + CommentsService.publicCreate({ + author_id: users[idx].id, + asset_id: assets[idx].id, + body: `hello there! ${String(Math.random()).slice(2)}`, + }) + ) ); }); diff --git a/test/server/services/assets.js b/test/server/services/assets.js index c071b5634..923f28751 100644 --- a/test/server/services/assets.js +++ b/test/server/services/assets.js @@ -176,28 +176,30 @@ describe('services.AssetsService', () => { ); // Create some comments on both assets. - await CommentsService.publicCreate([ - { - asset_id: '1', - body: 'This is a comment!', - status: 'ACCEPTED', - }, - { - asset_id: '1', - body: 'This is a comment!', - status: 'ACCEPTED', - }, - { - asset_id: '2', - body: 'This is a comment!', - status: 'ACCEPTED', - }, - { - asset_id: '2', - body: 'This is a comment!', - status: 'ACCEPTED', - }, - ]); + await Promise.all( + [ + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED', + }, + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED', + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED', + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED', + }, + ].map(comment => CommentsService.publicCreate(comment)) + ); // Merge all the comments from asset 1 into asset 2, followed by deleting // asset 1. diff --git a/test/server/services/comments.js b/test/server/services/comments.js index c9ec1315d..2d0b37163 100644 --- a/test/server/services/comments.js +++ b/test/server/services/comments.js @@ -131,6 +131,64 @@ describe('services.CommentsService', () => { }); describe('#publicCreate()', () => { + describe('does not allow replies to comments that are not visible', () => { + it('parent not found', async () => { + try { + await CommentsService.publicCreate({ + body: 'This is a comment!', + status: 'ACCEPTED', + parent_id: 'does not exist', + }); + throw new Error('comment should not have been created'); + } catch (err) { + expect(err).to.have.property( + 'translation_key', + 'COMMENT_PARENT_NOT_VISIBLE' + ); + } + }); + + it('parent REJECTED', async () => { + try { + const parent = await CommentsService.publicCreate({ + body: 'This is a comment!', + status: 'REJECTED', + }); + await CommentsService.publicCreate({ + body: 'This is a comment!', + status: 'ACCEPTED', + parent_id: parent.id, + }); + throw new Error('comment should not have been created'); + } catch (err) { + expect(err).to.have.property( + 'translation_key', + 'COMMENT_PARENT_NOT_VISIBLE' + ); + } + }); + + it('parent SYSTEM_WITHHELD', async () => { + try { + const parent = await CommentsService.publicCreate({ + body: 'This is a comment!', + status: 'SYSTEM_WITHHELD', + }); + await CommentsService.publicCreate({ + body: 'This is a comment!', + status: 'ACCEPTED', + parent_id: parent.id, + }); + throw new Error('comment should not have been created'); + } catch (err) { + expect(err).to.have.property( + 'translation_key', + 'COMMENT_PARENT_NOT_VISIBLE' + ); + } + }); + }); + it('creates a new comment', async () => { const c = await CommentsService.publicCreate({ body: 'This is a comment!', @@ -142,31 +200,6 @@ describe('services.CommentsService', () => { expect(c.id).to.be.uuid; expect(c.status).to.be.equal('ACCEPTED'); }); - - it('creates many new comments', async () => { - const [c1, c2, c3] = await CommentsService.publicCreate([ - { - body: 'This is a comment!', - status: 'ACCEPTED', - }, - { - body: 'This is another comment!', - }, - { - body: 'This is a rejected comment!', - status: 'REJECTED', - }, - ]); - - expect(c1).to.not.be.null; - expect(c1.status).to.be.equal('ACCEPTED'); - - expect(c2).to.not.be.null; - expect(c2.status).to.be.equal('NONE'); - - expect(c3).to.not.be.null; - expect(c3.status).to.be.equal('REJECTED'); - }); }); describe('#edit', () => {