Forgot password page redesigned
This commit is contained in:
3
.env
3
.env
@@ -16,7 +16,8 @@ SITE_NAME=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
AUTHN_MINIMAL_HEADER=true
|
||||
LOGIN_ISSUE_SUPPORT_LINK=null
|
||||
REGISTRATION_OPTIONAL_FIELDS=null
|
||||
REGISTRATION_OPTIONAL_FIELDS=''
|
||||
USER_SURVEY_COOKIE_NAME=null
|
||||
COOKIE_DOMAIN=null
|
||||
WELCOME_PAGE_SUPPORT_LINK=null
|
||||
INFO_EMAIL=''
|
||||
|
||||
@@ -27,3 +27,4 @@ REGISTRATION_OPTIONAL_FIELDS=''
|
||||
USER_SURVEY_COOKIE_NAME='openedx-user-survey-type'
|
||||
COOKIE_DOMAIN='localhost'
|
||||
WELCOME_PAGE_SUPPORT_LINK='http://localhost:1999/welcome'
|
||||
INFO_EMAIL='info@edx.org'
|
||||
|
||||
@@ -343,6 +343,11 @@ select.form-control {
|
||||
}
|
||||
|
||||
}
|
||||
#forgotpassword-success-alert {
|
||||
.alert-link {
|
||||
color: #454545 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.mw-500 {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const ConfirmationAlert = (props) => {
|
||||
const { email, intl } = props;
|
||||
|
||||
return (
|
||||
<Alert id="confirmation-alert" variant="success">
|
||||
<Alert.Heading>{intl.formatMessage(messages['forgot.password.confirmation.title'])}</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="forgot.password.confirmation.message"
|
||||
defaultMessage="You entered {strongEmail}. If this email address is associated with your
|
||||
{platformName} account, we will send a message with password recovery instructions to this email address."
|
||||
description="Forgot password confirmation message"
|
||||
values={{
|
||||
strongEmail: <strong className="data-hj-suppress">{email}</strong>,
|
||||
platformName: getConfig().SITE_NAME,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>{intl.formatMessage(messages['forgot.password.confirmation.info'])}</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="forgot.password.technical.support.help.message"
|
||||
defaultMessage="If you need further assistance, {technicalSupportLink}."
|
||||
description="Message to help user contact technical support"
|
||||
values={{
|
||||
technicalSupportLink: (
|
||||
<Alert.Link href={getConfig().PASSWORD_RESET_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['forgot.password.confirmation.support.link'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmationAlert.propTypes = {
|
||||
email: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ConfirmationAlert);
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import { mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import ConfirmationAlert from '../ConfirmationAlert';
|
||||
|
||||
const IntlConfirmationAlertMessage = injectIntl(ConfirmationAlert);
|
||||
|
||||
describe('ConfirmationAlert', () => {
|
||||
const supportLink = 'https://support.test.com/What-if-I-did-not-receive-a-password-reset-message';
|
||||
mergeConfig({
|
||||
PASSWORD_RESET_SUPPORT_LINK: supportLink,
|
||||
});
|
||||
|
||||
it('should match default confirmation message', () => {
|
||||
const confirmationAlertMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlConfirmationAlertMessage email="test@example.com" />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessage = 'Check your email'
|
||||
+ 'You entered test@example.com. If this email address is associated with your edX account, '
|
||||
+ 'we will send a message with password recovery instructions to this email address.'
|
||||
+ 'If you do not receive a password reset message after 1 minute, verify that you entered '
|
||||
+ 'the correct email address, or check your spam folder.'
|
||||
+ 'If you need further assistance, contact technical support.';
|
||||
|
||||
expect(confirmationAlertMessage.find('#confirmation-alert').first().text()).toEqual(expectedMessage);
|
||||
expect(confirmationAlertMessage.find('#confirmation-alert').find('a').props().href).toEqual(supportLink);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { Formik } from 'formik';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent } from '@edx/frontend-platform/analytics';
|
||||
@@ -13,37 +13,46 @@ import {
|
||||
Alert,
|
||||
Form,
|
||||
StatefulButton,
|
||||
Hyperlink,
|
||||
Icon,
|
||||
} from '@edx/paragon';
|
||||
import { Info } from '@edx/paragon/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSpinner, faChevronLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { forgotPassword } from './data/actions';
|
||||
import { forgotPasswordResultSelector } from './data/selectors';
|
||||
import RequestInProgressAlert from './RequestInProgressAlert';
|
||||
import SuccessAlert from './SuccessAlert';
|
||||
|
||||
import messages from './messages';
|
||||
import {
|
||||
AuthnValidationFormGroup,
|
||||
} from '../common-components';
|
||||
import { FormGroup } from '../common-components';
|
||||
import APIFailureMessage from '../common-components/APIFailureMessage';
|
||||
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
|
||||
import LoginHelpLinks from '../login/LoginHelpLinks';
|
||||
import { updatePathWithQueryParams, windowScrollTo } from '../data/utils';
|
||||
|
||||
const ForgotPasswordPage = (props) => {
|
||||
const { intl, status } = props;
|
||||
const { intl } = props;
|
||||
let { status } = props;
|
||||
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
||||
const [validationError, setValidationError] = useState('');
|
||||
|
||||
const getErrorMessage = (errors) => {
|
||||
const header = intl.formatMessage(messages['forgot.password.request.server.error']);
|
||||
const renderAlertMessages = (errors, email) => {
|
||||
const header = intl.formatMessage(messages['forgot.password.error.alert.title']);
|
||||
if (status === 'complete') {
|
||||
status = 'default';
|
||||
return (
|
||||
<SuccessAlert email={email} />
|
||||
);
|
||||
}
|
||||
if (errors.email) {
|
||||
return (
|
||||
<Alert variant="danger">
|
||||
<Icon src={Info} className="alert-icon" />
|
||||
<Alert.Heading>{header}</Alert.Heading>
|
||||
<ul><li>{errors.email}</li></ul>
|
||||
<p>{`${errors.email}${intl.formatMessage(messages['extend.field.errors'])}`}</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -69,74 +78,84 @@ const ForgotPasswordPage = (props) => {
|
||||
sendPageEvent('login_and_registration', 'reset');
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{ email: '' }}
|
||||
validateOnChange={false}
|
||||
validate={(values) => {
|
||||
const validationMessage = getValidationMessage(values.email);
|
||||
<div>
|
||||
<span className="nav nav-tabs">
|
||||
<Link className="nav-item nav-link" to={updatePathWithQueryParams(LOGIN_PAGE)}>
|
||||
<FontAwesomeIcon className="mr-2" icon={faChevronLeft} /> {intl.formatMessage(messages['sign.in.text'])}
|
||||
</Link>
|
||||
</span>
|
||||
<div id="main-content" className="main-content">
|
||||
<Formik
|
||||
initialValues={{ email: '' }}
|
||||
validateOnChange={false}
|
||||
validate={(values) => {
|
||||
const validationMessage = getValidationMessage(values.email);
|
||||
|
||||
if (validationMessage !== '') {
|
||||
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
return { email: validationMessage };
|
||||
}
|
||||
if (validationMessage !== '') {
|
||||
windowScrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
return { email: validationMessage };
|
||||
}
|
||||
|
||||
return {};
|
||||
}}
|
||||
onSubmit={(values) => { props.forgotPassword(values.email); }}
|
||||
>
|
||||
{({
|
||||
errors, handleSubmit, setFieldValue, values,
|
||||
}) => (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
{status === 'complete' ? <Redirect to={updatePathWithQueryParams(LOGIN_PAGE)} /> : null}
|
||||
<div className="d-flex justify-content-center m-4">
|
||||
<div className="d-flex flex-column">
|
||||
<Form className="mw-500">
|
||||
{ getErrorMessage(errors) }
|
||||
<h1 className="mt-3 h3">
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h1>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<AuthnValidationFormGroup
|
||||
label={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
for="forgot-password-input"
|
||||
name="email"
|
||||
type="email"
|
||||
invalid={validationError !== ''}
|
||||
ariaInvalid={validationError !== ''}
|
||||
invalidMessage={validationError}
|
||||
value={values.email}
|
||||
onBlur={() => getValidationMessage(values.email)}
|
||||
onChange={e => setFieldValue('email', e.target.value)}
|
||||
helpText={intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })}
|
||||
className="mb-0 w-100"
|
||||
inputFieldStyle="border-gray-600"
|
||||
/>
|
||||
<LoginHelpLinks page="forgot-password" />
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="btn-primary mt-3"
|
||||
state={status}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
}}
|
||||
icons={{ pending: <FontAwesomeIcon icon={faSpinner} spin /> }}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
return {};
|
||||
}}
|
||||
onSubmit={(values) => { props.forgotPassword(values.email); }}
|
||||
>
|
||||
{({
|
||||
errors, handleSubmit, setFieldValue, values,
|
||||
}) => (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['forgot.password.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div className="d-flex justify-content-center">
|
||||
<div className="d-flex flex-column">
|
||||
<Form className="mw-xs">
|
||||
{ renderAlertMessages(errors, values.email) }
|
||||
<h3>
|
||||
{intl.formatMessage(messages['forgot.password.page.heading'])}
|
||||
</h3>
|
||||
<p className="mb-4">
|
||||
{intl.formatMessage(messages['forgot.password.page.instructions'])}
|
||||
</p>
|
||||
<FormGroup
|
||||
floatingLabel={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
|
||||
name="email"
|
||||
errorMessage={validationError}
|
||||
value={values.email}
|
||||
handleBlur={() => getValidationMessage(values.email)}
|
||||
handleChange={e => setFieldValue('email', e.target.value)}
|
||||
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
|
||||
/>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="login-button-width"
|
||||
state={status}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['forgot.password.page.submit.button']),
|
||||
}}
|
||||
icons={{ pending: <FontAwesomeIcon icon={faSpinner} spin /> }}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
<Hyperlink id="forgot-password" className="btn btn-link font-weight-500 text-body" destination={getConfig().LOGIN_ISSUE_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['need.help.sign.in.text'])}
|
||||
</Hyperlink>
|
||||
<p className="mt-5">{intl.formatMessage(
|
||||
messages['additional.help.text'],
|
||||
{ infoEmail: process.env.INFO_EMAIL },
|
||||
)}
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
44
src/forgot-password/SuccessAlert.jsx
Normal file
44
src/forgot-password/SuccessAlert.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Alert, Icon } from '@edx/paragon';
|
||||
import { CheckCircle } from '@edx/paragon/icons';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const SuccessAlert = (props) => {
|
||||
const { email, intl } = props;
|
||||
|
||||
return (
|
||||
<Alert id="forgotpassword-success-alert" variant="success">
|
||||
<Icon src={CheckCircle} className="alert-icon" />
|
||||
<Alert.Heading>{intl.formatMessage(messages['confirmation.message.title'])}</Alert.Heading>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="forgot.password.confirmation.message"
|
||||
defaultMessage="We sent an email to {email} with instructions to reset your password.
|
||||
If you do not receive a password reset message after 1 minute, verify that you entered
|
||||
the correct email address, or check your spam folder. If you need further assistance, {supportLink}."
|
||||
description="Forgot password confirmation message"
|
||||
values={{
|
||||
email: <span className="data-hj-suppress">{email}</span>,
|
||||
supportLink: (
|
||||
<Alert.Link className="alert-link" href={getConfig().PASSWORD_RESET_SUPPORT_LINK}>
|
||||
{intl.formatMessage(messages['confirmation.support.link'])}
|
||||
</Alert.Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
SuccessAlert.propTypes = {
|
||||
email: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SuccessAlert);
|
||||
@@ -8,19 +8,20 @@ const messages = defineMessages({
|
||||
},
|
||||
'forgot.password.page.heading': {
|
||||
id: 'forgot.password.page.heading',
|
||||
defaultMessage: 'Password assistance',
|
||||
defaultMessage: 'Reset password',
|
||||
description: 'The page heading for the forgot password page.',
|
||||
},
|
||||
'forgot.password.page.instructions': {
|
||||
id: 'forgot.password.page.instructions',
|
||||
defaultMessage: 'Please enter your log-in or recovery email address below and we will send you an email with instructions.',
|
||||
defaultMessage: 'Please enter your email address below and we will send you an email with instructions on how to reset your password.',
|
||||
description: 'Instructions message for forgot password page.',
|
||||
},
|
||||
'forgot.password.page.invalid.email.message': {
|
||||
id: 'forgot.password.page.invalid.email.message',
|
||||
defaultMessage: "The email address you've provided isn't formatted correctly.",
|
||||
description: 'Invalid email address message for the forgot password page.',
|
||||
defaultMessage: 'Enter a valid email address',
|
||||
description: 'Invalid email address message for input field.',
|
||||
},
|
||||
|
||||
'forgot.password.page.email.field.label': {
|
||||
id: 'forgot.password.page.email.field.label',
|
||||
defaultMessage: 'Email',
|
||||
@@ -28,12 +29,12 @@ const messages = defineMessages({
|
||||
},
|
||||
'forgot.password.page.submit.button': {
|
||||
id: 'forgot.password.page.submit.button',
|
||||
defaultMessage: 'Recover my password',
|
||||
defaultMessage: 'Submit',
|
||||
description: 'Submit button text for the forgot password page.',
|
||||
},
|
||||
'forgot.password.request.server.error': {
|
||||
id: 'forgot.password.request.server.error',
|
||||
defaultMessage: 'We couldn’t send the password recovery email.',
|
||||
'forgot.password.error.alert.title': {
|
||||
id: 'forgot.password.error.alert.title.',
|
||||
defaultMessage: 'We were unable to contact you.',
|
||||
description: 'Failed to send password recovery email.',
|
||||
},
|
||||
'forgot.password.error.message.title': {
|
||||
@@ -48,7 +49,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'forgot.password.empty.email.field.error': {
|
||||
id: 'forgot.password.empty.email.field.error',
|
||||
defaultMessage: 'Please enter your email.',
|
||||
defaultMessage: 'Enter your email',
|
||||
description: 'Error message that appears when user tries to submit empty email field',
|
||||
},
|
||||
'forgot.password.invalid.email.heading': {
|
||||
@@ -66,5 +67,38 @@ const messages = defineMessages({
|
||||
defaultMessage: 'The email address you used to register with {platformName}',
|
||||
description: 'text help for the email',
|
||||
},
|
||||
// Confirmation Alert Message
|
||||
'confirmation.message.title': {
|
||||
id: 'confirmation.message.title',
|
||||
defaultMessage: 'Check your email',
|
||||
description: 'Forgot password confirmation message title',
|
||||
},
|
||||
'confirmation.support.link': {
|
||||
id: 'confirmation.support.link',
|
||||
defaultMessage: 'contact technical support',
|
||||
description: 'Technical support link text',
|
||||
},
|
||||
|
||||
'need.help.sign.in.text': {
|
||||
id: 'need.help.sign.in.text',
|
||||
defaultMessage: 'Need help signing in?',
|
||||
description: 'Sign in help link on forgot password page',
|
||||
},
|
||||
'additional.help.text': {
|
||||
id: 'additional.help.text',
|
||||
defaultMessage: 'For additional help, contact edX support at {infoEmail}',
|
||||
description: 'additional help text on forgot password page',
|
||||
},
|
||||
'sign.in.text': {
|
||||
id: 'sign.in.text',
|
||||
defaultMessage: 'Sign In',
|
||||
description: 'login page link on password page',
|
||||
},
|
||||
'extend.field.errors': {
|
||||
id: 'extend.field.errors',
|
||||
defaultMessage: ' below.',
|
||||
description: 'extends the field error for alert message',
|
||||
},
|
||||
|
||||
});
|
||||
export default messages;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { mount } from 'enzyme';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import CookiePolicyBanner from '@edx/frontend-component-cookie-policy-banner';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
@@ -19,7 +18,11 @@ analytics.sendPageEvent = jest.fn();
|
||||
|
||||
const IntlForgotPasswordPage = injectIntl(ForgotPasswordPage);
|
||||
const mockStore = configureStore();
|
||||
const history = createMemoryHistory();
|
||||
const initialState = {
|
||||
forgotPassword: {
|
||||
status: null,
|
||||
},
|
||||
};
|
||||
|
||||
describe('ForgotPasswordPage', () => {
|
||||
let props = {};
|
||||
@@ -27,12 +30,14 @@ describe('ForgotPasswordPage', () => {
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</MemoryRouter>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore();
|
||||
store = mockStore(initialState);
|
||||
props = {
|
||||
forgotPassword: jest.fn(),
|
||||
status: null,
|
||||
@@ -65,45 +70,29 @@ describe('ForgotPasswordPage', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match success section snapshot', () => {
|
||||
props = {
|
||||
...props,
|
||||
status: 'complete',
|
||||
};
|
||||
renderer.create(
|
||||
reduxWrapper(
|
||||
<Router history={history}>
|
||||
<IntlForgotPasswordPage {...props} />
|
||||
</Router>,
|
||||
),
|
||||
);
|
||||
expect(history.location.pathname).toEqual('/login');
|
||||
});
|
||||
|
||||
it('should display need other help signing in button', () => {
|
||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
expect(wrapper.find('button.field-link').first().text()).toEqual('Need other help signing in?');
|
||||
expect(wrapper.find('#forgot-password.btn-link').first().text()).toEqual('Need help signing in?');
|
||||
});
|
||||
|
||||
it('should display email validation error message', async () => {
|
||||
const validationMessage = "We couldn’t send the password recovery email.The email address you've provided isn't formatted correctly.";
|
||||
const validationMessage = 'We were unable to contact you.Enter a valid email address below.';
|
||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
wrapper.find('input#forgot-password-input').simulate(
|
||||
wrapper.find('input#email').simulate(
|
||||
'change', { target: { value: 'invalid-email', name: 'email' } },
|
||||
);
|
||||
await act(async () => { await wrapper.find('button.btn-primary').simulate('click'); });
|
||||
await act(async () => { await wrapper.find('button.btn-brand').simulate('click'); });
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.alert-danger').text()).toEqual(validationMessage);
|
||||
});
|
||||
|
||||
it('should show alert on server error', () => {
|
||||
props = {
|
||||
...props,
|
||||
status: INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
const expectedMessage = 'We couldn’t send the password recovery email.'
|
||||
store = mockStore({
|
||||
forgotPassword: { status: INTERNAL_SERVER_ERROR },
|
||||
});
|
||||
const expectedMessage = 'We were unable to contact you.'
|
||||
+ 'An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
@@ -111,10 +100,10 @@ describe('ForgotPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should display empty email validation message', async () => {
|
||||
const validationMessage = 'We couldn’t send the password recovery email.Please enter your email.';
|
||||
const validationMessage = 'We were unable to contact you.Enter your email below.';
|
||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
await act(async () => { await forgotPasswordPage.find('button.btn-primary').simulate('click'); });
|
||||
await act(async () => { await forgotPasswordPage.find('button.btn-brand').simulate('click'); });
|
||||
|
||||
forgotPasswordPage.update();
|
||||
expect(forgotPasswordPage.find('.alert-danger').text()).toEqual(validationMessage);
|
||||
@@ -133,7 +122,7 @@ describe('ForgotPasswordPage', () => {
|
||||
it('should not display any error message on change event', () => {
|
||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
|
||||
const emailInput = forgotPasswordPage.find('input#forgot-password-input');
|
||||
const emailInput = forgotPasswordPage.find('input#email');
|
||||
emailInput.simulate('change', { target: { value: 'invalid-email', name: 'email' } });
|
||||
forgotPasswordPage.update();
|
||||
|
||||
@@ -141,20 +130,35 @@ describe('ForgotPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should display error message on blur event', async () => {
|
||||
const validationMessage = 'Please enter your email.';
|
||||
const validationMessage = 'Enter your email';
|
||||
const forgotPasswordPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
const emailInput = forgotPasswordPage.find('input#forgot-password-input');
|
||||
const emailInput = forgotPasswordPage.find('input#email');
|
||||
|
||||
await act(async () => {
|
||||
await emailInput.simulate('blur', { target: { value: '', name: 'email' } });
|
||||
});
|
||||
|
||||
forgotPasswordPage.update();
|
||||
expect(forgotPasswordPage.find('#forgot-password-input-invalid-feedback').text()).toEqual(validationMessage);
|
||||
expect(forgotPasswordPage.find('.pgn__form-control-description-invalid').text()).toEqual(validationMessage);
|
||||
});
|
||||
|
||||
it('check cookie rendered', () => {
|
||||
const forgotPage = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
expect(forgotPage.find(<CookiePolicyBanner />)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display success message after email is sent', async () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
forgotPassword: {
|
||||
status: 'complete',
|
||||
},
|
||||
});
|
||||
const successMessage = 'Check your emailWe sent an email to with instructions to reset your password. If you do not '
|
||||
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address,'
|
||||
+ ' or check your spam folder. If you need further assistance, contact technical support.';
|
||||
|
||||
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
|
||||
expect(wrapper.find('.alert-success').text()).toEqual(successMessage);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ForgotPasswordPage should match default section snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex justify-content-center m-4"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<form
|
||||
className="mw-500"
|
||||
>
|
||||
<h1
|
||||
className="mt-3 h3"
|
||||
>
|
||||
Password assistance
|
||||
</h1>
|
||||
<p
|
||||
className="mb-4"
|
||||
>
|
||||
Please enter your log-in or recovery email address below and we will send you an email with instructions.
|
||||
</p>
|
||||
<div
|
||||
className="form-group mb-0 w-100"
|
||||
>
|
||||
<label
|
||||
className="pgn__form-label pt-10 focus-out"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-invalid={false}
|
||||
autoComplete="on"
|
||||
className="form-control border-gray-600"
|
||||
id="forgot-password-input"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<span />
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 field-link small"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-caret-right fa-w-6 mr-1"
|
||||
data-icon="caret-right"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 192 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Need other help signing in?
|
||||
</button>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled={false}
|
||||
aria-live="assertive"
|
||||
className="pgn__stateful-btn pgn__stateful-btn-state-null btn-primary mt-3 btn btn-primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<span>
|
||||
Recover my password
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ForgotPasswordPage should match forbidden section snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex justify-content-center m-4"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<form
|
||||
className="mw-500"
|
||||
>
|
||||
<div
|
||||
className="fade alert alert-danger show"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
className="alert-heading h4"
|
||||
>
|
||||
An error occurred.
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
Your previous request is in progress, please try again in a few moments.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1
|
||||
className="mt-3 h3"
|
||||
>
|
||||
Password assistance
|
||||
</h1>
|
||||
<p
|
||||
className="mb-4"
|
||||
>
|
||||
Please enter your log-in or recovery email address below and we will send you an email with instructions.
|
||||
</p>
|
||||
<div
|
||||
className="form-group mb-0 w-100"
|
||||
>
|
||||
<label
|
||||
className="pgn__form-label pt-10 focus-out"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-invalid={false}
|
||||
autoComplete="on"
|
||||
className="form-control border-gray-600"
|
||||
id="forgot-password-input"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<span />
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 field-link small"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-caret-right fa-w-6 mr-1"
|
||||
data-icon="caret-right"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 192 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Need other help signing in?
|
||||
</button>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled={false}
|
||||
aria-live="assertive"
|
||||
className="pgn__stateful-btn pgn__stateful-btn-state-forbidden btn-primary mt-3 btn btn-primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<span>
|
||||
Recover my password
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ForgotPasswordPage should match pending section snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex justify-content-center m-4"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column"
|
||||
>
|
||||
<form
|
||||
className="mw-500"
|
||||
>
|
||||
<h1
|
||||
className="mt-3 h3"
|
||||
>
|
||||
Password assistance
|
||||
</h1>
|
||||
<p
|
||||
className="mb-4"
|
||||
>
|
||||
Please enter your log-in or recovery email address below and we will send you an email with instructions.
|
||||
</p>
|
||||
<div
|
||||
className="form-group mb-0 w-100"
|
||||
>
|
||||
<label
|
||||
className="pgn__form-label pt-10 focus-out"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-invalid={false}
|
||||
autoComplete="on"
|
||||
className="form-control border-gray-600"
|
||||
id="forgot-password-input"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<span />
|
||||
</div>
|
||||
<button
|
||||
className="mt-2 field-link small"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-caret-right fa-w-6 mr-1"
|
||||
data-icon="caret-right"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 192 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Need other help signing in?
|
||||
</button>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled={true}
|
||||
aria-live="assertive"
|
||||
className="pgn__stateful-btn pgn__stateful-btn-state-pending btn-primary mt-3 disabled btn btn-primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<span
|
||||
className="pgn__stateful-btn-icon"
|
||||
>
|
||||
<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>
|
||||
</span>
|
||||
<span>
|
||||
Recover my password
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton,
|
||||
InstitutionLogistration, FormGroup, PasswordField,
|
||||
} from '../common-components';
|
||||
import ConfirmationAlert from '../common-components/ConfirmationAlert';
|
||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||
@@ -197,9 +196,6 @@ class LoginPage extends React.Component {
|
||||
{this.props.loginError ? <LoginFailureMessage loginError={this.props.loginError} /> : null}
|
||||
{submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
|
||||
{activationMsgType && <AccountActivationMessage messageType={activationMsgType} />}
|
||||
{this.props.forgotPassword.status === 'complete' && !this.props.loginError ? (
|
||||
<ConfirmationAlert email={this.props.forgotPassword.email} />
|
||||
) : null}
|
||||
<Form className="test">
|
||||
<FormGroup
|
||||
name="email"
|
||||
|
||||
@@ -259,22 +259,6 @@ describe('LoginPage', () => {
|
||||
expect(loginPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match forget password confirmation message', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
forgotPassword: { status: 'complete', email: 'test@example.com' },
|
||||
});
|
||||
|
||||
const confirmationMessage = 'Check your email'
|
||||
+ 'You entered test@example.com. If this email address is associated with your edX account, '
|
||||
+ 'we will send a message with password recovery instructions to this email address.If you do not '
|
||||
+ 'receive a password reset message after 1 minute, verify that you entered the correct email address, '
|
||||
+ 'or check your spam folder.If you need further assistance, contact technical support.';
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#confirmation-alert').first().text()).toEqual(confirmationMessage);
|
||||
});
|
||||
|
||||
it('should match invalid login form error message', () => {
|
||||
const errorMessage = 'Please fill in the fields below.';
|
||||
store = mockStore({
|
||||
|
||||
Reference in New Issue
Block a user