Skip to content

Commit

Permalink
feat(comment): add comment on article
Browse files Browse the repository at this point in the history
 - 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 825ca99
Show file tree
Hide file tree
Showing 24 changed files with 1,332 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/components/Apps/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import PasswordResetPage from 'pages/PasswordReset';
import PageNotFound from 'pages/Error';

import 'assets/MainStyle.scss';
import Comment from 'components/Comment';

class App extends Component {
render() {
return (
<Router>
<Switch>
<Route exact path="/" render={props => <LandingPage {...props} />} />
<Route path="/comments" render={props => <Comment {...props} articleSlug="javascript-code" />} />
<Route path="/reset-password" render={props => <PasswordResetPage {...props} />} />
<Route component={PageNotFound} />
</Switch>
Expand Down
1 change: 0 additions & 1 deletion src/components/Apps/App.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Provider } from 'react-redux';
import { shallow, mount } from 'enzyme';
import store from 'store/store';
import { LandingPage } from 'pages/Landing';
import PageNotFound from 'pages/Error';
import { MemoryRouter } from 'react-router';
import App from './App';

Expand Down
96 changes: 96 additions & 0 deletions src/components/Comment/Comment.scss
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;
}
}
}
56 changes: 56 additions & 0 deletions src/components/Comment/Comment.test.js
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();
});
});
50 changes: 50 additions & 0 deletions src/components/Comment/CommentBox.js
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;
25 changes: 25 additions & 0 deletions src/components/Comment/CommentBox.test.js
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();
});
});
105 changes: 105 additions & 0 deletions src/components/Comment/CommentForm.js
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);
Loading

0 comments on commit 825ca99

Please sign in to comment.