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 15, 2019
1 parent ad456ec commit 950414d
Show file tree
Hide file tree
Showing 16 changed files with 776 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
113 changes: 113 additions & 0 deletions src/components/comment/Comment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import { CommentForm, mapStateToProps, mapDispatchToProps } from 'components/Comment/CommentForm';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';


describe('Comment component', () => {
const props = {
articleSlug: '',
createNewComment: jest.fn(),
username: 'dojo',
};

const initialState = {
commentBody: '',
};
const mockStore = configureStore([thunk]);
let wrapper;
let store;


beforeEach(() => {
store = mockStore(initialState);
moxios.install();
wrapper = shallow(<CommentForm store={store} {...props} />);
});

afterEach(() => {
moxios.uninstall();
});

it('should render without crashing', () => {
expect(wrapper).toMatchSnapshot();
});

it('should limit commenting on articles to authenticated users', () => {
wrapper.setProps({ username: '' });
expect(wrapper.text()).toBe('login to comment on this article');
});

it('should handle the comment body input change event', () => {
const event = {
target: {
name: 'commentBody',
value: 'My first comment',
},
};

wrapper.instance().handleChange(event);
expect(wrapper.instance().state.commentBody).toBe(event.target.value);
});

it('should handle the onSubmit event', () => {
const instance = wrapper.instance();
wrapper.setState({ commentBody: '' });
wrapper.setProps({ articleSlug: 'my_article_slug' });
const event = {
target: {
type: 'submit',
name: 'addComment',
},
preventDefault: jest.fn(),
};

instance.handleCommentSubmit(event);

expect(instance.props.createNewComment.mock.calls.length).toBe(0);

wrapper.setState({ commentBody: 'My first comment' });
instance.handleCommentSubmit(event);

expect(instance.props.createNewComment.mock.calls.length).toBe(1);
expect(instance.props.createNewComment).toBeCalled();
});

it('should show an error on submitting a comment with on missing comment body', () => {
wrapper.setState({ error: 'Comment body is required' });
expect(wrapper.find('.error-msg').text()).toBe('Comment body is required');
});

it('should show a success message on successfully commenting on an article', () => {
wrapper.setProps({ success: true });
expect(wrapper.find('.success-msg').text()).toBe('Comment added successfully');
});


it('should map state to props', () => {
const mockedState = {
loginReducer: {
user: {
username: 'arthur'
}
},
commentReducer: {
success: true,
}
};

const state = mapStateToProps(mockedState);

expect(state.username).toEqual('arthur');
expect(state.success).toEqual(true);
});

it('should map dispatch to props', () => {
const mockedDispatch = jest.fn();

mapDispatchToProps(mockedDispatch).createNewComment();
expect(mockedDispatch).toHaveBeenCalled();
});
});
44 changes: 44 additions & 0 deletions src/components/comment/CommentBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// react
import React from 'react';
import PropTypes from 'prop-types';
import DeleteComment from 'components/comment/DeleteComment';


const CommentBox = (
{
id, author: { username, }, created_at, body, articleSlug, deleteComment
}
) => (
<div className="comment">
<span>
{' '}
{id}
</span>
<h4>
<b>username</b>
{username}
{' '}
</h4>
<p>{created_at}</p>
<p>{body}</p>
<DeleteComment
author={username}
id={id}
articleSlug={articleSlug}
deleteComment={deleteComment}
/>
</div>
);

CommentBox.propTypes = {
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;
107 changes: 107 additions & 0 deletions src/components/comment/CommentForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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';

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);
}
};

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>
<div>
<h2>Add your Comment Here</h2>
{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 type="submit" name="addComment">Post Comment</button>
</div>
</form>
</div>

</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);
35 changes: 35 additions & 0 deletions src/components/comment/DeleteComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// react
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { isAuthenticated } from 'utils';


const DeleteComment = ({
id, author, articleSlug, deleteComment
}) => (
<Fragment>
{isAuthenticated().username === author && (
(
<button
type="button"
onClick={(event) => {
event.preventDefault();
deleteComment(articleSlug, id);
}}
>
Delete
</button>
)
)
}
</Fragment>
);

DeleteComment.propTypes = {
articleSlug: PropTypes.string.isRequired,
id: PropTypes.number.isRequired,
author: PropTypes.string.isRequired,
deleteComment: PropTypes.func.isRequired,
};

export default DeleteComment;
33 changes: 33 additions & 0 deletions src/components/comment/__snapshots__/Comment.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Comment component should render without crashing 1`] = `
<div>
<div>
<h2>
Add your Comment Here
</h2>
<form
onSubmit={[Function]}
>
<div>
<textarea
name="commentBody"
onChange={[Function]}
placeholder="Comment"
required={true}
rows="3"
value=""
/>
</div>
<div>
<button
name="addComment"
type="submit"
>
Post Comment
</button>
</div>
</form>
</div>
</div>
`;
Loading

0 comments on commit 950414d

Please sign in to comment.