- {props.status === 'failure' && props.errors === INTERNAL_SERVER_ERROR ? (
-
- ) : null}
- {props.status === 'failure' && props.errors === API_RATELIMIT_ERROR ? (
-
-
- {bannerErrorMessage ? (
-
- {intl.formatMessage(messages['forgot.password.empty.new.password.error.heading'])}
-
-
- ) : null}
+
+
+ {intl.formatMessage(messages['sign.in'])}
+
+
+
+
+
+
{intl.formatMessage(messages['reset.password'])}
+
{intl.formatMessage(messages['reset.password.page.instructions'])}
- >
+
);
}
return null;
@@ -199,7 +172,7 @@ ResetPasswordPage.defaultProps = {
status: null,
token: null,
match: null,
- errors: null,
+ errorMsg: null,
};
ResetPasswordPage.propTypes = {
@@ -213,7 +186,7 @@ ResetPasswordPage.propTypes = {
}),
}),
status: PropTypes.string,
- errors: PropTypes.string,
+ errorMsg: PropTypes.string,
};
export default connect(
diff --git a/src/reset-password/ResetPasswordSuccess.jsx b/src/reset-password/ResetPasswordSuccess.jsx
new file mode 100644
index 00000000..3b1bc19f
--- /dev/null
+++ b/src/reset-password/ResetPasswordSuccess.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Alert } from '@edx/paragon';
+
+import messages from './messages';
+
+const ResetPasswordSuccess = (props) => {
+ const { intl } = props;
+
+ return (
+
+
+ {intl.formatMessage(messages['reset.password.success.heading'])}
+
+ {intl.formatMessage(messages['reset.password.success'])}
+
+ );
+};
+
+ResetPasswordSuccess.propTypes = {
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(ResetPasswordSuccess);
diff --git a/src/reset-password/ResetSuccess.jsx b/src/reset-password/ResetSuccess.jsx
deleted file mode 100644
index 37a298ea..00000000
--- a/src/reset-password/ResetSuccess.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import { Helmet } from 'react-helmet';
-
-import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import { getConfig } from '@edx/frontend-platform';
-import { Alert } from '@edx/paragon';
-
-import messages from './messages';
-
-const ResetSuccessMessage = (props) => {
- const { intl } = props;
-
- const loginPasswordLink = (
-
-
-
- );
-
- return (
- <>
-
- {intl.formatMessage(messages['reset.password.page.title'],
- { siteName: getConfig().SITE_NAME })}
-
-
-
-
-
-
-
- {intl.formatMessage(messages['reset.password.request.success.header.message'])}
-
-
-
-
-
-
- >
- );
-};
-
-ResetSuccessMessage.propTypes = {
- intl: intlShape.isRequired,
-};
-
-export default injectIntl(ResetSuccessMessage);
diff --git a/src/reset-password/Spinner.jsx b/src/reset-password/Spinner.jsx
deleted file mode 100644
index f02bff8e..00000000
--- a/src/reset-password/Spinner.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { Spinner as ParagonSpinner } from '@edx/paragon';
-
-const Spinner = () => (
-
-);
-
-export default Spinner;
diff --git a/src/reset-password/data/actions.js b/src/reset-password/data/actions.js
index 7e6b637b..846db6e9 100644
--- a/src/reset-password/data/actions.js
+++ b/src/reset-password/data/actions.js
@@ -2,6 +2,12 @@ import { AsyncActionType } from '../../data/utils';
export const RESET_PASSWORD = new AsyncActionType('RESET', 'PASSWORD');
export const VALIDATE_TOKEN = new AsyncActionType('VALIDATE', 'TOKEN');
+export const PASSWORD_RESET_FAILURE = 'PASSWORD_RESET_FAILURE';
+
+export const passwordResetFailure = (errorCode) => ({
+ type: PASSWORD_RESET_FAILURE,
+ payload: { errorCode },
+});
// Validate confirmation token
export const validateToken = (token) => ({
@@ -18,9 +24,9 @@ export const validateTokenSuccess = (tokenStatus, token) => ({
payload: { tokenStatus, token },
});
-export const validateTokenFailure = errors => ({
+export const validateTokenFailure = errorCode => ({
type: VALIDATE_TOKEN.FAILURE,
- payload: { errors },
+ payload: { errorCode },
});
// Reset Password
@@ -38,7 +44,7 @@ export const resetPasswordSuccess = data => ({
payload: { data },
});
-export const resetPasswordFailure = errors => ({
+export const resetPasswordFailure = (errorCode, errorMsg = null) => ({
type: RESET_PASSWORD.FAILURE,
- payload: { errors },
+ payload: { errorCode, errorMsg: errorMsg || errorCode },
});
diff --git a/src/reset-password/data/constants.js b/src/reset-password/data/constants.js
new file mode 100644
index 00000000..6f50fcf7
--- /dev/null
+++ b/src/reset-password/data/constants.js
@@ -0,0 +1,15 @@
+export const TOKEN_STATE = {
+ PENDING: 'token-pending',
+ VALID: 'token-valid',
+};
+
+// password reset error codes
+export const FORM_SUBMISSION_ERROR = 'form-submission-error';
+export const PASSWORD_RESET_ERROR = 'password-reset-error';
+export const PASSWORD_VALIDATION_ERROR = 'password-validation-failure';
+
+export const PASSWORD_RESET = {
+ INVALID_TOKEN: 'invalid-token',
+ INTERNAL_SERVER_ERROR: 'password-reset-internal-server-error',
+ FORBIDDEN_REQUEST: 'password-reset-rate-limit-error',
+};
diff --git a/src/reset-password/data/reducers.js b/src/reset-password/data/reducers.js
index f7ce1898..554bcb2d 100644
--- a/src/reset-password/data/reducers.js
+++ b/src/reset-password/data/reducers.js
@@ -1,9 +1,10 @@
-import { RESET_PASSWORD, VALIDATE_TOKEN } from './actions';
+import { RESET_PASSWORD, VALIDATE_TOKEN, PASSWORD_RESET_FAILURE } from './actions';
+import { PASSWORD_RESET_ERROR, TOKEN_STATE } from './constants';
export const defaultState = {
- status: 'token-pending',
+ status: TOKEN_STATE.PENDING,
token: null,
- errors: null,
+ errorMsg: null,
};
const reducer = (state = defaultState, action = null) => {
@@ -11,14 +12,13 @@ const reducer = (state = defaultState, action = null) => {
case VALIDATE_TOKEN.SUCCESS:
return {
...state,
- status: 'valid',
+ status: TOKEN_STATE.VALID,
token: action.payload.token,
};
- case VALIDATE_TOKEN.FAILURE:
+ case PASSWORD_RESET_FAILURE:
return {
...state,
- status: 'invalid',
- errors: action.payload.errors,
+ status: PASSWORD_RESET_ERROR,
};
case RESET_PASSWORD.BEGIN:
return {
@@ -33,8 +33,8 @@ const reducer = (state = defaultState, action = null) => {
case RESET_PASSWORD.FAILURE:
return {
...state,
- status: 'failure',
- errors: action.payload.errors,
+ status: action.payload.errorCode,
+ errorMsg: action.payload.errorMsg,
};
default:
return state;
diff --git a/src/reset-password/data/sagas.js b/src/reset-password/data/sagas.js
index ef16270c..ea10b174 100644
--- a/src/reset-password/data/sagas.js
+++ b/src/reset-password/data/sagas.js
@@ -6,15 +6,15 @@ import {
VALIDATE_TOKEN,
validateTokenBegin,
validateTokenSuccess,
- validateTokenFailure,
RESET_PASSWORD,
resetPasswordBegin,
resetPasswordSuccess,
resetPasswordFailure,
+ passwordResetFailure,
} from './actions';
import { validateToken, resetPassword } from './service';
-import { INTERNAL_SERVER_ERROR, API_RATELIMIT_ERROR } from '../../data/constants';
+import { PASSWORD_RESET, PASSWORD_VALIDATION_ERROR } from './constants';
// Services
export function* handleValidateToken(action) {
@@ -25,15 +25,14 @@ export function* handleValidateToken(action) {
if (isValid) {
yield put(validateTokenSuccess(isValid, action.payload.token));
} else {
- yield put(validateTokenFailure(isValid));
+ yield put(passwordResetFailure(PASSWORD_RESET.INVALID_TOKEN));
}
} catch (err) {
- const statusCodes = [429];
- if (err.response && statusCodes.includes(err.response.status)) {
- yield put(validateTokenFailure(API_RATELIMIT_ERROR));
+ if (err.response && err.response.status === 429) {
+ yield put(passwordResetFailure(PASSWORD_RESET.FORBIDDEN_REQUEST));
logInfo(err);
} else {
- yield put(validateTokenFailure(INTERNAL_SERVER_ERROR));
+ yield put(passwordResetFailure(PASSWORD_RESET.INTERNAL_SERVER_ERROR));
logError(err);
}
}
@@ -49,15 +48,14 @@ export function* handleResetPassword(action) {
if (resetStatus) {
yield put(resetPasswordSuccess(resetStatus));
} else {
- yield put(resetPasswordFailure(resetErrors));
+ yield put(resetPasswordFailure(PASSWORD_VALIDATION_ERROR, resetErrors));
}
} catch (err) {
- const statusCodes = [429];
- if (err.response && statusCodes.includes(err.response.status)) {
- yield put(resetPasswordFailure(API_RATELIMIT_ERROR));
+ if (err.response && err.response.status === 429) {
+ yield put(resetPasswordFailure(PASSWORD_RESET.FORBIDDEN_REQUEST));
logInfo(err);
} else {
- yield put(resetPasswordFailure(INTERNAL_SERVER_ERROR));
+ yield put(resetPasswordFailure(PASSWORD_RESET.INTERNAL_SERVER_ERROR));
logError(err);
}
}
diff --git a/src/reset-password/data/tests/sagas.test.js b/src/reset-password/data/tests/sagas.test.js
index 47f9060f..f156d334 100644
--- a/src/reset-password/data/tests/sagas.test.js
+++ b/src/reset-password/data/tests/sagas.test.js
@@ -3,14 +3,14 @@ import { runSaga } from 'redux-saga';
import {
resetPasswordBegin,
resetPasswordSuccess,
- resetPasswordFailure,
validateTokenBegin,
- validateTokenFailure,
+ passwordResetFailure, resetPasswordFailure,
} from '../actions';
+import { PASSWORD_RESET } from '../constants';
import { handleResetPassword, handleValidateToken } from '../sagas';
import * as api from '../service';
+
import initializeMockLogging from '../../../setupTest';
-import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../../data/constants';
const { loggingService } = initializeMockLogging();
@@ -57,7 +57,7 @@ describe('handleResetPassword', () => {
response: {
status: 500,
data: {
- errorCode: INTERNAL_SERVER_ERROR,
+ errorCode: PASSWORD_RESET.INTERNAL_SERVER_ERROR,
},
},
};
@@ -73,7 +73,7 @@ describe('handleResetPassword', () => {
expect(loggingService.logError).toHaveBeenCalled();
expect(resetPassword).toHaveBeenCalledTimes(1);
- expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(INTERNAL_SERVER_ERROR)]);
+ expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(PASSWORD_RESET.INTERNAL_SERVER_ERROR)]);
resetPassword.mockClear();
});
@@ -82,7 +82,7 @@ describe('handleResetPassword', () => {
response: {
status: 429,
data: {
- errorCode: API_RATELIMIT_ERROR,
+ errorCode: PASSWORD_RESET.FORBIDDEN_REQUEST,
},
},
};
@@ -98,7 +98,7 @@ describe('handleResetPassword', () => {
expect(loggingService.logInfo).toHaveBeenCalled();
expect(resetPassword).toHaveBeenCalledTimes(1);
- expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(API_RATELIMIT_ERROR)]);
+ expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(PASSWORD_RESET.FORBIDDEN_REQUEST)]);
resetPassword.mockClear();
});
});
@@ -121,7 +121,7 @@ describe('handleValidateToken', () => {
response: {
status: 500,
data: {
- errorCode: INTERNAL_SERVER_ERROR,
+ errorCode: PASSWORD_RESET.INTERNAL_SERVER_ERROR,
},
},
};
@@ -136,16 +136,16 @@ describe('handleValidateToken', () => {
);
expect(validateToken).toHaveBeenCalledTimes(1);
- expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(INTERNAL_SERVER_ERROR)]);
+ expect(dispatched).toEqual([validateTokenBegin(), passwordResetFailure(PASSWORD_RESET.INTERNAL_SERVER_ERROR)]);
validateToken.mockClear();
});
- it('should call service and dispatch ratelimit error', async () => {
+ it('should call service and dispatch rate limit error', async () => {
const errorResponse = {
response: {
status: 429,
data: {
- errorCode: API_RATELIMIT_ERROR,
+ errorCode: PASSWORD_RESET.FORBIDDEN_REQUEST,
},
},
};
@@ -161,7 +161,7 @@ describe('handleValidateToken', () => {
expect(loggingService.logInfo).toHaveBeenCalled();
expect(validateToken).toHaveBeenCalledTimes(1);
- expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(API_RATELIMIT_ERROR)]);
+ expect(dispatched).toEqual([validateTokenBegin(), passwordResetFailure(PASSWORD_RESET.FORBIDDEN_REQUEST)]);
validateToken.mockClear();
});
});
diff --git a/src/reset-password/messages.js b/src/reset-password/messages.js
index b68d82d6..6319b6bc 100644
--- a/src/reset-password/messages.js
+++ b/src/reset-password/messages.js
@@ -1,45 +1,51 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
+ 'sign.in': {
+ id: 'sign.in',
+ defaultMessage: 'Sign in',
+ description: 'Sign in toggle text',
+ },
'reset.password.page.title': {
id: 'reset.password.page.title',
defaultMessage: 'Reset Password | {siteName}',
description: 'page title',
},
- 'reset.password.page.heading': {
- id: 'reset.password.page.heading',
- defaultMessage: 'Reset your password',
- description: 'The page heading for the Reset password page.',
+ 'reset.password': {
+ id: 'reset.password',
+ defaultMessage: 'Reset password',
+ description: 'The page heading and button text for reset password page.',
},
'reset.password.page.instructions': {
id: 'reset.password.page.instructions',
defaultMessage: 'Enter and confirm your new password.',
description: 'Instructions message for reset password page.',
},
- 'reset.password.page.invalid.match.message': {
- id: 'reset.password.page.invalid.match.message',
- defaultMessage: 'Passwords do not match.',
- description: 'Password format error.',
- },
- 'reset.password.page.new.field.label': {
- id: 'forgot.password.page.new.field.label',
+ 'new.password.label': {
+ id: 'new.password.label',
defaultMessage: 'New password',
description: 'New password field label for the reset password page.',
},
- 'reset.password.page.confirm.field.label': {
- id: 'forgot.password.page.confirm.field.label',
+ 'confirm.password.label': {
+ id: 'confirm.password.label',
defaultMessage: 'Confirm password',
description: 'Confirm password field label for the reset password page.',
},
- 'reset.password.page.submit.button': {
- id: 'reset.password.page.submit.button',
- defaultMessage: 'Reset my password',
- description: 'Submit button text for the reset password page.',
+ // validation errors
+ 'password.validation.message': {
+ id: 'password.validation.message',
+ defaultMessage: 'Password criteria has not been met',
+ description: 'Error message for empty or invalid password',
},
- 'reset.password.request.success.header.message': {
- id: 'reset.password.request.success.header.message',
- defaultMessage: 'Password reset complete.',
- description: 'header message when reset is successful.',
+ 'passwords.do.not.match': {
+ id: 'passwords.do.not.match',
+ defaultMessage: 'Passwords do not match',
+ description: 'Password format error.',
+ },
+ 'confirm.your.password': {
+ id: 'confirm.your.password',
+ defaultMessage: 'Confirm your password',
+ description: 'Field validation message when confirm password is empty',
},
'forgot.password.confirmation.sign.in.link': {
id: 'forgot.password.confirmation.sign.in.link',
@@ -61,10 +67,16 @@ const messages = defineMessages({
defaultMessage: 'Please enter your new password.',
description: 'Error message that appears when user tries to submit form with empty New Password field',
},
- 'forgot.password.empty.new.password.error.heading': {
- id: 'forgot.password.empty.new.password.error.heading',
+ // alert banner strings
+ 'reset.password.failure.heading': {
+ id: 'reset.password.failure.heading',
defaultMessage: 'We couldn\'t reset your password.',
- description: 'Heading that appears above error message when user submits empty form.',
+ description: 'Heading for reset password request failure',
+ },
+ 'reset.password.form.submission.error': {
+ id: 'reset.password.form.submission.error',
+ defaultMessage: 'Please check your responses and try again.',
+ description: 'Error message for reset password page',
},
'reset.password.request.server.error': {
id: 'reset.password.request.server.error',
@@ -76,11 +88,31 @@ const messages = defineMessages({
defaultMessage: 'Token validation failure',
description: 'Failed to validate reset password token error message.',
},
- 'reset.server.ratelimit.error': {
- id: 'reset.server.ratelimit.error',
+ 'reset.server.rate.limit.error': {
+ id: 'reset.server.rate.limit.error',
defaultMessage: 'Too many requests.',
description: 'Too many request at server end point',
},
+ 'reset.password.success.heading': {
+ id: 'reset.password.success.heading',
+ defaultMessage: 'Password reset complete.',
+ description: 'Heading for alert box when reset password is successful',
+ },
+ 'reset.password.success': {
+ id: 'reset.password.success',
+ defaultMessage: 'Your password has been reset. Sign in to your account.',
+ description: 'Reset password success message',
+ },
+ 'internal.server.error': {
+ id: 'internal.server.error',
+ defaultMessage: 'An error has occurred. Try refreshing the page, or check your internet connection.',
+ description: 'Error message that appears when server responds with 500 error code',
+ },
+ 'rate.limit.error': {
+ id: 'rate.limit.error',
+ defaultMessage: 'An error has occurred because of too many requests. Please try again after some time.',
+ description: 'Error message that appears when server responds with 429 error code',
+ },
});
export default messages;
diff --git a/src/reset-password/tests/ResetPasswordPage.test.jsx b/src/reset-password/tests/ResetPasswordPage.test.jsx
index 25427cb1..cb3cf6e0 100644
--- a/src/reset-password/tests/ResetPasswordPage.test.jsx
+++ b/src/reset-password/tests/ResetPasswordPage.test.jsx
@@ -1,17 +1,17 @@
import React from 'react';
-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 CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
-import * as auth from '@edx/frontend-platform/auth';
-import { resetPassword } from '../data/actions';
-import { APIFailureMessage } from '../../common-components';
+import { mount } from 'enzyme';
+import configureStore from 'redux-mock-store';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+
+import * as auth from '@edx/frontend-platform/auth';
+import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
+import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
+
+import { resetPassword } from '../data/actions';
+import { PASSWORD_RESET, TOKEN_STATE } from '../data/constants';
import ResetPasswordPage from '../ResetPasswordPage';
-import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../data/constants';
jest.mock('@edx/frontend-platform/auth');
@@ -22,22 +22,19 @@ describe('ResetPasswordPage', () => {
let props = {};
let store = {};
- const emptyFieldError = 'Please enter your new password.';
- const validationMessage = 'This password is too short. It must contain at least 8 characters. This password must contain at least 1 number.';
-
const reduxWrapper = children => (
- {children}
+
+ {children}
+
);
- const submitForm = async (password) => {
+ const submitForm = (password) => {
const resetPasswordPage = mount(reduxWrapper(
));
- await act(async () => {
- resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: password } });
- });
- resetPasswordPage.find('input#confirm-password-input').simulate('change', { target: { value: password } });
- resetPasswordPage.find('button.btn-primary').simulate('click');
+ resetPasswordPage.find('input#newPassword').simulate('change', { target: { value: password, name: 'newPassword' } });
+ resetPasswordPage.find('input#confirmPassword').simulate('change', { target: { value: password, name: 'confirmPassword' } });
+ resetPasswordPage.find('button.btn-brand').simulate('click');
return resetPasswordPage;
};
@@ -47,7 +44,6 @@ describe('ResetPasswordPage', () => {
props = {
resetPassword: jest.fn(),
status: null,
- token_status: 'pending',
token: null,
errors: null,
match: {
@@ -60,120 +56,16 @@ describe('ResetPasswordPage', () => {
jest.clearAllMocks();
});
- it('should match reset password default section snapshot', () => {
- props = {
- ...props,
- token: 'token',
- token_status: 'valid',
- };
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
+ // ******** form submission tests ********
- it('show spinner component during token validation', () => {
- props = {
- ...props,
- token_status: 'pending',
- match: {
- params: {
- token: 'test-token',
- },
+ it('with valid inputs resetPassword action is dispatched', async () => {
+ const password = 'test-password-1';
+
+ store = mockStore({
+ resetPassword: {
+ status: TOKEN_STATE.VALID,
},
- };
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match invalid token message section snapshot', () => {
- props = {
- ...props,
- token_status: 'invalid',
- };
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match pending reset message section snapshot', () => {
- props = {
- ...props,
- token_status: 'valid',
- status: 'pending',
- };
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should match successful reset message section snapshot', () => {
- props = {
- ...props,
- token_status: 'valid',
- status: 'success',
- };
- const tree = renderer.create(reduxWrapper(
))
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- it('should display invalid password message', async () => {
- props = {
- ...props,
- token_status: 'valid',
- };
-
- auth.getHttpClient = jest.fn(() => ({
- post: async () => ({
- data: {
- validation_decisions: {
- password: validationMessage,
- },
- },
- catch: () => {},
- }),
- }));
-
- const resetPasswordPage = mount(reduxWrapper(
));
-
- // Focus out of empty field
- await act(async () => {
- await resetPasswordPage.find('input#reset-password-input').simulate('blur');
});
- resetPasswordPage.update();
- expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
-
- // Enter non-compliant password
- await act(async () => {
- await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: 'invalid' } });
- });
- expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(validationMessage);
- });
-
- it('should display error message on empty form submission', () => {
- const bannerMessage = 'We couldn\'t reset your password.'.concat(emptyFieldError);
- props = {
- ...props,
- token_status: 'valid',
- token: 'token',
- };
-
- const resetPasswordPage = mount(reduxWrapper(
));
- resetPasswordPage.find('button.btn-primary').simulate('click');
-
- resetPasswordPage.update();
- expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
- expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
- });
-
- it('with valid inputs resetPassword action is dispatch', async () => {
- const newPassword = 'test-password1';
- props = {
- ...props,
- token_status: 'valid',
- token: 'token',
- };
auth.getHttpClient = jest.fn(() => ({
post: async () => ({
@@ -182,110 +74,83 @@ describe('ResetPasswordPage', () => {
}),
}));
- const formPayload = {
- new_password1: newPassword,
- new_password2: newPassword,
- };
-
store.dispatch = jest.fn(store.dispatch);
+ const resetPasswordPage = submitForm(password);
- const resetPasswordPage = await submitForm(newPassword);
- expect(store.dispatch).toHaveBeenCalledWith(resetPassword(formPayload, props.token, {}));
+ expect(store.dispatch).toHaveBeenCalledWith(resetPassword(
+ { new_password1: password, new_password2: password }, props.token, {},
+ ));
resetPasswordPage.unmount();
});
- it('should dispatch resetPassword action if validations have reached rate limit', async () => {
- const password = 'test-password';
+ // ******** test reset password field validations ********
- auth.getHttpClient = jest.fn(() => ({
- post: async () => {
- throw new Error('error');
+ it('should show error messages for required fields on empty form submission', () => {
+ store = mockStore({
+ resetPassword: {
+ status: TOKEN_STATE.VALID,
},
- }));
- store.dispatch = jest.fn(store.dispatch);
+ });
+ const resetPasswordPage = mount(reduxWrapper(
));
+ resetPasswordPage.find('button.btn-brand').simulate('click');
- props = {
- ...props,
- token_status: 'valid',
- token: 'token',
- };
-
- const resetPasswordPage = await submitForm(password);
- expect(store.dispatch).toHaveBeenCalledWith(
- resetPassword({ new_password1: password, new_password2: password }, props.token, {}),
+ expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(
+ 'We couldn\'t reset your password.Please check your responses and try again.',
);
-
- resetPasswordPage.unmount();
+ expect(resetPasswordPage.find('div[feedback-for="newPassword"]').text()).toEqual('Password criteria has not been met');
+ expect(resetPasswordPage.find('div[feedback-for="confirmPassword"]').text()).toEqual('Confirm your password');
});
- it('should not update the banner message on focus out', async () => {
- const bannerMessage = 'We couldn\'t reset your password.'.concat(validationMessage);
- props = {
- ...props,
- token_status: 'valid',
- token: 'token',
- errors: validationMessage,
- status: 'failure',
- };
+ it('should show error message when new and confirm password do not match', () => {
+ store = mockStore({
+ resetPassword: {
+ status: TOKEN_STATE.VALID,
+ },
+ });
+ const resetPasswordPage = mount(reduxWrapper(
));
+ resetPasswordPage.find('input#confirmPassword').simulate(
+ 'change', { target: { value: 'password-mismatch', name: 'confirmPassword' } },
+ );
+ expect(resetPasswordPage.find('div[feedback-for="confirmPassword"]').text()).toEqual('Passwords do not match');
+ });
+
+ // ******** alert message tests ********
+
+ it('should show reset password rate limit error', () => {
+ store = mockStore({
+ resetPassword: {
+ status: PASSWORD_RESET.FORBIDDEN_REQUEST,
+ },
+ });
const resetPasswordPage = mount(reduxWrapper(
));
- expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
-
- await act(async () => {
- await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: '' } });
- });
- // On blur event, the banner message remains same
- expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
- expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
+ expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(
+ 'Too many requests.An error has occurred because of too many requests. Please try again after some time.',
+ );
});
+ it('should show reset password internal server error', () => {
+ store = mockStore({
+ resetPassword: {
+ status: PASSWORD_RESET.INTERNAL_SERVER_ERROR,
+ },
+ });
+
+ const resetPasswordPage = mount(reduxWrapper(
));
+ expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(
+ 'We couldn\'t reset your password.An error has occurred. Try refreshing the page, or check your internet connection.',
+ );
+ });
+
+ // ******** miscellaneous tests ********
+
it('check cookie rendered', () => {
const resetPasswordPage = mount(reduxWrapper(
));
expect(resetPasswordPage.find(
)).toBeTruthy();
});
- it('should display error banner on server error', () => {
- const bannerMessage = 'Failed to reset passwordAn error has occurred. Try refreshing the page, or check your internet connection.';
- props = {
- ...props,
- status: 'failure',
- errors: INTERNAL_SERVER_ERROR,
- };
-
+ it('show spinner during token validation', () => {
const resetPasswordPage = mount(reduxWrapper(
));
- resetPasswordPage.find('button.btn-primary').simulate('click');
-
- resetPasswordPage.update();
- expect(resetPasswordPage.find('#internal-server-error').first().text()).toEqual(bannerMessage);
- });
-
- it('check api failure banner rendered', () => {
- props = {
- ...props,
- status: 'invalid',
- errors: INTERNAL_SERVER_ERROR,
- };
- const resetPasswordPage = mount(reduxWrapper(
));
- expect(resetPasswordPage.find(
)).toBeTruthy();
- });
-
- it('check failure banner rendered on validate token api ratelimit', () => {
- props = {
- ...props,
- status: 'invalid',
- errors: API_RATELIMIT_ERROR,
- };
- const resetPasswordPage = mount(reduxWrapper(
));
- expect(resetPasswordPage.find(
)).toBeTruthy();
- });
-
- it('check failure banner rendered on reset password api ratelimit', () => {
- props = {
- ...props,
- status: 'failure',
- errors: API_RATELIMIT_ERROR,
- };
- const resetPasswordPage = mount(reduxWrapper(
));
- expect(resetPasswordPage.find(
)).toBeTruthy();
+ expect(resetPasswordPage.find('div.spinner-header')).toBeTruthy();
});
});
diff --git a/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap b/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap
deleted file mode 100644
index a24622c0..00000000
--- a/src/reset-password/tests/__snapshots__/ResetPasswordPage.test.jsx.snap
+++ /dev/null
@@ -1,464 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ResetPasswordPage should match invalid token message section snapshot 1`] = `
-
-`;
-
-exports[`ResetPasswordPage should match pending reset message section snapshot 1`] = `
-
-`;
-
-exports[`ResetPasswordPage should match reset password default section snapshot 1`] = `
-
-`;
-
-exports[`ResetPasswordPage should match successful reset message section snapshot 1`] = `
-
-`;
-
-exports[`ResetPasswordPage show spinner component during token validation 1`] = `
-
-`;