From 950414d87446185ada2413561dcdfad5b6783f6b Mon Sep 17 00:00:00 2001 From: Arthur Kalule Date: Fri, 14 Jun 2019 09:21:57 +0300 Subject: [PATCH] feat(comment): add comment on article - add comment reducer and actions tests and implementation - add comment form [starts #165273535] --- src/components/Apps/App.js | 2 + src/components/Apps/App.test.js | 1 - src/components/comment/Comment.test.js | 113 ++++++++++++++++++ src/components/comment/CommentBox.js | 44 +++++++ src/components/comment/CommentForm.js | 107 +++++++++++++++++ src/components/comment/DeleteComment.js | 35 ++++++ .../__snapshots__/Comment.test.js.snap | 33 +++++ src/components/comment/index.js | 91 ++++++++++++++ src/store/actions/__mocks__/index.js | 26 ++++ .../commentActions/commentActions.test.js | 47 ++++++++ src/store/actions/commentActions/index.js | 112 +++++++++++++++++ src/store/actions/commentTypes.js | 5 + .../reducers/commentReducer/__mocks__.js | 38 ++++++ .../commentReducer/commentReducer.test.js | 76 ++++++++++++ src/store/reducers/commentReducer/index.js | 44 +++++++ src/store/rootReducer.js | 4 +- 16 files changed, 776 insertions(+), 2 deletions(-) create mode 100644 src/components/comment/Comment.test.js create mode 100644 src/components/comment/CommentBox.js create mode 100644 src/components/comment/CommentForm.js create mode 100644 src/components/comment/DeleteComment.js create mode 100644 src/components/comment/__snapshots__/Comment.test.js.snap create mode 100644 src/components/comment/index.js create mode 100644 src/store/actions/commentActions/commentActions.test.js create mode 100644 src/store/actions/commentActions/index.js create mode 100644 src/store/actions/commentTypes.js create mode 100644 src/store/reducers/commentReducer/__mocks__.js create mode 100644 src/store/reducers/commentReducer/commentReducer.test.js create mode 100644 src/store/reducers/commentReducer/index.js diff --git a/src/components/Apps/App.js b/src/components/Apps/App.js index c24ea38..ceda567 100644 --- a/src/components/Apps/App.js +++ b/src/components/Apps/App.js @@ -5,6 +5,7 @@ import PasswordResetPage from 'pages/PasswordReset'; import PageNotFound from 'pages/Error'; import 'assets/MainStyle.scss'; +import Comment from 'components/comment'; class App extends Component { render() { @@ -12,6 +13,7 @@ class App extends Component { } /> + } /> } /> diff --git a/src/components/Apps/App.test.js b/src/components/Apps/App.test.js index c823227..41ad499 100644 --- a/src/components/Apps/App.test.js +++ b/src/components/Apps/App.test.js @@ -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'; diff --git a/src/components/comment/Comment.test.js b/src/components/comment/Comment.test.js new file mode 100644 index 0000000..cd06434 --- /dev/null +++ b/src/components/comment/Comment.test.js @@ -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(); + }); + + 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(); + }); +}); diff --git a/src/components/comment/CommentBox.js b/src/components/comment/CommentBox.js new file mode 100644 index 0000000..279abc4 --- /dev/null +++ b/src/components/comment/CommentBox.js @@ -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 + } +) => ( +
+ + {' '} + {id} + +

+ username + {username} + {' '} +

+

{created_at}

+

{body}

+ +
+); + +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; diff --git a/src/components/comment/CommentForm.js b/src/components/comment/CommentForm.js new file mode 100644 index 0000000..a724c20 --- /dev/null +++ b/src/components/comment/CommentForm.js @@ -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 ?
{ error }
: '' + ); + + renderCommentSuccess = success => (success ?
Comment added successfully
: ''); + + + render() { + const { commentBody, error } = this.state; + const { success, username } = this.props; + if (!username) { + return (
login to comment on this article
); + } + + return ( +
+
+

Add your Comment Here

+ {this.renderCommentError(error)} + {this.renderCommentSuccess(success)} + +
+
+