-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(comment): add comment on article
- add comment reducer and actions tests and implementation - add comment form [starts #165273535]
- Loading branch information
Arthur Kalule
committed
Jun 16, 2019
1 parent
ad456ec
commit 38a924c
Showing
23 changed files
with
1,306 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
@import 'assets/MainStyle.scss'; | ||
|
||
.comment_form { | ||
> form { | ||
width: 100%; | ||
//padding: none; | ||
background: none; | ||
|
||
& textarea { | ||
outline: none; | ||
width: 100%; | ||
border: 0.06rem #2D658623 solid; | ||
background: $white; | ||
border-radius: 0.7rem; | ||
padding: 0.5rem; | ||
height: 4rem; | ||
|
||
&:hover, &:focus { | ||
border: 0.06rem #2D658656 solid; | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
.comment { | ||
display: grid; | ||
grid-template-columns: repeat(4, 1fr); | ||
grid-gap: 1.2rem; | ||
//box-shadow: 0 0 0 0.1rem rgba(34, 34, 34, 0.06); | ||
border-radius: 5%; | ||
padding: 0.2rem; | ||
margin-bottom: 0.8rem; | ||
|
||
&__img { | ||
grid-column: 1/ span 1; | ||
grid-row: 1; | ||
justify-self: end; | ||
top: 50%; | ||
width: 4.375rem; | ||
box-shadow: 0 0 0.4rem rgba(0,0,0,.6); | ||
border-radius: 50%; | ||
} | ||
|
||
&__header { | ||
grid-column: 2 /span 3; | ||
grid-row: 1; | ||
display: inline-grid; | ||
grid-template-columns: repeat(3, 1fr); | ||
align-items: center; | ||
border-bottom: 1px solid #E5E5E5; | ||
|
||
&__author { | ||
grid-column: span 1; | ||
grid-row: 1; | ||
color: $primary; | ||
transition: color .3s ease-in-out; | ||
justify-self: start; | ||
|
||
&:hover { | ||
color: #222; | ||
text-decoration: underline; | ||
} | ||
|
||
} | ||
|
||
&__timestamp { | ||
grid-column: 2/span 2; | ||
grid-row: 1; | ||
color: #AEADAD; | ||
justify-self: end; | ||
} | ||
|
||
} | ||
|
||
&__body { | ||
grid-column: 2/span 3; | ||
grid-row: 2; | ||
display: block; | ||
color: rgba(34, 34, 34, 0.64); | ||
} | ||
|
||
&__footer { | ||
grid-column: 2/span 3; | ||
grid-row: 3; | ||
padding-bottom: 0.8rem; | ||
|
||
> .delete { | ||
color: #FF492E; | ||
} | ||
|
||
&__actions { | ||
justify-self: end; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import { Comment, mapStateToProps, mapDispatchToProps } from 'components/Comment/'; | ||
import commentTestData from 'store/reducers/commentReducer/__mocks__'; | ||
|
||
|
||
describe('Comment Box', () => { | ||
let wrapper; | ||
const getCommentsFn = jest.fn(); | ||
const deleteCommentFn = jest.fn(); | ||
const props = { | ||
getComments: getCommentsFn, | ||
deleteComment: deleteCommentFn, | ||
comments: commentTestData, | ||
articleSlug: 'javascript-code', | ||
commentsCount: commentTestData.length, | ||
username: 'zack' | ||
}; | ||
|
||
it('should render without crushing', () => { | ||
wrapper = shallow(<Comment | ||
{...props} | ||
/>); | ||
|
||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
|
||
it('should map state to props', () => { | ||
const mockedState = { | ||
loginReducer: { | ||
user: { | ||
username: 'arthur' | ||
} | ||
}, | ||
commentReducer: { | ||
comments: commentTestData, | ||
commentsCount: commentTestData.length, | ||
} | ||
}; | ||
|
||
const state = mapStateToProps(mockedState); | ||
|
||
expect(state.username).toEqual('arthur'); | ||
expect(state.commentsCount).toBe(2); | ||
}); | ||
|
||
it('should map dispatch to props', () => { | ||
mapDispatchToProps(getCommentsFn).getComments(); | ||
expect(getCommentsFn).toHaveBeenCalled(); | ||
expect(getCommentsFn).toHaveBeenCalled(); | ||
|
||
mapDispatchToProps(deleteCommentFn).deleteComment(); | ||
expect(deleteCommentFn).toHaveBeenCalled(); | ||
expect(deleteCommentFn).toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// react | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import DeleteComment from 'components/Comment/DeleteComment'; | ||
|
||
|
||
const date = timestamp => (new Date(timestamp).toString().slice(0, 25)); | ||
const CommentBox = ( | ||
{ | ||
id, author: { username, image }, | ||
created_at, body, articleSlug, deleteComment, authenticatedUsername | ||
} | ||
) => ( | ||
<div className="comment"> | ||
<img className="comment__img" alt="author" src={image} /> | ||
<div className="comment__header"> | ||
<p className="comment__header__author">{username}</p> | ||
<span className="comment__header__timestamp">{date(created_at)}</span> | ||
</div> | ||
<div className="comment__body">{body}</div> | ||
<div className="comment_footer"> | ||
{' '} | ||
{} | ||
</div> | ||
<div className="comment__footer"> | ||
<DeleteComment | ||
author={username} | ||
id={id} | ||
articleSlug={articleSlug} | ||
deleteComment={deleteComment} | ||
authenticatedUsername={authenticatedUsername} | ||
/> | ||
</div> | ||
|
||
</div> | ||
); | ||
|
||
CommentBox.propTypes = { | ||
authenticatedUsername: PropTypes.string.isRequired, | ||
deleteComment: PropTypes.func.isRequired, | ||
articleSlug: PropTypes.string.isRequired, | ||
id: PropTypes.number.isRequired, | ||
body: PropTypes.string.isRequired, | ||
created_at: PropTypes.string.isRequired, | ||
author: PropTypes.shape({ | ||
username: PropTypes.string.isRequired, | ||
}).isRequired, | ||
}; | ||
|
||
export default CommentBox; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import CommentBox from 'components/Comment/CommentBox'; | ||
|
||
|
||
describe('Comment Box', () => { | ||
let wrapper; | ||
const props = { | ||
author: { username: 'arthur' }, | ||
id: 1, | ||
created_at: '2019-06-15T06:02:00.086733Z', | ||
body: 'my first comment', | ||
articleSlug: 'my_article_slug', | ||
deleteComment: jest.fn(), | ||
authenticatedUsername: 'arthur', | ||
}; | ||
|
||
it('should render without crushing', () => { | ||
wrapper = shallow(<CommentBox | ||
{...props} | ||
/>); | ||
|
||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import React, { Component } from 'react'; | ||
|
||
// third party libraries | ||
import PropTypes from 'prop-types'; | ||
import { connect } from 'react-redux'; | ||
|
||
// comment actions | ||
import createComment from 'store/actions/commentActions'; | ||
import Button from 'components/Button'; | ||
|
||
export class CommentForm extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
commentBody: '', | ||
error: '', | ||
}; | ||
} | ||
|
||
handleCommentSubmit = (e) => { | ||
e.preventDefault(); | ||
|
||
|
||
const { commentBody } = this.state; | ||
const { createNewComment, articleSlug } = this.props; | ||
|
||
if (articleSlug && commentBody) { | ||
createNewComment(articleSlug, commentBody); | ||
this.setState({ commentBody: '' }); | ||
} | ||
}; | ||
|
||
handleChange = (e) => { | ||
const { name, value } = e.target; | ||
|
||
this.setState({ [name]: value }); | ||
}; | ||
|
||
renderCommentError = error => ( | ||
error ? <div className="error-msg text-danger">{ error }</div> : '' | ||
); | ||
|
||
renderCommentSuccess = success => (success ? <div className="success-msg text-success">Comment added successfully</div> : ''); | ||
|
||
|
||
render() { | ||
const { commentBody, error } = this.state; | ||
const { success, username } = this.props; | ||
if (!username) { | ||
return (<div>login to comment on this article</div>); | ||
} | ||
|
||
return ( | ||
<div className="comment_form"> | ||
{this.renderCommentError(error)} | ||
{this.renderCommentSuccess(success)} | ||
|
||
<form onSubmit={this.handleCommentSubmit}> | ||
<div> | ||
<textarea | ||
placeholder="Comment" | ||
rows="3" | ||
required | ||
name="commentBody" | ||
value={commentBody} | ||
onChange={this.handleChange} | ||
/> | ||
</div> | ||
<div> | ||
<Button inputType="submit" btnClass="btn" btnEvent={this.handleCommentSubmit} btnName="Post Comment" /> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
// default props | ||
CommentForm.defaultProps = { | ||
username: '', | ||
success: false, | ||
}; | ||
|
||
// Props Validation | ||
CommentForm.propTypes = { | ||
createNewComment: PropTypes.func.isRequired, | ||
articleSlug: PropTypes.string.isRequired, | ||
success: PropTypes.bool, | ||
username: PropTypes.string, | ||
}; | ||
|
||
// mapState to props | ||
export const mapStateToProps = (state) => { | ||
const { commentReducer: { success }, loginReducer: { user: { username } } } = state; | ||
return { success, username }; | ||
}; | ||
|
||
// map dispatch to props | ||
export const mapDispatchToProps = dispatch => ({ | ||
createNewComment(articleSlug, commentBody) { | ||
dispatch(createComment(articleSlug, commentBody)); | ||
} | ||
}); | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(CommentForm); |
Oops, something went wrong.