Enable forgot password functinality for secondary email.

VAN-18
This commit is contained in:
Adeel Khan
2020-10-20 09:00:08 +05:00
parent 0e0782c0e1
commit ca54e93aba
7 changed files with 166 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { Button, Input, ValidationFormGroup } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getQueryParameters } from '@edx/frontend-platform';
import messages from './messages';
import { resetPassword, validateToken } from './data/actions';
import { resetPasswordResultSelector } from './data/selectors';
@@ -16,6 +17,7 @@ import Spinner from './Spinner';
const ResetPasswordPage = (props) => {
const { intl } = props;
const params = getQueryParameters();
const [newPasswordInput, setNewPasswordValue] = useState('');
const [confirmPasswordInput, setConfirmPasswordValue] = useState('');
@@ -55,7 +57,7 @@ const ResetPasswordPage = (props) => {
new_password1: newPasswordInput,
new_password2: confirmPasswordInput,
};
props.resetPassword(formPayload, props.token);
props.resetPassword(formPayload, props.token, params);
}
};

View File

@@ -24,9 +24,9 @@ export const validateTokenFailure = (tokenStatus) => ({
});
// Reset Password
export const resetPassword = (formPayload, token) => ({
export const resetPassword = (formPayload, token, params) => ({
type: RESET_PASSWORD.BASE,
payload: { formPayload, token },
payload: { formPayload, token, params },
});
export const resetPasswordBegin = () => ({

View File

@@ -34,7 +34,7 @@ export function* handleValidateToken(action) {
export function* handleResetPassword(action) {
try {
yield put(resetPasswordBegin());
const data = yield call(resetPassword, action.payload.formPayload, action.payload.token);
const data = yield call(resetPassword, action.payload.formPayload, action.payload.token, action.payload.params);
const resetStatus = data.reset_status;
const resetErrors = data.err_msg;

View File

@@ -22,15 +22,17 @@ export async function validateToken(token) {
}
// eslint-disable-next-line import/prefer-default-export
export async function resetPassword(payload, token) {
export async function resetPassword(payload, token, queryParams) {
const requestConfig = {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
isPublic: true,
};
let path = `password/reset/${token}/?track=pwreset`;
path += queryParams.is_account_recovery ? '&is_account_recovery=true' : '';
const { data } = await getAuthenticatedHttpClient()
.post(
`${getConfig().LMS_BASE_URL}/password/reset/${token}/?track=pwreset`,
`${getConfig().LMS_BASE_URL}/${path}`,
formurlencoded(payload),
requestConfig,
)

View File

@@ -0,0 +1,59 @@
import { runSaga } from 'redux-saga';
import {
resetPasswordBegin,
resetPasswordSuccess,
resetPasswordFailure,
} from '../actions';
import { handleResetPassword } from '../sagas';
import * as api from '../service';
describe('handleResetPassword', () => {
const params = {
payload: {
formPayload: {
new_password1: 'new_password1',
new_password2: 'new_password1',
},
token: 'token',
params: {},
},
};
const responseData = {
reset_status: true,
err_msg: '',
};
it('should call service and dispatch success action', async () => {
const resetPassword = jest.spyOn(api, 'resetPassword')
.mockImplementation(() => Promise.resolve(responseData));
const dispatched = [];
await runSaga(
{ dispatch: (action) => dispatched.push(action) },
handleResetPassword,
params,
);
expect(resetPassword).toHaveBeenCalledTimes(1);
expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordSuccess(true)]);
resetPassword.mockClear();
});
it('should call service and dispatch error action', async () => {
const resetPassword = jest.spyOn(api, 'resetPassword')
.mockImplementation(() => Promise.reject());
const dispatched = [];
await runSaga(
{ dispatch: (action) => dispatched.push(action) },
handleResetPassword,
params,
);
expect(resetPassword).toHaveBeenCalledTimes(1);
expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure()]);
resetPassword.mockClear();
});
});

View File

@@ -1,11 +1,12 @@
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import { act } from 'react-dom/test-utils';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { mount } from 'enzyme';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import * as auth from '@edx/frontend-platform/auth';
import { resetPassword } from '../data/actions';
import ResetPasswordPage from '../ResetPasswordPage';
@@ -15,6 +16,7 @@ jest.mock('@edx/frontend-platform/auth');
const IntlResetPasswordPage = injectIntl(ResetPasswordPage);
const mockStore = configureStore();
describe('ResetPasswordPage', () => {
let props = {};
let store = {};
@@ -36,6 +38,10 @@ describe('ResetPasswordPage', () => {
};
});
afterEach(() => {
jest.clearAllMocks();
});
it('should match reset password default section snapshot', () => {
props = {
...props,
@@ -106,4 +112,46 @@ describe('ResetPasswordPage', () => {
resetPasswordPage.update();
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(validationMessage);
});
it('with valid inputs resetPassword action is dispatch', () => {
const newPassword = 'test-password1';
store = mockStore({
...store,
});
props = {
...props,
token_status: 'valid',
token: 'token',
};
const formPayload = {
new_password1: newPassword,
new_password2: newPassword,
};
store.dispatch = jest.fn(store.dispatch);
const resetPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
resetPage.find('input#reset-password-input').simulate('blur', { target: { value: newPassword } });
resetPage.find('input#confirm-password-input').simulate('change', { target: { value: newPassword } });
resetPage.find('button.submit').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(resetPassword(formPayload, props.token, {}));
resetPage.unmount();
});
it('show spinner component during token validation', () => {
props = {
...props,
token_status: 'pending',
match: {
params: {
token: 'test-token',
},
},
};
const tree = renderer.create(reduxWrapper(<IntlResetPasswordPage {...props} />))
.toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -355,3 +355,51 @@ Array [
</div>,
]
`;
exports[`ResetPasswordPage show spinner component during token validation 1`] = `
<div
className="d-flex justify-content-center reset-password-container"
>
<div
className="d-flex flex-column"
style={
Object {
"width": "400px",
}
}
>
<div
className="form-group"
>
<div
className="d-flex flex-column align-items-start"
>
<h3
className="text-center mt-3"
>
<span>
Token validation in progress ..
</span>
<svg
aria-hidden="true"
className="svg-inline--fa fa-spinner fa-w-16 fa-spin "
data-icon="spinner"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z"
fill="currentColor"
style={Object {}}
/>
</svg>
</h3>
</div>
</div>
</div>
</div>
`;