diff --git a/src/reset-password/ResetPasswordPage.jsx b/src/reset-password/ResetPasswordPage.jsx index 54629fe9..511b4e5a 100644 --- a/src/reset-password/ResetPasswordPage.jsx +++ b/src/reset-password/ResetPasswordPage.jsx @@ -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); } }; diff --git a/src/reset-password/data/actions.js b/src/reset-password/data/actions.js index 7e8eb146..88b3b71a 100644 --- a/src/reset-password/data/actions.js +++ b/src/reset-password/data/actions.js @@ -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 = () => ({ diff --git a/src/reset-password/data/sagas.js b/src/reset-password/data/sagas.js index 849f7200..3768cdb5 100644 --- a/src/reset-password/data/sagas.js +++ b/src/reset-password/data/sagas.js @@ -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; diff --git a/src/reset-password/data/service.js b/src/reset-password/data/service.js index 8682c728..cc7f3274 100644 --- a/src/reset-password/data/service.js +++ b/src/reset-password/data/service.js @@ -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, ) diff --git a/src/reset-password/data/tests/sagas.test.js b/src/reset-password/data/tests/sagas.test.js new file mode 100644 index 00000000..9541d8f6 --- /dev/null +++ b/src/reset-password/data/tests/sagas.test.js @@ -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(); + }); +}); diff --git a/src/reset-password/tests/ResetPasswordPage.test.jsx b/src/reset-password/tests/ResetPasswordPage.test.jsx index 901d9679..f6bb7b24 100644 --- a/src/reset-password/tests/ResetPasswordPage.test.jsx +++ b/src/reset-password/tests/ResetPasswordPage.test.jsx @@ -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()); + 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()) + .toJSON(); + expect(tree).toMatchSnapshot(); + }); }); diff --git a/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap b/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap index 55bbb2a8..049eb703 100644 --- a/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap +++ b/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap @@ -355,3 +355,51 @@ Array [ , ] `; + +exports[`ResetPasswordPage show spinner component during token validation 1`] = ` +
+
+
+
+

+ + Token validation in progress .. + + +

+
+
+
+
+`;