Skip to content

Commit

Permalink
feat(password reset): user should be able to reset their password (#37)
Browse files Browse the repository at this point in the history
• Created an email confirmation form
    • Created a password reset form

[Finishes #165273526]
  • Loading branch information
bisonlou authored and kcharles52 committed Jun 20, 2019
1 parent 3e08557 commit da27459
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/components/ArticlePreview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ ArticlePreview.propTypes = {
views: PropTypes.number.isRequired,
reads: PropTypes.number.isRequired,
}),
likeCount: PropTypes.arrayOf(PropTypes.shape({
likeCount: PropTypes.shape({
likes: PropTypes.number.isRequired,
dislikes: PropTypes.number.isRequired,
})),
}),
}),
};

Expand Down
32 changes: 26 additions & 6 deletions src/components/EmailConfirmationForm/EmailConfirmationForm.test.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
// react libraries
import React from 'react';

// third party libraries
import { shallow } from 'enzyme';

// components
import EmailConfirmationForm from '.';

describe('EmailConfirmation Component', () => {
const props = {
backdropId: 'id',
closeModal: () => { },
};

it('should render without exploding', () => {
const wrapper = shallow(<EmailConfirmationForm {...props} />);
expect(wrapper.length).toBe(1);
});

// it('should call start logout on button click', () => {
// const mockLogout = jest.fn();
// const wrapper = shallow(<EmailConfirmationForm startLogout={mockLogout} />);
// wrapper.find('button').at(0).simulate('click');
// expect(mockLogout).toHaveBeenCalled();
// });
it('should render an error', () => {
const wrapper = shallow(<EmailConfirmationForm />);
wrapper.setProps({ isConfirmEmailError: true });

expect(wrapper.find('small').length).toBe(1);
});

it('should render a success message', () => {
const wrapper = shallow(<EmailConfirmationForm />);
wrapper.setProps({ isConfirmEmailSuccess: true });

expect(wrapper.find('small').length).toBe(1);
});

it('should render a loader while sending a message', () => {
const wrapper = shallow(<EmailConfirmationForm />);
wrapper.setProps({ isLoading: true });

expect(wrapper.find('.signupForm__loader').length).toBe(1);
});
});
4 changes: 2 additions & 2 deletions src/pages/Landing/ArticleColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ ArticleColumn.propTypes = {
views: PropTypes.number.isRequired,
reads: PropTypes.number.isRequired,
}),
likeCount: PropTypes.arrayOf(PropTypes.shape({
likeCount: PropTypes.shape({
likes: PropTypes.number.isRequired,
dislikes: PropTypes.number.isRequired,
})),
}),
})),
};

Expand Down
4 changes: 2 additions & 2 deletions src/pages/Landing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ LandingPage.propTypes = {
views: PropTypes.number.isRequired,
reads: PropTypes.number.isRequired,
}),
likeCount: PropTypes.arrayOf(PropTypes.shape({
likeCount: PropTypes.shape({
likes: PropTypes.number.isRequired,
dislikes: PropTypes.number.isRequired,
})),
}),
})),
};

Expand Down
46 changes: 37 additions & 9 deletions src/pages/PasswordReset/PasswordReset.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { shallow, mount } from 'enzyme';
import { PasswordReset } from '.';
import { PasswordReset, mapDispatchToProps, mapStateToProps } from '.';


const mockStore = configureStore();
Expand Down Expand Up @@ -33,13 +33,6 @@ const initialState = {

const store = mockStore(initialState);

describe('Password Reset Component', () => {
it('should render without exploding', () => {
const wrapper = shallow(<PasswordReset />);
expect(wrapper.length).toBe(1);
});
});

describe('Password Reset Component', () => {
const wrapper = mount(
<Provider store={store}>
Expand All @@ -60,6 +53,11 @@ describe('Password Reset Component', () => {
const passwordResetWrapper = wrapper.find('PasswordReset');
const buttonWrapper = wrapper.find('.password-reset__fields__field__button');

it('should render without exploding', () => {
const wrapper = shallow(<PasswordReset />);
expect(wrapper.length).toBe(1);
});

it('should handle change', () => {
const inputWrapper = wrapper.find('#confirmPassword');
inputWrapper.simulate('change', event);
Expand Down Expand Up @@ -88,7 +86,7 @@ describe('Password Reset Component', () => {
expect(component.find('Loader').length).toBe(1);
});

it('should show a a success message when the password is reset', () => {
it('should display a success message when the password is reset', () => {
const component = shallow(<PasswordReset />);
component.setProps({ isPassordResetSuccess: true });

Expand All @@ -99,6 +97,11 @@ describe('Password Reset Component', () => {
const component = shallow(<PasswordReset />);
component.setProps({ isPasswordResetError: true });
component.setProps({ tokenErrors: ['test error'] });
});

it('should display a password reset error', () => {
const component = shallow(<PasswordReset />);
component.setProps({ isPasswordResetError: true, passwordErrors: ['Invalid password'] });

expect(component.find('.error').length).toBe(1);
});
Expand All @@ -110,4 +113,29 @@ describe('Password Reset Component', () => {

expect(component.find('.error').length).toBe(1);
});

it('should display a a token error when a token is invalid', () => {
const component = shallow(<PasswordReset />);
component.setProps({ isPasswordResetError: true, tokenErrors: ['Invalid password'] });

expect(component.find('.error').length).toBe(1);
});

it('should map state to props', () => {
const mockedState = {
passwordResetReducer: {
isLoading: true,
}
};

const state = mapStateToProps(mockedState);
expect(state.isLoading).toBe(true);
});

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

mapDispatchToProps(mockedDispatch).dispatchResetPassword();
expect(mockedDispatch).toHaveBeenCalled();
});
});
4 changes: 2 additions & 2 deletions src/pages/PasswordReset/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ PasswordReset.defaultProps = {
dispatchResetPassword: () => { },
};

const mapStateToProps = ({
export const mapStateToProps = ({
passwordResetReducer: {
isLoading,
isPassordResetSuccess,
Expand All @@ -198,7 +198,7 @@ const mapStateToProps = ({
tokenErrors,
});

const mapDispatchToProps = dispatch => ({
export const mapDispatchToProps = dispatch => ({
dispatchResetPassword: (password, token) => dispatch(resetPassword(password, token)),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ jest.mock('axios');

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore();
let store;

const mockData = { status: 'OK' };
const email = '[email protected]';

beforeEach(() => {
store = mockStore();
jest.clearAllMocks();
});

it('Should send an email', async () => {
mockAxios.post.mockImplementationOnce(() => Promise.resolve({ data: mockData }));

Expand All @@ -31,3 +36,17 @@ it('Should send an email', async () => {
expect(store.getActions()).toEqual(expectedActions);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});

it('Should send an email', async () => {
mockAxios.post.mockRejectedValueOnce();

const expectedActions = [
{ type: CONFIRM_EMAIL_START },
{ type: CONFIRM_EMAIL_FAILURE, error: undefined },
];

await store.dispatch(confirmEmailActions(email));

expect(store.getActions()).toEqual(expectedActions);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});
69 changes: 69 additions & 0 deletions src/store/actions/resetPassword/resetPasswordActions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// third party libraries
import mockAxios from 'axios';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

// thunks
import resetPassword from 'store/actions/resetPassword';
import {
PASSWORD_RESET_START,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAILURE,
TOKEN_RESET_FAILURE,
} from 'store/actions/passwordResetTypes';

jest.mock('axios');

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
let store;

const mockData = { status: 'OK' };
const password = 'Pa$$word123';

beforeEach(() => {
store = mockStore();
jest.clearAllMocks();
});

it('Should reset the password', async () => {
mockAxios.post.mockResolvedValue({ data: mockData });

const expectedActions = [
{ type: PASSWORD_RESET_START },
{ type: PASSWORD_RESET_SUCCESS },
];

await store.dispatch(resetPassword(password));

expect(store.getActions()).toEqual(expectedActions);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});

it('Should dispatch the password error action on failure', async () => {
mockAxios.post.mockRejectedValue({ response: { data: { errors: { password: [] } } } });

const expectedActions = [
{ type: PASSWORD_RESET_START },
{ type: PASSWORD_RESET_FAILURE, error: [] },
];

await store.dispatch(resetPassword(password));

expect(store.getActions()).toEqual(expectedActions);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});

it('Should dispatch the token error action on failure', async () => {
mockAxios.post.mockRejectedValue({ response: { data: { error: 'Invalid token' } } });

const expectedActions = [
{ type: PASSWORD_RESET_START },
{ type: TOKEN_RESET_FAILURE, error: ['Invalid token'] },
];

await store.dispatch(resetPassword(password));

expect(store.getActions()).toEqual(expectedActions);
expect(mockAxios.post).toHaveBeenCalledTimes(1);
});
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
import {
PASSWORD_RESET_START,
PASSWORD_RESET_SUCCESS,
PASSWORD_RESET_FAILURE
PASSWORD_RESET_FAILURE,
TOKEN_RESET_FAILURE,
} from 'store/actions/passwordResetTypes';

import passwordResetReducer from '.';

describe('cpassowd reset Reducer', () => {
describe('Password reset Reducer', () => {
it('Should return the initial state', () => {
const newState = passwordResetReducer(undefined, {});
expect(newState.isLoading).toBe(false);
expect(newState.isPassordResetSuccess).toBe(false);
expect(newState.isPasswordResetError).toBe(false);
});

it('Should start loading', () => {
it('Should indicate the start of loading', () => {
const newState = passwordResetReducer(undefined, { type: PASSWORD_RESET_START });
expect(newState.isLoading).toBe(true);
expect(newState.isPassordResetSuccess).toBe(false);
expect(newState.isPasswordResetError).toBe(false);
});

it('Should set successfuly have reset the password', () => {
it('Should indicate a successful reset of the password', () => {
const newState = passwordResetReducer(undefined, { type: PASSWORD_RESET_SUCCESS });
expect(newState.isLoading).toBe(false);
expect(newState.isPassordResetSuccess).toBe(true);
expect(newState.isPasswordResetError).toBe(false);
});

it('Should have failed', () => {
const newState = passwordResetReducer(undefined, { type: PASSWORD_RESET_FAILURE });
it('Should indicate failure in reseting the password due to a password error', () => {
const newState = passwordResetReducer(undefined, { type: PASSWORD_RESET_FAILURE, error: ['Invalid password'] });
expect(newState.isLoading).toBe(false);
expect(newState.isPassordResetSuccess).toBe(false);
expect(newState.isPasswordResetError).toBe(true);
expect(newState.passwordErrors.length).toBe(1);
});

it('Should indicate failure in reseting the password due to a token error', () => {
const newState = passwordResetReducer(undefined, { type: TOKEN_RESET_FAILURE, error: ['Token error'] });
expect(newState.isLoading).toBe(false);
expect(newState.isPassordResetSuccess).toBe(false);
expect(newState.isPasswordResetError).toBe(true);
expect(newState.tokenErrors.length).toBe(1);
});
});

0 comments on commit da27459

Please sign in to comment.