-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(follow user): Users should be able to follow each other
- Follow a specific user - Unfollow a specific user - View followers (users that follow you) - View users you follow [Starts #161348765]
- Loading branch information
Bruce Allan Makaaru
authored and
Bruce Allan Makaaru
committed
Dec 20, 2018
1 parent
d35cfd8
commit e27a80b
Showing
24 changed files
with
1,416 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import axios from 'axios'; | ||
import actionTypes from './actionTypes'; | ||
import APP_URL from '../utils/constants'; | ||
|
||
const showProfileAction = payload => ({ | ||
type: actionTypes.SHOW_USER_PROFILE, | ||
payload, | ||
}); | ||
|
||
const followAction = payload => ({ | ||
type: actionTypes.FOLLOW_USER, | ||
payload, | ||
}); | ||
|
||
const unfollowAction = payload => ({ | ||
type: actionTypes.UNFOLLOW_USER, | ||
payload, | ||
}); | ||
|
||
const getFollowersAction = payload => ({ | ||
type: actionTypes.GET_FOLLOWERS, | ||
payload, | ||
}); | ||
|
||
const getFolloweesAction = payload => ({ // followees = users you follow | ||
type: actionTypes.GET_FOLLOWEES, | ||
payload, | ||
}); | ||
|
||
const showErrorAction = payload => ({ | ||
type: actionTypes.SHOW_ERROR, | ||
payload, | ||
}); | ||
|
||
export const getProfileThunk = ({ username, token }) => (dispatch) => { | ||
const url = `${APP_URL}/profiles/${username}`; | ||
return axios.get(url, { headers: { Authorization: `Token ${token}` } }) | ||
.then((response) => { | ||
dispatch(showProfileAction(response.data.results)); | ||
}) | ||
.catch(() => { | ||
dispatch(showErrorAction('Could not find profile')); | ||
}); | ||
}; | ||
|
||
export const followThunk = ({ user, token }) => (dispatch) => { | ||
const url = `${APP_URL}/users/${user}/follow`; | ||
return axios.post(url, user, { headers: { Authorization: `Token ${token}` } }) | ||
.then((response) => { | ||
dispatch(followAction(response.data.results)); | ||
}) | ||
.catch((error) => { | ||
dispatch(showErrorAction(error.response.data.results.error)); | ||
}); | ||
}; | ||
|
||
export const unfollowThunk = ({ user, token }) => (dispatch) => { | ||
const url = `${APP_URL}/users/${user}/follow`; | ||
return axios.delete(url, { headers: { Authorization: `Token ${token}` } }) | ||
.then((response) => { | ||
dispatch(unfollowAction(response.data.results)); | ||
}) | ||
.catch((error) => { | ||
dispatch(showErrorAction(error.response.data.results.error)); | ||
}); | ||
}; | ||
|
||
const headers = { headers: { Authorization: `Token ${localStorage.getItem('token')}` } }; | ||
export const getFollowProfilesThunk = (url, getFollowers) => (dispatch) => { | ||
if (getFollowers) { | ||
return axios.get(url, headers).then((response) => { | ||
dispatch(getFollowersAction(response.data.results)); | ||
}).catch(() => {}); | ||
} | ||
return axios.get(url, headers).then((response) => { | ||
dispatch(getFolloweesAction(response.data.results)); | ||
}).catch(() => {}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import configureMockStore from 'redux-mock-store'; | ||
import thunk from 'redux-thunk'; | ||
import moxios from 'moxios'; | ||
import actionTypes from '../actionTypes'; | ||
import { | ||
getProfileThunk, | ||
followThunk, unfollowThunk, | ||
getFollowProfilesThunk, | ||
} from '../followActions'; | ||
import APP_URL from '../../utils/constants'; | ||
|
||
const mockStore = configureMockStore([thunk]); | ||
|
||
describe('Folow, Unfollow Actions', () => { | ||
let store; | ||
let profileUrl; | ||
let usersUrl; | ||
let janedoe; | ||
let profilesList; | ||
let error; | ||
|
||
beforeEach(() => { | ||
moxios.install(); | ||
store = mockStore({}); | ||
profileUrl = `${APP_URL}/profiles/janedoe`; | ||
usersUrl = `${APP_URL}/users/`; | ||
janedoe = { | ||
username: 'janedoe', | ||
bio: 'This is who I am', | ||
first_name: 'Jane', | ||
last_name: 'Doe', | ||
image: 'https://picsum.photos/600', | ||
created_at: '10-11-2011', | ||
updated_at: '11-11-2018', | ||
isFollowee: false, | ||
}; | ||
profilesList = [ | ||
{ | ||
username: 'jack', | ||
bio: '', | ||
image: '', | ||
first_name: 'Jack', | ||
last_name: 'Katto', | ||
created_at: '10-11-2018', | ||
updated_at: '12-11-2018', | ||
}, | ||
{ | ||
username: 'ivy', | ||
bio: '', | ||
image: '', | ||
first_name: 'Ivy', | ||
last_name: 'Jones', | ||
created_at: '10-11-2018', | ||
updated_at: '15-11-2018', | ||
}, | ||
]; | ||
error = { | ||
response: { data: { results: { error: 'Error message' } } }, | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
moxios.uninstall(); | ||
}); | ||
|
||
test('Get profile of user to follow or unfollow', () => { | ||
moxios.stubRequest(profileUrl, { | ||
status: 200, | ||
responseText: janedoe, | ||
}); | ||
const expectedActions = [{ type: actionTypes.SHOW_USER_PROFILE }]; | ||
store.dispatch(getProfileThunk({ username: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Profile of user one is trying to follow is not found', () => { | ||
moxios.stubRequest(profileUrl, { | ||
status: 404, | ||
responseText: 'Could not find profile', | ||
}); | ||
const expectedActions = [{ type: actionTypes.SHOW_ERROR }]; | ||
store.dispatch(getProfileThunk({ username: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Follow a user', () => { | ||
moxios.stubRequest(`${usersUrl}janedoe/follow`, { | ||
status: 200, | ||
responseText: janedoe, | ||
}); | ||
const expectedActions = [{ type: actionTypes.FOLLOW_USER }]; | ||
store.dispatch(followThunk({ user: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Tying to Follow a user that does not exist', () => { | ||
moxios.stubRequest(`${usersUrl}janedoe/follow`, { | ||
status: 404, | ||
responseText: error, | ||
}); | ||
const expectedActions = [{ type: actionTypes.SHOW_ERROR }]; | ||
store.dispatch(followThunk({ user: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Unfollow a user', () => { | ||
moxios.stubRequest(`${usersUrl}janedoe/follow`, { | ||
status: 200, | ||
responseText: { message: 'You have Unfollowed the User' }, | ||
}); | ||
const expectedActions = [{ type: actionTypes.UNFOLLOW_USER }]; | ||
store.dispatch(unfollowThunk({ user: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Tyring to Unfollow a user that does not exist', () => { | ||
moxios.stubRequest(`${usersUrl}janedoe/follow`, { | ||
status: 404, | ||
responseText: error, | ||
}); | ||
const expectedActions = [{ type: actionTypes.SHOW_ERROR }]; | ||
store.dispatch(unfollowThunk({ user: 'janedoe', token: 'abcabc' })) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('View followers of a specific user', () => { | ||
const url = `${usersUrl}janedoe/followers`; | ||
moxios.stubRequest(url, { | ||
status: 200, | ||
responseText: profilesList, | ||
}); | ||
const expectedActions = [{ type: actionTypes.GET_FOLLOWERS }]; | ||
store.dispatch(getFollowProfilesThunk(url, true)) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('View users that a specific user follows ("followees")', () => { | ||
const url = `${usersUrl}janedoe/following`; | ||
moxios.stubRequest(url, { | ||
status: 200, | ||
responseText: profilesList, | ||
}); | ||
const expectedActions = [{ type: actionTypes.GET_FOLLOWEES }]; | ||
store.dispatch(getFollowProfilesThunk(url, false)) | ||
.then(() => { | ||
expect(store.getActions()).toEqual(expectedActions); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`<Follow /> component renders the Follow component 1`] = ` | ||
<button | ||
className="btn btn-outline-danger btn-sm m-2" | ||
id="bt-follow" | ||
onClick={[Function]} | ||
type="button" | ||
> | ||
Unfollow | ||
</button> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const Follow = ({ onClick, option }) => ( | ||
<button | ||
type="button" | ||
id="bt-follow" | ||
className={option ? 'btn btn-outline-danger btn-sm m-2' : 'btn btn-outline-primary btn-sm m-2'} | ||
onClick={() => { onClick(option); }} | ||
> | ||
{option ? 'Unfollow' : 'Follow'} | ||
</button> | ||
); | ||
|
||
Follow.propTypes = { | ||
onClick: PropTypes.func.isRequired, | ||
option: PropTypes.bool.isRequired, | ||
}; | ||
|
||
export default Follow; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import Follow from '.'; | ||
|
||
describe('<Follow /> component', () => { | ||
it('renders the Follow component', () => { | ||
const wrapper = shallow(<Follow onClick={jest.fn()} option />); | ||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.