From 89137723ec38b36ae1dafd56585efba38959fbcf Mon Sep 17 00:00:00 2001 From: ibrahimalausa Date: Sat, 2 Mar 2019 00:48:02 +0100 Subject: [PATCH] Implement actions for liking and disliking an article Edit toast error to display error message from server --- .../articleReactionsAction/actionTypes.js | 3 +- .../articleReactionActions.test.js | 31 ++++++++++++++----- src/actions/articleReactionsAction/index.js | 30 ++++++++++++++++-- src/components/widgets/ReactionIcons.jsx | 28 ++++++++++------- .../widgets/test/ReactionIcons.test.js | 4 ++- .../__snapshots__/ReactionIcons.test.js.snap | 4 +++ .../test/updateArticleReaction.test.js | 23 ++++++++++++++ .../axiosHelper/updateArticleReaction.js | 12 +++++++ src/mockData/articleReactions.js | 12 +++++++ 9 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 src/helpers/axiosHelper/test/updateArticleReaction.test.js create mode 100644 src/helpers/axiosHelper/updateArticleReaction.js create mode 100644 src/mockData/articleReactions.js diff --git a/src/actions/articleReactionsAction/actionTypes.js b/src/actions/articleReactionsAction/actionTypes.js index 690bda7f..77df2f27 100644 --- a/src/actions/articleReactionsAction/actionTypes.js +++ b/src/actions/articleReactionsAction/actionTypes.js @@ -1,7 +1,8 @@ const actionTypes = { TOTAL_REACTIONS_LOADING: 'TOTAL_REACTIONS_LOADING', TOTAL_REACTIONS_SUCCESS: 'TOTAL_REACTIONS_SUCCESS', - TOTAL_REACTIONS_FAILURE: 'TOTAL_REACTIONS_FAILURE' + TOTAL_REACTIONS_FAILURE: 'TOTAL_REACTIONS_FAILURE', + ARTICLE_REACTION_SUCCESS: 'ARTICLE_REACTION_SUCCESS' }; export default actionTypes; diff --git a/src/actions/articleReactionsAction/articleReactionActions.test.js b/src/actions/articleReactionsAction/articleReactionActions.test.js index dc4615eb..d5d215cb 100644 --- a/src/actions/articleReactionsAction/articleReactionActions.test.js +++ b/src/actions/articleReactionsAction/articleReactionActions.test.js @@ -4,15 +4,16 @@ import moxios from 'moxios'; import * as actions from './index'; import actionTypes from './actionTypes'; import axios from '../../helpers/axiosHelper/totalArticleReactions'; +import helper from '../../helpers/axiosHelper/updateArticleReaction'; import { articleId, totalReactions as payload } from '../../mockData/readArticle'; +import { error, reactionType, validReaction } from '../../mockData/articleReactions'; -const error = { - response: { - data: { success: false, message: 'Value must be a UUID' } - } -}; - -const { TOTAL_REACTIONS_FAILURE, TOTAL_REACTIONS_LOADING, TOTAL_REACTIONS_SUCCESS } = actionTypes; +const { + TOTAL_REACTIONS_FAILURE, + TOTAL_REACTIONS_LOADING, + TOTAL_REACTIONS_SUCCESS, + ARTICLE_REACTION_SUCCESS +} = actionTypes; const mockStore = configureStore([thunk]); const store = mockStore({ totalReactions: {} }); @@ -58,4 +59,20 @@ describe('actions to fetch number of likes and dislikes', () => { payload: payload.data }); }); + + it('should return an action type ARTICLE_REACTION_SUCCESS when the operation is successful', () => { + expect(actions.articleReactionSuccess(reactionType)).toEqual({ + type: ARTICLE_REACTION_SUCCESS, + payload: reactionType + }); + }); + it('should dispatch success action type and response as payload', async () => { + helper.updateArticleReaction = jest.fn().mockResolvedValue(validReaction); + await actions.articleReaction(articleId, reactionType)(dispatch); + expect(dispatch).toBeCalledTimes(1); + expect(dispatch).toBeCalledWith({ + type: ARTICLE_REACTION_SUCCESS, + payload: reactionType + }); + }); }); diff --git a/src/actions/articleReactionsAction/index.js b/src/actions/articleReactionsAction/index.js index 6770baa5..e0b00c48 100644 --- a/src/actions/articleReactionsAction/index.js +++ b/src/actions/articleReactionsAction/index.js @@ -1,8 +1,15 @@ +import { toast } from 'react-toastify'; import actionTypes from './actionTypes'; +import axios from '../../helpers/axiosHelper/updateArticleReaction'; import axiosHelper from '../../helpers/axiosHelper/totalArticleReactions'; import triggerLoading from '../authAction/loading'; -const { TOTAL_REACTIONS_FAILURE, TOTAL_REACTIONS_LOADING, TOTAL_REACTIONS_SUCCESS } = actionTypes; +const { + TOTAL_REACTIONS_FAILURE, + TOTAL_REACTIONS_LOADING, + TOTAL_REACTIONS_SUCCESS, + ARTICLE_REACTION_SUCCESS +} = actionTypes; const totalReactionsSuccess = payload => ({ type: TOTAL_REACTIONS_SUCCESS, @@ -24,4 +31,23 @@ const getTotalReactions = articleId => async (dispatch) => { } }; -export { getTotalReactions, totalReactionsFailure, totalReactionsSuccess }; +const articleReactionSuccess = reactionType => ({ + type: ARTICLE_REACTION_SUCCESS, + payload: reactionType +}); + +const articleReaction = (articleId, reactionType) => async (dispatch) => { + try { + await axios.updateArticleReaction(articleId, reactionType); + dispatch(articleReactionSuccess(reactionType)); + } catch (error) { + toast.error(error.response.data.message); + } +}; +export { + getTotalReactions, + totalReactionsFailure, + totalReactionsSuccess, + articleReaction, + articleReactionSuccess +}; diff --git a/src/components/widgets/ReactionIcons.jsx b/src/components/widgets/ReactionIcons.jsx index 91a7db23..517255ce 100644 --- a/src/components/widgets/ReactionIcons.jsx +++ b/src/components/widgets/ReactionIcons.jsx @@ -2,8 +2,8 @@ import React, { Component, Fragment } from 'react'; import { Icon } from 'semantic-ui-react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { getTotalReactions } from '../../actions/articleReactionsAction'; import { dateFormatter } from '../../helpers/articleInfoFormatter'; +import { getTotalReactions, articleReaction } from '../../actions/articleReactionsAction'; class ReactionIcons extends Component { state = { @@ -15,24 +15,28 @@ class ReactionIcons extends Component { this.props.getTotalReactions(this.props.selectedArticleId); }; - likeArticle = (outline = 'outline') => { + createLikeIcon = (outline = 'outline') => { const name = outline.length === 0 ? 'thumbs up' : `thumbs up ${outline}`; return ( - + ); }; - dislikeArticle = (outline = 'outline') => { + createDislikeIcon = (outline = 'outline') => { const name = outline.length === 0 ? 'thumbs down' : `thumbs down ${outline}`; return ( - + ); }; + createReaction = (reaction) => { + this.props.articleReaction(this.props.selectedArticleId, reaction); + }; + render() { const { date, numberofcomments, totalArticleReactions } = this.props; const { likes, dislikes } = totalArticleReactions.successResponse; @@ -43,10 +47,11 @@ class ReactionIcons extends Component { {dateFormatter(date)} - {likeClicked ? this.likeArticle(outline) : this.likeArticle()} - {likes} - {dislikeClicked ? this.dislikeArticle(outline) : this.dislikeArticle()} - {dislikes} + {date} + {likeClicked ? this.createLikeIcon(outline) : this.createLikeIcon()} + {likes} + {dislikeClicked ? this.createDislikeIcon(outline) : this.createDislikeIcon()} + {dislikes} {numberofcomments} @@ -65,7 +70,8 @@ ReactionIcons.propTypes = { numberofcomments: PropTypes.number, selectedArticleId: PropTypes.string.isRequired, getTotalReactions: PropTypes.func.isRequired, - totalArticleReactions: PropTypes.object.isRequired + totalArticleReactions: PropTypes.object.isRequired, + articleReaction: PropTypes.func.isRequired }; const mapStateToProps = state => ({ @@ -75,5 +81,5 @@ const mapStateToProps = state => ({ export { ReactionIcons as ReactionIconsPage }; export default connect( mapStateToProps, - { getTotalReactions } + { getTotalReactions, articleReaction } )(ReactionIcons); diff --git a/src/components/widgets/test/ReactionIcons.test.js b/src/components/widgets/test/ReactionIcons.test.js index ac685e05..3c735d39 100644 --- a/src/components/widgets/test/ReactionIcons.test.js +++ b/src/components/widgets/test/ReactionIcons.test.js @@ -20,16 +20,18 @@ describe('Test for reaction Icons', () => { }); it('should have initial state of false and change when clicked ', () => { + const articleReaction = jest.fn(); const wrapper = shallow( ); expect(wrapper).toMatchSnapshot(); - + wrapper.instance().createReaction(); expect(wrapper.state().dislikeClicked).toEqual(false); }); }); diff --git a/src/components/widgets/test/__snapshots__/ReactionIcons.test.js.snap b/src/components/widgets/test/__snapshots__/ReactionIcons.test.js.snap index 63375d56..9acf9eaa 100644 --- a/src/components/widgets/test/__snapshots__/ReactionIcons.test.js.snap +++ b/src/components/widgets/test/__snapshots__/ReactionIcons.test.js.snap @@ -15,6 +15,7 @@ exports[`Test for reaction Icons should have initial state of false and change w { + it('should return a response object', async () => { + const axiosPut = axios.put; + const urlPath = `${ + process.env.API_BASE_URL + }/articles/reaction/${articleId}/?reaction=${reactionType}`; + axios.put = jest.fn(() => Promise.resolve(result)); + const response = await verify.updateArticleReaction(urlPath); + expect(response).toEqual(result); + axios.put = axiosPut; + }); +}); diff --git a/src/helpers/axiosHelper/updateArticleReaction.js b/src/helpers/axiosHelper/updateArticleReaction.js new file mode 100644 index 00000000..ba1e6788 --- /dev/null +++ b/src/helpers/axiosHelper/updateArticleReaction.js @@ -0,0 +1,12 @@ +import axios from 'axios'; +import { config } from '../jwt'; + +const updateArticleReaction = (articleId, reactionType) => { + const url = `${ + process.env.API_BASE_URL + }/articles/reaction/${articleId}/?reaction=${reactionType}`; + const response = axios.put(url, { articleId, reactionType }, config); + return response; +}; + +export default { updateArticleReaction }; diff --git a/src/mockData/articleReactions.js b/src/mockData/articleReactions.js new file mode 100644 index 00000000..c8072fea --- /dev/null +++ b/src/mockData/articleReactions.js @@ -0,0 +1,12 @@ +const error = { + response: { + data: { success: false, message: 'Value must be a UUID' } + } +}; +const reactionType = 'dislike'; + +const validReaction = { + success: true, + message: 'Operation Successful' +}; +export { error, reactionType, validReaction };