Skip to content

Commit

Permalink
Merge pull request #12 from andela/ft-like-dislike-article-161348762
Browse files Browse the repository at this point in the history
#161348762 Users can like or dislike an Article
  • Loading branch information
tibetegya committed Dec 12, 2018
2 parents 8bd4656 + a2acb9f commit 43e9a27
Show file tree
Hide file tree
Showing 47 changed files with 1,905 additions and 14,013 deletions.
14 changes: 7 additions & 7 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
13,847 changes: 0 additions & 13,847 deletions package-lock.json

This file was deleted.

17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"start": "serve -s dist",
"build": "webpack --config webpack.prod.js",
"lint": "eslint src",
"heroku-postbuild": "npm run build",
"test": "jest",
"heroku-postbuild": "npm run build",
"test": "jest",
"test:cov": "jest --coverage --env='jsdom'",
"coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls"
},
Expand Down Expand Up @@ -86,7 +86,8 @@
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.4"
"webpack-merge": "^4.1.4",
"@babel/plugin-proposal-class-properties": "^7.2.1"
},
"jest": {
"snapshotSerializers": [
Expand All @@ -106,5 +107,13 @@
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less|scss)$": "identity-obj-proxy"
}
}
},
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/>
<meta name="keywords" content="author, blog, write" />
<meta name="author" content="Team Thanos: Andela Cohort 11" />

<base href="/" />
<title>Authors Haven</title>
</head>
<body>
Expand Down
44 changes: 23 additions & 21 deletions src/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,29 @@
exports[`Provider and App renders <App/> correctly 1`] = `
<BrowserRouter>
<Header />
<Route
component={[Function]}
exact={true}
path="/"
/>
<Route
component={[Function]}
path="/login"
/>
<Route
component={[Function]}
path="/signup"
/>
<Route
component={[Function]}
path="/articles"
/>
<Route
component={[Function]}
path="/articles/:article_id"
/>
<Switch>
<Route
component={[Function]}
exact={true}
path="/"
/>
<Route
component={[Function]}
path="/login"
/>
<Route
component={[Function]}
path="/signup"
/>
<Route
component={[Function]}
path="/articles"
/>
<Route
component={[Function]}
path="/article/:articleId"
/>
</Switch>
<Footer />
</BrowserRouter>
`;
Expand Down
5 changes: 5 additions & 0 deletions src/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const ACTION_TYPE = {
USER_REGISTER_FAIL: 'USER_REGISTER_FAIL',
FETCH_ARTICLES_SUCCESS: 'FETCH_ARTICLES_SUCCESS',
FETCH_ARTICLES_FAILURE: 'FETCH_ARTICLES_FAILURE',
LIKEDISLIKE_ARTICLE: 'LIKEDISLIKE_ARTICLE',
GET_ARTICLE: 'GET_ARTICLE',
GET_LIKE_STATUS: 'GET_LIKE_STATUS',
USER_LOGIN: 'USER_LOGIN',
SHOW_ERROR: 'SHOW_ERROR',
};

export default ACTION_TYPE;
46 changes: 45 additions & 1 deletion src/actions/articleActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,56 @@ export const fetchArticlesFailure = errorMessage => ({
errorMessage,
});

const fetchArticlesThunk = () => dispatch => axios.get(`${APP_URL}/articles`)
export const showErrorAction = payload => ({
type: ACTION_TYPE.SHOW_ERROR,
payload, // error message
});

export const fetchArticlesThunk = () => dispatch => axios.get(`${APP_URL}/articles`)
.then((response) => {
dispatch(fetchArticlesSuccess(response.data.results));
})
.catch(() => {
dispatch(fetchArticlesFailure('Check your internet conectivity'));
});


export const getArticleAction = payload => ({
type: ACTION_TYPE.GET_ARTICLE,
payload, // entire article
});

export const getLikeStatusAction = payload => ({
type: ACTION_TYPE.GET_LIKE_STATUS,
payload,
});

export const getArticleThunk = articleId => (dispatch) => {
const url = `${APP_URL}/articles/${articleId}`;
return axios.get(url)
.then((response) => {
dispatch(getArticleAction(response.data.results));
return response;
})
.catch((error) => {
dispatch(showErrorAction({
...alert,
text: error.response ? error.response.data.results.error : 'Article not found',
}));
});
};

export const getLikeStatusThunk = ({ articleId, token }) => (dispatch, getState) => {
const url = `${APP_URL}/articles/${articleId}/like_status`;
return axios.get(url, { headers: { Authorization: `Token ${token}` } })
.then((response) => {
const obj = response.data.results.filter(
res => res.user.username === localStorage.getItem('username')
&& res.article === getState().articleReducer.article.id,
);
dispatch(getLikeStatusAction(obj));
})
.catch(() => {}); // has no like-status
};

export default fetchArticlesThunk;
29 changes: 29 additions & 0 deletions src/actions/likedislikeActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import axios from 'axios';
import actionTypes from './actionTypes';
import APP_URL from '../utils/constants';

export const likeDislikeArticleAction = payload => ({
type: actionTypes.LIKEDISLIKE_ARTICLE,
payload,
});

export const likeDislikeArticleThunk = likeObj => (dispatch) => {
const { articleId, likeDislikeStatus, token } = likeObj;
const url = `${APP_URL}/articles/${articleId}/like_status`;
const headerValue = { headers: { Authorization: `Token ${token}` } };
const payload = res => ({
results: res.data.results,
likeStatus: likeObj.like_status,
});
if (likeDislikeStatus) { // if a user has liked/disliked this article before
return axios.put(url, likeObj, headerValue)
.then((response) => {
dispatch(likeDislikeArticleAction(payload(response)));
})
.catch(() => {});
}
return axios.post(url, likeObj, headerValue)
.then((response) => {
dispatch(likeDislikeArticleAction(payload(response)));
});
};
8 changes: 5 additions & 3 deletions src/actions/loginActions/loginActions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ describe('Login Actions tests', () => {
});
const expectedtActions = { type: ACTION_TYPE.USER_LOGIN_SUCCESS };
const store = mockStore({});
store.dispatch(loginThunk()).then(() => {
expect(store.getActions()).toEqual(expect.objectContaining(expectedtActions));
});
store.dispatch(loginThunk())
.then(() => {
expect(store.getActions()).toEqual(expect.objectContaining(expectedtActions));
})
.catch(() => {});
});
});
87 changes: 74 additions & 13 deletions src/actions/tests/articleActions.test.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import * as moxios from 'moxios';
import configurestore from 'redux-mock-store';
import configureMockStore from 'redux-mock-store';
import reduxThunk from 'redux-thunk';
import fetchArticlesThunk, {
import moxios from 'moxios';
import {
getLikeStatusThunk,
getArticleThunk,
fetchArticlesSuccess,
fetchArticlesFailure,
fetchArticlesThunk,
} from '../articleActions';
import ACTION_TYPE from '../actionTypes';
import APP_URL from '../../utils/constants';

const middlewares = [reduxThunk];
const mockStore = configurestore(middlewares);
let store;
describe('get articles component', () => {
describe('Article component', () => {
let store;
let url;
let sampleId;

beforeEach(() => {
moxios.install();
store = mockStore();
sampleId = 1;
const mockStore = configureMockStore([reduxThunk]);
store = mockStore({});
url = `${APP_URL}/articles/${sampleId}`;
});

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

it('should handel fetchArticlesFailure', () => {
it('should handle fetchArticlesFailure', () => {
const errorMessage = 'Check your internet conectivity';
moxios.stubRequest(
`${APP_URL}/articles`,
Expand Down Expand Up @@ -65,9 +72,7 @@ describe('get articles component', () => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});

describe('fetchArticleSucess', () => {
it('should create an action on successful fetching of all articles', () => {
const article = {
title: 'training a dragon',
Expand All @@ -79,9 +84,7 @@ describe('fetchArticleSucess', () => {
};
expect(fetchArticlesSuccess(article)).toEqual(expectedAction);
});
});

describe('fetchArticlesFailure', () => {
it('should create an action on failure to fetch all articles', () => {
const msg = 'no internet connection';

Expand All @@ -91,4 +94,62 @@ describe('fetchArticlesFailure', () => {
};
expect(fetchArticlesFailure(msg)).toEqual(expectedAction);
});

test('get article thunk action', () => {
moxios.stubRequest(url, {
status: 200,
responseText: { title: 'Sample Title' },
});
const expectedActions = [{ type: ACTION_TYPE.GET_ARTICLE }];
store.dispatch(getArticleThunk(sampleId))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
})
.catch(() => {});
});

test('get article thunk action with error', () => {
moxios.stubRequest(url, {
status: 400,
responseText: {},
});
const expectedActions = [{ type: ACTION_TYPE.SHOW_ERROR }];
store.dispatch(getArticleThunk(sampleId))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
})
.catch(() => {});
});

test('get like-status thunk action', () => {
const likeObj = {
articleId: sampleId,
token: 'abcabc',
};
moxios.stubRequest(`${url}/like_status`, {
status: 200,
responseText: {
status_code: 200,
results: [
{
id: 9,
article_title: 'title',
like_status: 'like',
article: 1,
user: {
id: 1,
username: 'janedoe',
email: 'janedoe@gmail.com',
},
},
],
},
});
const expectedActions = [{ type: ACTION_TYPE.GET_LIKE_STATUS }];
store.dispatch(getLikeStatusThunk(likeObj))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
})
.catch(() => {});
});
});
Loading

0 comments on commit 43e9a27

Please sign in to comment.