fix: Persist Register/Sign in form states on tab switch [VAN-618]

This commit is contained in:
Syed Sajjad Hussain Shah
2022-05-20 10:15:15 +05:00
parent b04257a62d
commit 4bf0021982
20 changed files with 661 additions and 79 deletions

View File

@@ -19,7 +19,7 @@ import {
} from '@edx/paragon'; } from '@edx/paragon';
import { ChevronLeft } from '@edx/paragon/icons'; import { ChevronLeft } from '@edx/paragon/icons';
import { forgotPassword } from './data/actions'; import { forgotPassword, setForgotPasswordFormData } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors'; import { forgotPasswordResultSelector } from './data/selectors';
import messages from './messages'; import messages from './messages';
@@ -34,7 +34,7 @@ const ForgotPasswordPage = (props) => {
const platformName = getConfig().SITE_NAME; const platformName = getConfig().SITE_NAME;
const regex = new RegExp(VALID_EMAIL_REGEX, 'i'); const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
const [validationError, setValidationError] = useState(''); const [validationError, setValidationError] = useState(props.emailValidationError || '');
const [key, setKey] = useState(''); const [key, setKey] = useState('');
const supportUrl = getConfig().LOGIN_ISSUE_SUPPORT_LINK; const supportUrl = getConfig().LOGIN_ISSUE_SUPPORT_LINK;
@@ -56,6 +56,11 @@ const ForgotPasswordPage = (props) => {
return error; return error;
}; };
const onBlur = (email) => {
const emailValidationError = getValidationMessage(email);
props.setForgotPasswordFormData({ email, emailValidationError });
};
const tabTitle = ( const tabTitle = (
<div className="d-flex"> <div className="d-flex">
<Icon src={ChevronLeft} className="arrow-back-icon" /> <Icon src={ChevronLeft} className="arrow-back-icon" />
@@ -74,7 +79,7 @@ const ForgotPasswordPage = (props) => {
)} )}
<div id="main-content" className="main-content"> <div id="main-content" className="main-content">
<Formik <Formik
initialValues={{ email: '' }} initialValues={{ email: props.email || '' }}
validateOnChange={false} validateOnChange={false}
validate={(values) => { validate={(values) => {
const validationMessage = getValidationMessage(values.email); const validationMessage = getValidationMessage(values.email);
@@ -110,7 +115,7 @@ const ForgotPasswordPage = (props) => {
name="email" name="email"
errorMessage={validationError} errorMessage={validationError}
value={values.email} value={values.email}
handleBlur={() => getValidationMessage(values.email)} handleBlur={() => onBlur(values.email)}
handleChange={e => setFieldValue('email', e.target.value)} handleChange={e => setFieldValue('email', e.target.value)}
handleFocus={() => setValidationError('')} handleFocus={() => setValidationError('')}
helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]} helpText={[intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })]}
@@ -156,13 +161,16 @@ const ForgotPasswordPage = (props) => {
ForgotPasswordPage.propTypes = { ForgotPasswordPage.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired,
email: PropTypes.string, email: PropTypes.string,
emailValidationError: PropTypes.string,
forgotPassword: PropTypes.func.isRequired, forgotPassword: PropTypes.func.isRequired,
setForgotPasswordFormData: PropTypes.func.isRequired,
status: PropTypes.string, status: PropTypes.string,
submitState: PropTypes.string, submitState: PropTypes.string,
}; };
ForgotPasswordPage.defaultProps = { ForgotPasswordPage.defaultProps = {
email: '', email: '',
emailValidationError: '',
status: null, status: null,
submitState: DEFAULT_STATE, submitState: DEFAULT_STATE,
}; };
@@ -171,5 +179,6 @@ export default connect(
forgotPasswordResultSelector, forgotPasswordResultSelector,
{ {
forgotPassword, forgotPassword,
setForgotPasswordFormData,
}, },
)(injectIntl(ForgotPasswordPage)); )(injectIntl(ForgotPasswordPage));

View File

@@ -1,6 +1,7 @@
import { AsyncActionType } from '../../data/utils'; import { AsyncActionType } from '../../data/utils';
export const FORGOT_PASSWORD = new AsyncActionType('FORGOT', 'PASSWORD'); export const FORGOT_PASSWORD = new AsyncActionType('FORGOT', 'PASSWORD');
export const FORGOT_PASSWORD_PERSIST_FORM_DATA = 'FORGOT_PASSWORD_PERSIST_FORM_DATA';
// Forgot Password // Forgot Password
export const forgotPassword = email => ({ export const forgotPassword = email => ({
@@ -24,3 +25,8 @@ export const forgotPasswordForbidden = () => ({
export const forgotPasswordServerError = () => ({ export const forgotPasswordServerError = () => ({
type: FORGOT_PASSWORD.FAILURE, type: FORGOT_PASSWORD.FAILURE,
}); });
export const setForgotPasswordFormData = (forgotPasswordFormData) => ({
type: FORGOT_PASSWORD_PERSIST_FORM_DATA,
payload: { forgotPasswordFormData },
});

View File

@@ -1,4 +1,4 @@
import { FORGOT_PASSWORD } from './actions'; import { FORGOT_PASSWORD, FORGOT_PASSWORD_PERSIST_FORM_DATA } from './actions';
import { INTERNAL_SERVER_ERROR, PENDING_STATE } from '../../data/constants'; import { INTERNAL_SERVER_ERROR, PENDING_STATE } from '../../data/constants';
import { PASSWORD_RESET_FAILURE } from '../../reset-password/data/actions'; import { PASSWORD_RESET_FAILURE } from '../../reset-password/data/actions';
@@ -6,6 +6,7 @@ export const defaultState = {
status: '', status: '',
submitState: '', submitState: '',
email: '', email: '',
emailValidationError: '',
}; };
const reducer = (state = defaultState, action = null) => { const reducer = (state = defaultState, action = null) => {
@@ -33,8 +34,20 @@ const reducer = (state = defaultState, action = null) => {
return { return {
status: action.payload.errorCode, status: action.payload.errorCode,
}; };
case FORGOT_PASSWORD_PERSIST_FORM_DATA: {
const { forgotPasswordFormData } = action.payload;
return {
...state,
email: forgotPasswordFormData.email,
emailValidationError: forgotPasswordFormData.emailValidationError,
};
}
default: default:
return defaultState; return {
...defaultState,
email: state.email,
emailValidationError: state.emailValidationError,
};
} }
} }
return state; return state;

View File

@@ -0,0 +1,34 @@
import reducer from '../reducers';
import {
FORGOT_PASSWORD_PERSIST_FORM_DATA,
} from '../actions';
describe('forgot password reducer', () => {
it('should set email and emailValidationError', () => {
const state = {
status: '',
submitState: '',
email: '',
emailValidationError: '',
};
const forgotPasswordFormData = {
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
};
const action = {
type: FORGOT_PASSWORD_PERSIST_FORM_DATA,
payload: { forgotPasswordFormData },
};
expect(
reducer(state, action),
).toEqual(
{
status: '',
submitState: '',
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
},
);
});
});

View File

@@ -10,7 +10,7 @@ const { loggingService } = initializeMockLogging();
describe('handleForgotPassword', () => { describe('handleForgotPassword', () => {
const params = { const params = {
payload: { payload: {
formData: { forgotPasswordFormData: {
email: 'test@test.com', email: 'test@test.com',
}, },
}, },

View File

@@ -16,6 +16,7 @@ import * as auth from '@edx/frontend-platform/auth';
import ForgotPasswordPage from '../ForgotPasswordPage'; import ForgotPasswordPage from '../ForgotPasswordPage';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants'; import { INTERNAL_SERVER_ERROR, LOGIN_PAGE } from '../../data/constants';
import { PASSWORD_RESET } from '../../reset-password/data/constants'; import { PASSWORD_RESET } from '../../reset-password/data/constants';
import { setForgotPasswordFormData } from '../data/actions';
jest.mock('@edx/frontend-platform/analytics'); jest.mock('@edx/frontend-platform/analytics');
jest.mock('@edx/frontend-platform/auth'); jest.mock('@edx/frontend-platform/auth');
@@ -197,4 +198,24 @@ describe('ForgotPasswordPage', () => {
forgotPasswordPage.update(); forgotPasswordPage.update();
expect(history.location.pathname).toEqual(LOGIN_PAGE); expect(history.location.pathname).toEqual(LOGIN_PAGE);
}); });
// *********** persists form data on going back to sign in page ***********
it('should set form data in redux store on onBlur', () => {
const forgotPasswordFormData = {
email: 'test@gmail',
emailValidationError: 'Enter a valid email address',
};
props = {
...props,
email: 'test@gmail',
emailValidationError: '',
};
store.dispatch = jest.fn(store.dispatch);
const wrapper = mount(reduxWrapper(<IntlForgotPasswordPage {...props} />));
wrapper.find('input#email').simulate('blur');
expect(store.dispatch).toHaveBeenCalledWith(setForgotPasswordFormData(forgotPasswordFormData));
});
}); });

View File

@@ -15,9 +15,11 @@ import {
import { Institution } from '@edx/paragon/icons'; import { Institution } from '@edx/paragon/icons';
import AccountActivationMessage from './AccountActivationMessage'; import AccountActivationMessage from './AccountActivationMessage';
import { loginRequest, loginRequestFailure, loginRequestReset } from './data/actions'; import {
loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
} from './data/actions';
import { INVALID_FORM } from './data/constants'; import { INVALID_FORM } from './data/constants';
import { loginErrorSelector, loginRequestSelector } from './data/selectors'; import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
import LoginFailureMessage from './LoginFailure'; import LoginFailureMessage from './LoginFailure';
import messages from './messages'; import messages from './messages';
@@ -40,20 +42,18 @@ import {
getAllPossibleQueryParam, getAllPossibleQueryParam,
updatePathWithQueryParams, updatePathWithQueryParams,
} from '../data/utils'; } from '../data/utils';
import { forgotPasswordResultSelector } from '../forgot-password';
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess'; import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
class LoginPage extends React.Component { class LoginPage extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
sendPageEvent('login_and_registration', 'login'); sendPageEvent('login_and_registration', 'login');
this.state = { this.state = {
password: '', password: this.props.loginFormData.password || '',
emailOrUsername: '', emailOrUsername: this.props.loginFormData.emailOrUsername || '',
errors: { errors: {
emailOrUsername: '', emailOrUsername: this.props.loginFormData.errors.emailOrUsername || '',
password: '', password: this.props.loginFormData.errors.password || '',
}, },
isSubmitted: false, isSubmitted: false,
}; };
@@ -78,12 +78,18 @@ class LoginPage extends React.Component {
handleSubmit = (e) => { handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
this.setState({ isSubmitted: true }); this.setState({ isSubmitted: true });
const { emailOrUsername, password } = this.state; const { emailOrUsername, password } = this.state;
const emailValidationError = this.validateEmail(emailOrUsername); const emailValidationError = this.validateEmail(emailOrUsername);
const passwordValidationError = this.validatePassword(password); const passwordValidationError = this.validatePassword(password);
if (emailValidationError !== '' || passwordValidationError !== '') { if (emailValidationError !== '' || passwordValidationError !== '') {
this.props.setLoginFormData({
...this.props.loginFormData,
errors: {
emailOrUsername: emailValidationError,
password: passwordValidationError,
},
});
this.props.loginRequestFailure({ this.props.loginRequestFailure({
errorCode: INVALID_FORM, errorCode: INVALID_FORM,
}); });
@@ -99,7 +105,18 @@ class LoginPage extends React.Component {
handleOnFocus = (e) => { handleOnFocus = (e) => {
const { errors } = this.state; const { errors } = this.state;
errors[e.target.name] = ''; errors[e.target.name] = '';
this.setState({ errors }); this.props.setLoginFormData({
...this.props.loginFormData,
errors,
});
}
handleOnBlur = (e) => {
const payload = {
...this.props.loginFormData,
[e.target.name]: e.target.value,
};
this.props.setLoginFormData(payload);
} }
handleForgotPasswordLinkClickEvent = () => { handleForgotPasswordLinkClickEvent = () => {
@@ -116,7 +133,6 @@ class LoginPage extends React.Component {
} else { } else {
errors.emailOrUsername = ''; errors.emailOrUsername = '';
} }
this.setState({ errors });
return errors.emailOrUsername; return errors.emailOrUsername;
} }
@@ -124,7 +140,6 @@ class LoginPage extends React.Component {
const { errors } = this.state; const { errors } = this.state;
errors.password = password.length > 0 ? '' : this.props.intl.formatMessage(messages['password.validation.message']); errors.password = password.length > 0 ? '' : this.props.intl.formatMessage(messages['password.validation.message']);
this.setState({ errors });
return errors.password; return errors.password;
} }
@@ -230,6 +245,7 @@ class LoginPage extends React.Component {
value={this.state.emailOrUsername} value={this.state.emailOrUsername}
handleChange={(e) => this.setState({ emailOrUsername: e.target.value, isSubmitted: false })} handleChange={(e) => this.setState({ emailOrUsername: e.target.value, isSubmitted: false })}
handleFocus={this.handleOnFocus} handleFocus={this.handleOnFocus}
handleBlur={this.handleOnBlur}
errorMessage={this.state.errors.emailOrUsername} errorMessage={this.state.errors.emailOrUsername}
floatingLabel={intl.formatMessage(messages['login.user.identity.label'])} floatingLabel={intl.formatMessage(messages['login.user.identity.label'])}
/> />
@@ -239,6 +255,7 @@ class LoginPage extends React.Component {
showRequirements={false} showRequirements={false}
handleChange={(e) => this.setState({ password: e.target.value, isSubmitted: false })} handleChange={(e) => this.setState({ password: e.target.value, isSubmitted: false })}
handleFocus={this.handleOnFocus} handleFocus={this.handleOnFocus}
handleBlur={this.handleOnBlur}
errorMessage={this.state.errors.password} errorMessage={this.state.errors.password}
floatingLabel={intl.formatMessage(messages['login.password.label'])} floatingLabel={intl.formatMessage(messages['login.password.label'])}
/> />
@@ -310,9 +327,16 @@ class LoginPage extends React.Component {
} }
LoginPage.defaultProps = { LoginPage.defaultProps = {
forgotPassword: null,
loginResult: null, loginResult: null,
loginError: null, loginError: null,
loginFormData: {
emailOrUsername: '',
password: '',
errors: {
emailOrUsername: '',
password: '',
},
},
resetPassword: false, resetPassword: false,
submitState: DEFAULT_STATE, submitState: DEFAULT_STATE,
thirdPartyAuthApiStatus: 'pending', thirdPartyAuthApiStatus: 'pending',
@@ -325,20 +349,25 @@ LoginPage.defaultProps = {
}; };
LoginPage.propTypes = { LoginPage.propTypes = {
forgotPassword: PropTypes.shape({
email: PropTypes.string,
status: PropTypes.string,
}),
getThirdPartyAuthContext: PropTypes.func.isRequired, getThirdPartyAuthContext: PropTypes.func.isRequired,
intl: intlShape.isRequired, intl: intlShape.isRequired,
loginError: PropTypes.objectOf(PropTypes.any), loginError: PropTypes.objectOf(PropTypes.any),
loginRequest: PropTypes.func.isRequired, loginRequest: PropTypes.func.isRequired,
loginRequestFailure: PropTypes.func.isRequired, loginRequestFailure: PropTypes.func.isRequired,
loginRequestReset: PropTypes.func.isRequired, loginRequestReset: PropTypes.func.isRequired,
setLoginFormData: PropTypes.func.isRequired,
loginResult: PropTypes.shape({ loginResult: PropTypes.shape({
redirectUrl: PropTypes.string, redirectUrl: PropTypes.string,
success: PropTypes.bool, success: PropTypes.bool,
}), }),
loginFormData: PropTypes.shape({
emailOrUsername: PropTypes.string,
password: PropTypes.string,
errors: PropTypes.shape({
emailOrUsername: PropTypes.string,
password: PropTypes.string,
}),
}),
resetPassword: PropTypes.bool, resetPassword: PropTypes.bool,
submitState: PropTypes.string, submitState: PropTypes.string,
thirdPartyAuthApiStatus: PropTypes.string, thirdPartyAuthApiStatus: PropTypes.string,
@@ -354,17 +383,17 @@ LoginPage.propTypes = {
}; };
const mapStateToProps = state => { const mapStateToProps = state => {
const forgotPassword = forgotPasswordResultSelector(state);
const loginResult = loginRequestSelector(state); const loginResult = loginRequestSelector(state);
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state); const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
const loginError = loginErrorSelector(state); const loginError = loginErrorSelector(state);
const loginFormData = loginFormDataSelector(state);
return { return {
submitState: state.login.submitState, submitState: state.login.submitState,
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus, thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
forgotPassword,
loginError, loginError,
loginResult, loginResult,
thirdPartyAuthContext, thirdPartyAuthContext,
loginFormData,
resetPassword: state.login.resetPassword, resetPassword: state.login.resetPassword,
}; };
}; };
@@ -376,5 +405,6 @@ export default connect(
loginRequest, loginRequest,
loginRequestFailure, loginRequestFailure,
loginRequestReset, loginRequestReset,
setLoginFormData,
}, },
)(injectIntl(LoginPage)); )(injectIntl(LoginPage));

View File

@@ -1,6 +1,7 @@
import { AsyncActionType } from '../../data/utils'; import { AsyncActionType } from '../../data/utils';
export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST'); export const LOGIN_REQUEST = new AsyncActionType('LOGIN', 'REQUEST');
export const LOGIN_PERSIST_FORM_DATA = 'LOGIN_PERSIST_FORM_DATA';
// Login // Login
export const loginRequest = creds => ({ export const loginRequest = creds => ({
@@ -25,3 +26,8 @@ export const loginRequestFailure = (loginError) => ({
export const loginRequestReset = () => ({ export const loginRequestReset = () => ({
type: LOGIN_REQUEST.RESET, type: LOGIN_REQUEST.RESET,
}); });
export const setLoginFormData = (loginFormData) => ({
type: LOGIN_PERSIST_FORM_DATA,
payload: { loginFormData },
});

View File

@@ -1,4 +1,4 @@
import { LOGIN_REQUEST } from './actions'; import { LOGIN_REQUEST, LOGIN_PERSIST_FORM_DATA } from './actions';
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants'; import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
import { RESET_PASSWORD } from '../../reset-password'; import { RESET_PASSWORD } from '../../reset-password';
@@ -7,6 +7,14 @@ export const defaultState = {
loginError: null, loginError: null,
loginResult: {}, loginResult: {},
resetPassword: false, resetPassword: false,
loginFormData: {
password: '',
emailOrUsername: '',
errors: {
emailOrUsername: '',
password: '',
},
},
}; };
const reducer = (state = defaultState, action) => { const reducer = (state = defaultState, action) => {
@@ -38,6 +46,13 @@ const reducer = (state = defaultState, action) => {
...state, ...state,
resetPassword: true, resetPassword: true,
}; };
case LOGIN_PERSIST_FORM_DATA: {
const { loginFormData } = action.payload;
return {
...state,
loginFormData,
};
}
default: default:
return { return {
...state, ...state,

View File

@@ -13,3 +13,8 @@ export const loginErrorSelector = createSelector(
loginSelector, loginSelector,
login => login.loginError, login => login.loginError,
); );
export const loginFormDataSelector = createSelector(
loginSelector,
login => login.loginFormData,
);

View File

@@ -0,0 +1,46 @@
import reducer from '../reducers';
import {
LOGIN_PERSIST_FORM_DATA,
} from '../actions';
describe('login reducer', () => {
it('should set registrationFormData', () => {
const state = {
loginFormData: {
password: '',
emailOrUsername: '',
errors: {
emailOrUsername: '',
password: '',
},
},
};
const loginFormData = {
password: 'johndoe',
emailOrUsername: 'john@gmail.com',
errors: {
emailOrUsername: '',
password: '',
},
};
const action = {
type: LOGIN_PERSIST_FORM_DATA,
payload: { loginFormData },
};
expect(
reducer(state, action),
).toEqual(
{
loginFormData: {
password: 'johndoe',
emailOrUsername: 'john@gmail.com',
errors: {
emailOrUsername: '',
password: '',
},
},
},
);
});
});

View File

@@ -13,7 +13,7 @@ const { loggingService } = initializeMockLogging();
describe('handleLoginRequest', () => { describe('handleLoginRequest', () => {
const params = { const params = {
payload: { payload: {
formData: { loginFormData: {
email: 'test@test.com', email: 'test@test.com',
password: 'test-password', password: 'test-password',
}, },

View File

@@ -12,7 +12,9 @@ import * as analytics from '@edx/frontend-platform/analytics';
import * as auth from '@edx/frontend-platform/auth'; import * as auth from '@edx/frontend-platform/auth';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import { loginRequest, loginRequestFailure, loginRequestReset } from '../data/actions'; import {
loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
} from '../data/actions';
import LoginFailureMessage from '../LoginFailure'; import LoginFailureMessage from '../LoginFailure';
import LoginPage from '../LoginPage'; import LoginPage from '../LoginPage';
@@ -35,6 +37,7 @@ describe('LoginPage', () => {
}); });
let props = {}; let props = {};
let store = {}; let store = {};
let loginFormData = {};
const reduxWrapper = children => ( const reduxWrapper = children => (
<IntlProvider locale="en"> <IntlProvider locale="en">
@@ -82,6 +85,14 @@ describe('LoginPage', () => {
handleInstitutionLogin: jest.fn(), handleInstitutionLogin: jest.fn(),
institutionLogin: false, institutionLogin: false,
}; };
loginFormData = {
emailOrUsername: '',
password: '',
errors: {
emailOrUsername: '',
password: '',
},
};
}); });
// ******** test login form submission ******** // ******** test login form submission ********
@@ -465,4 +476,42 @@ describe('LoginPage', () => {
expect(store.dispatch).toHaveBeenCalledWith(loginRequestReset()); expect(store.dispatch).toHaveBeenCalledWith(loginRequestReset());
expect(loginPage.state('errors')).toEqual(errorState); expect(loginPage.state('errors')).toEqual(errorState);
}); });
// persists form data tests
it('should set errors in redux store on submit form for invalid input', () => {
loginFormData = {
...loginFormData,
errors: {
emailOrUsername: 'Enter your username or email',
password: 'Enter your password',
},
};
store.dispatch = jest.fn(store.dispatch);
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#emailOrUsername').simulate('change', { target: { value: '' } });
loginPage.find('input#password').simulate('change', { target: { value: '' } });
loginPage.find('button.btn-brand').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData(loginFormData));
});
it('should set form data in redux store on onBlur', () => {
store.dispatch = jest.fn(store.dispatch);
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#emailOrUsername').simulate('blur');
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData(loginFormData));
});
it('should clear form field errors in redux store on onFocus', () => {
store.dispatch = jest.fn(store.dispatch);
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#emailOrUsername').simulate('focus');
expect(store.dispatch).toHaveBeenCalledWith(setLoginFormData(loginFormData));
});
}); });

View File

@@ -16,7 +16,7 @@ import {
import { Error, Close } from '@edx/paragon/icons'; import { Error, Close } from '@edx/paragon/icons';
import FormFieldRenderer from '../field-renderer'; import FormFieldRenderer from '../field-renderer';
import { import {
clearUsernameSuggestions, registerNewUser, resetRegistrationForm, fetchRealtimeValidations, clearUsernameSuggestions, registerNewUser, resetRegistrationForm, fetchRealtimeValidations, setRegistrationFormData,
} from './data/actions'; } from './data/actions';
import { import {
FIELDS, FORM_SUBMISSION_ERROR, DEFAULT_SERVICE_PROVIDER_DOMAINS, DEFAULT_TOP_LEVEL_DOMAINS, COMMON_EMAIL_PROVIDERS, FIELDS, FORM_SUBMISSION_ERROR, DEFAULT_SERVICE_PROVIDER_DOMAINS, DEFAULT_TOP_LEVEL_DOMAINS, COMMON_EMAIL_PROVIDERS,
@@ -26,6 +26,7 @@ import {
registrationRequestSelector, registrationRequestSelector,
validationsSelector, validationsSelector,
usernameSuggestionsSelector, usernameSuggestionsSelector,
registrationFormDataSelector,
} from './data/selectors'; } from './data/selectors';
import messages from './messages'; import messages from './messages';
import RegistrationFailure from './RegistrationFailure'; import RegistrationFailure from './RegistrationFailure';
@@ -66,20 +67,21 @@ class RegistrationPage extends React.Component {
this.tpaHint = getTpaHint(); this.tpaHint = getTpaHint();
this.state = { this.state = {
country: '', country: '',
email: '', email: this.props.registrationFormData.email || '',
name: '', name: this.props.registrationFormData.name || '',
password: '', password: this.props.registrationFormData.password || '',
username: '', username: this.props.registrationFormData.username || '',
marketingOptIn: true, marketingOptIn: this.props.registrationFormData.marketingOptIn || true,
errors: { errors: {
email: '', email: this.props.registrationFormData.errors.email || '',
name: '', name: this.props.registrationFormData.errors.name || '',
username: '', username: this.props.registrationFormData.errors.username || '',
password: '', password: this.props.registrationFormData.errors.password || '',
country: '', country: '',
}, },
emailErrorSuggestion: null, emailFieldBorderClass: this.props.registrationFormData.emailFieldBorderClass || '',
emailWarningSuggestion: null, emailErrorSuggestion: this.props.registrationFormData.emailErrorSuggestion || null,
emailWarningSuggestion: this.props.registrationFormData.emailWarningSuggestion || null,
errorCode: null, errorCode: null,
failureCount: 0, failureCount: 0,
startTime: Date.now(), startTime: Date.now(),
@@ -119,6 +121,12 @@ class RegistrationPage extends React.Component {
}); });
return false; return false;
} }
if (this.props.registrationFormData !== nextProps.registrationFormData) {
const state = { ...this.state, ...nextProps.registrationFormData };
this.setState({
...state,
});
}
if (this.props.validationDecisions !== nextProps.validationDecisions) { if (this.props.validationDecisions !== nextProps.validationDecisions) {
const state = { errors: { ...this.state.errors, ...nextProps.validationDecisions } }; const state = { errors: { ...this.state.errors, ...nextProps.validationDecisions } };
let validatePassword = false; let validatePassword = false;
@@ -287,24 +295,27 @@ class RegistrationPage extends React.Component {
handleOnFocus = (e) => { handleOnFocus = (e) => {
const { errors } = this.state; const { errors } = this.state;
errors[e.target.name] = ''; errors[e.target.name] = '';
const state = { errors };
if (e.target.name === 'username') { if (e.target.name === 'username') {
this.props.clearUsernameSuggestions(); this.props.clearUsernameSuggestions();
} }
if (e.target.name === 'country') { if (e.target.name === 'country') {
state.readOnly = false; this.setState({ readOnly: false });
} }
if (e.target.name === 'passwordValidation') { if (e.target.name === 'passwordValidation') {
state.errors.password = ''; errors.password = '';
} }
this.setState({ ...state }); this.props.setRegistrationFormData({
...this.props.registrationFormData,
errors,
});
} }
handleSuggestionClick = (e, suggestion) => { handleSuggestionClick = (e, suggestion) => {
const { errors } = this.state; const { errors } = this.state;
if (e.target.name === 'username') { if (e.target.name === 'username') {
errors.username = ''; errors.username = '';
this.setState({ this.props.setRegistrationFormData({
...this.props.registrationFormData,
username: suggestion, username: suggestion,
errors, errors,
}); });
@@ -312,18 +323,20 @@ class RegistrationPage extends React.Component {
} else if (e.target.name === 'email') { } else if (e.target.name === 'email') {
e.preventDefault(); e.preventDefault();
errors.email = ''; errors.email = '';
this.setState({ this.props.setRegistrationFormData({
borderClass: '', ...this.props.registrationFormData,
email: suggestion, email: suggestion,
emailErrorSuggestion: null, emailErrorSuggestion: null,
emailWarningSuggestion: null, emailWarningSuggestion: null,
emailFieldBorderClass: '',
errors, errors,
}); });
} }
} }
handleUsernameSuggestionClose = () => { handleUsernameSuggestionClose = () => {
this.setState({ this.props.setRegistrationFormData({
...this.props.registrationFormData,
username: '', username: '',
}); });
this.props.clearUsernameSuggestions(); this.props.clearUsernameSuggestions();
@@ -352,11 +365,15 @@ class RegistrationPage extends React.Component {
} }
}); });
this.setState({ errors }); this.props.setRegistrationFormData({
...this.props.registrationFormData,
errors,
});
return isValid; return isValid;
} }
validateInput(fieldName, value, payload) { validateInput(fieldName, value, payload) {
let state = {};
const { errors } = this.state; const { errors } = this.state;
const { intl, statusCode } = this.props; const { intl, statusCode } = this.props;
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i'); const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
@@ -409,11 +426,11 @@ class RegistrationPage extends React.Component {
errors.email = ''; errors.email = '';
} }
} }
this.setState({ state = {
emailWarningSuggestion, emailWarningSuggestion,
emailErrorSuggestion, emailErrorSuggestion,
borderClass: emailWarningSuggestion ? 'yellow-border' : null, emailFieldBorderClass: emailWarningSuggestion ? 'yellow-border' : null,
}); };
} }
break; break;
case 'name': case 'name':
@@ -467,12 +484,21 @@ class RegistrationPage extends React.Component {
break; break;
} }
this.setState({ errors }); this.props.setRegistrationFormData({
...this.props.registrationFormData,
...state,
[fieldName]: value,
errors,
});
return errors; return errors;
} }
handleOnClose() { handleOnClose() {
this.setState({ emailErrorSuggestion: null }); this.props.setRegistrationFormData({
...this.props.registrationFormData,
emailErrorSuggestion: null,
});
} }
renderEmailFeedback() { renderEmailFeedback() {
@@ -695,7 +721,7 @@ class RegistrationPage extends React.Component {
handleFocus={this.handleOnFocus} handleFocus={this.handleOnFocus}
helpText={[intl.formatMessage(messages['help.text.email'])]} helpText={[intl.formatMessage(messages['help.text.email'])]}
floatingLabel={intl.formatMessage(messages['registration.email.label'])} floatingLabel={intl.formatMessage(messages['registration.email.label'])}
borderClass={this.state.borderClass} borderClass={this.state.emailFieldBorderClass}
> >
{this.renderEmailFeedback()} {this.renderEmailFeedback()}
</FormGroup> </FormGroup>
@@ -845,6 +871,24 @@ RegistrationPage.defaultProps = {
secondaryProviders: [], secondaryProviders: [],
pipelineUserDetails: null, pipelineUserDetails: null,
}, },
registrationFormData: {
country: '',
email: '',
name: '',
password: '',
username: '',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailFieldBorderClass: '',
emailErrorSuggestion: null,
emailWarningSuggestion: null,
},
validationDecisions: null, validationDecisions: null,
statusCode: null, statusCode: null,
usernameSuggestions: [], usernameSuggestions: [],
@@ -857,6 +901,7 @@ RegistrationPage.propTypes = {
getThirdPartyAuthContext: PropTypes.func.isRequired, getThirdPartyAuthContext: PropTypes.func.isRequired,
registerNewUser: PropTypes.func, registerNewUser: PropTypes.func,
resetRegistrationForm: PropTypes.func.isRequired, resetRegistrationForm: PropTypes.func.isRequired,
setRegistrationFormData: PropTypes.func.isRequired,
registrationResult: PropTypes.shape({ registrationResult: PropTypes.shape({
redirectUrl: PropTypes.string, redirectUrl: PropTypes.string,
success: PropTypes.bool, success: PropTypes.bool,
@@ -864,6 +909,24 @@ RegistrationPage.propTypes = {
registrationErrorCode: PropTypes.string, registrationErrorCode: PropTypes.string,
submitState: PropTypes.string, submitState: PropTypes.string,
thirdPartyAuthApiStatus: PropTypes.string, thirdPartyAuthApiStatus: PropTypes.string,
registrationFormData: PropTypes.shape({
country: PropTypes.string,
email: PropTypes.string,
name: PropTypes.string,
password: PropTypes.string,
username: PropTypes.string,
marketingOptIn: PropTypes.bool,
errors: PropTypes.shape({
email: PropTypes.string,
name: PropTypes.string,
username: PropTypes.string,
password: PropTypes.string,
country: PropTypes.string,
}),
emailFieldBorderClass: PropTypes.string,
emailErrorSuggestion: PropTypes.string,
emailWarningSuggestion: PropTypes.string,
}),
thirdPartyAuthContext: PropTypes.shape({ thirdPartyAuthContext: PropTypes.shape({
currentProvider: PropTypes.string, currentProvider: PropTypes.string,
platformName: PropTypes.string, platformName: PropTypes.string,
@@ -906,6 +969,7 @@ const mapStateToProps = state => {
validationDecisions: validationsSelector(state), validationDecisions: validationsSelector(state),
statusCode: state.register.statusCode, statusCode: state.register.statusCode,
usernameSuggestions: usernameSuggestionsSelector(state), usernameSuggestions: usernameSuggestionsSelector(state),
registrationFormData: registrationFormDataSelector(state),
fieldDescriptions: fieldDescriptionSelector(state), fieldDescriptions: fieldDescriptionSelector(state),
extendedProfile: extendedProfileSelector(state), extendedProfile: extendedProfileSelector(state),
}; };
@@ -919,5 +983,6 @@ export default connect(
fetchRealtimeValidations, fetchRealtimeValidations,
registerNewUser, registerNewUser,
resetRegistrationForm, resetRegistrationForm,
setRegistrationFormData,
}, },
)(injectIntl(RegistrationPage)); )(injectIntl(RegistrationPage));

View File

@@ -4,6 +4,7 @@ export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_N
export const REGISTER_FORM_VALIDATIONS = new AsyncActionType('REGISTRATION', 'GET_FORM_VALIDATIONS'); export const REGISTER_FORM_VALIDATIONS = new AsyncActionType('REGISTRATION', 'GET_FORM_VALIDATIONS');
export const REGISTRATION_FORM = new AsyncActionType('REGISTRATION', 'REGISTRATION_FORM'); export const REGISTRATION_FORM = new AsyncActionType('REGISTRATION', 'REGISTRATION_FORM');
export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_SUGGESTIONS'; export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_SUGGESTIONS';
export const REGISTER_PERSIST_FORM_DATA = 'REGISTER_PERSIST_FORM_DATA';
// Reset Form // Reset Form
export const resetRegistrationForm = () => ({ export const resetRegistrationForm = () => ({
@@ -53,3 +54,8 @@ export const fetchRealtimeValidationsFailure = () => ({
export const clearUsernameSuggestions = () => ({ export const clearUsernameSuggestions = () => ({
type: REGISTER_CLEAR_USERNAME_SUGGESTIONS, type: REGISTER_CLEAR_USERNAME_SUGGESTIONS,
}); });
export const setRegistrationFormData = (registrationFormData) => ({
type: REGISTER_PERSIST_FORM_DATA,
payload: { registrationFormData },
});

View File

@@ -3,6 +3,7 @@ import {
REGISTER_NEW_USER, REGISTER_NEW_USER,
REGISTER_FORM_VALIDATIONS, REGISTER_FORM_VALIDATIONS,
REGISTER_CLEAR_USERNAME_SUGGESTIONS, REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_PERSIST_FORM_DATA,
} from './actions'; } from './actions';
import { import {
@@ -13,7 +14,24 @@ import {
export const defaultState = { export const defaultState = {
registrationError: {}, registrationError: {},
registrationResult: {}, registrationResult: {},
formData: null, registrationFormData: {
country: '',
email: '',
name: '',
password: '',
username: '',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailFieldBorderClass: '',
emailErrorSuggestion: null,
emailWarningSuggestion: null,
},
validations: null, validations: null,
statusCode: null, statusCode: null,
usernameSuggestions: [], usernameSuggestions: [],
@@ -27,6 +45,8 @@ const reducer = (state = defaultState, action) => {
case REGISTRATION_FORM.RESET: case REGISTRATION_FORM.RESET:
return { return {
...defaultState, ...defaultState,
registrationFormData: state.registrationFormData,
usernameSuggestions: state.usernameSuggestions,
}; };
case REGISTER_NEW_USER.BEGIN: case REGISTER_NEW_USER.BEGIN:
return { return {
@@ -72,6 +92,13 @@ const reducer = (state = defaultState, action) => {
...state, ...state,
usernameSuggestions: [], usernameSuggestions: [],
}; };
case REGISTER_PERSIST_FORM_DATA: {
const { registrationFormData } = action.payload;
return {
...state,
registrationFormData,
};
}
default: default:
return state; return state;
} }

View File

@@ -41,3 +41,8 @@ export const usernameSuggestionsSelector = createSelector(
registerSelector, registerSelector,
register => register.usernameSuggestions, register => register.usernameSuggestions,
); );
export const registrationFormDataSelector = createSelector(
registerSelector,
register => register.registrationFormData,
);

View File

@@ -1,6 +1,10 @@
import reducer from '../reducers'; import reducer from '../reducers';
import { import {
REGISTER_CLEAR_USERNAME_SUGGESTIONS, REGISTER_FORM_VALIDATIONS, REGISTER_NEW_USER, REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
REGISTER_PERSIST_FORM_DATA,
REGISTRATION_FORM,
} from '../actions'; } from '../actions';
import { DEFAULT_STATE } from '../../../data/constants'; import { DEFAULT_STATE } from '../../../data/constants';
@@ -10,7 +14,24 @@ describe('register reducer', () => {
{ {
registrationError: {}, registrationError: {},
registrationResult: {}, registrationResult: {},
formData: null, registrationFormData: {
country: '',
email: '',
name: '',
password: '',
username: '',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailFieldBorderClass: '',
emailErrorSuggestion: null,
emailWarningSuggestion: null,
},
validations: null, validations: null,
statusCode: null, statusCode: null,
usernameSuggestions: [], usernameSuggestions: [],
@@ -75,4 +96,93 @@ describe('register reducer', () => {
}, },
); );
}); });
it('should not reset username suggestions and form data in form reset', () => {
const state = {
registrationError: {},
registrationResult: {},
registrationFormData: {
country: 'Pakistan',
email: 'test@email.com',
name: 'John Doe',
password: 'johndoe',
username: 'john',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailErrorSuggestion: 'test@email.com',
emailWarningSuggestion: 'test@email.com',
},
validations: null,
statusCode: null,
extendedProfile: [],
fieldDescriptions: {},
formRenderState: DEFAULT_STATE,
usernameSuggestions: ['test1', 'test2'],
};
const action = {
type: REGISTRATION_FORM.RESET,
};
expect(
reducer(state, action),
).toEqual(
state,
);
});
it('should set registrationFormData', () => {
const state = {
registrationFormData: {
country: '',
email: '',
name: '',
password: '',
username: '',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailErrorSuggestion: null,
emailWarningSuggestion: null,
},
};
const registrationFormData = {
country: 'Pakistan',
email: 'test@email.com',
name: 'John Doe',
password: 'johndoe',
username: 'john',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailErrorSuggestion: 'test@email.com',
emailWarningSuggestion: 'test@email.com',
};
const action = {
type: REGISTER_PERSIST_FORM_DATA,
payload: { registrationFormData },
};
expect(
reducer(state, action),
).toEqual(
{
registrationFormData,
},
);
});
}); });

View File

@@ -15,7 +15,7 @@ const { loggingService } = initializeMockLogging();
describe('fetchRealtimeValidations', () => { describe('fetchRealtimeValidations', () => {
const params = { const params = {
payload: { payload: {
formData: { registrationFormData: {
email: 'test@test.com', email: 'test@test.com',
username: '', username: '',
password: 'test-password', password: 'test-password',
@@ -112,7 +112,7 @@ describe('fetchRealtimeValidations', () => {
describe('handleNewUserRegistration', () => { describe('handleNewUserRegistration', () => {
const params = { const params = {
payload: { payload: {
formData: { registrationFormData: {
email: 'test@test.com', email: 'test@test.com',
username: 'test-username', username: 'test-username',
password: 'test-password', password: 'test-password',

View File

@@ -13,7 +13,7 @@ import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n
import { import {
clearUsernameSuggestions, clearUsernameSuggestions,
fetchRealtimeValidations, fetchRealtimeValidations,
registerNewUser, registerNewUser, setRegistrationFormData,
resetRegistrationForm, resetRegistrationForm,
} from '../data/actions'; } from '../data/actions';
import { import {
@@ -43,6 +43,7 @@ describe('RegistrationPage', () => {
let props = {}; let props = {};
let store = {}; let store = {};
let registrationFormData = {};
const reduxWrapper = children => ( const reduxWrapper = children => (
<IntlProvider locale="en"> <IntlProvider locale="en">
@@ -86,6 +87,24 @@ describe('RegistrationPage', () => {
handleInstitutionLogin: jest.fn(), handleInstitutionLogin: jest.fn(),
institutionLogin: false, institutionLogin: false,
}; };
registrationFormData = {
country: '',
email: '',
name: '',
password: '',
username: '',
marketingOptIn: true,
errors: {
email: '',
name: '',
username: '',
password: '',
country: '',
},
emailFieldBorderClass: '',
emailErrorSuggestion: null,
emailWarningSuggestion: null,
};
}); });
afterEach(() => { afterEach(() => {
@@ -252,19 +271,6 @@ describe('RegistrationPage', () => {
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload)); expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload));
}); });
it('should validate the did you mean suggestions', () => {
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('blur', { target: { value: 'test@gmail.con', name: 'email' } });
expect(registrationPage.find('RegistrationPage').state('emailErrorSuggestion')).toEqual('test@gmail.com');
registrationPage.find('input#email').simulate('blur', { target: { value: 'test@fmail.com', name: 'email' } });
expect(registrationPage.find('RegistrationPage').state('emailWarningSuggestion')).toEqual('test@gmail.com');
registrationPage.find('input#email').simulate('blur', { target: { value: 'test@hotmail.com', name: 'email' } });
expect(registrationPage.find('RegistrationPage').state('emailWarningSuggestion')).toEqual(null);
});
it('should update props with validations returned by registration api', () => { it('should update props with validations returned by registration api', () => {
store = mockStore({ store = mockStore({
...initialState, ...initialState,
@@ -511,8 +517,6 @@ describe('RegistrationPage', () => {
registerPage.find('RegistrationPage').instance().shouldComponentUpdate(props); registerPage.find('RegistrationPage').instance().shouldComponentUpdate(props);
registerPage.find('RegistrationPage').setState({ errors: { username: 'It looks like this username is already taken' } }); registerPage.find('RegistrationPage').setState({ errors: { username: 'It looks like this username is already taken' } });
expect(registerPage.find('button.username-suggestion').length).toEqual(3); expect(registerPage.find('button.username-suggestion').length).toEqual(3);
registerPage.find('button.username-suggestion').at(0).simulate('click');
expect(registerPage.find('RegistrationPage').state('username')).toEqual('test_1');
}); });
it('should show username suggestions when full name is populated', () => { it('should show username suggestions when full name is populated', () => {
@@ -528,8 +532,6 @@ describe('RegistrationPage', () => {
registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } }); registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
expect(registerPage.find('button.username-suggestion').length).toEqual(3); expect(registerPage.find('button.username-suggestion').length).toEqual(3);
registerPage.find('button.username-suggestion').at(0).simulate('click');
expect(registerPage.find('RegistrationPage').state('username')).toEqual('testname');
}); });
it('should clear username suggestions when close icon is clicked', () => { it('should clear username suggestions when close icon is clicked', () => {
@@ -762,6 +764,30 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('RegistrationPage').state('errorCode')).toEqual(INTERNAL_SERVER_ERROR); expect(registrationPage.find('RegistrationPage').state('errorCode')).toEqual(INTERNAL_SERVER_ERROR);
}); });
it('should update form fields state if updated in redux store', () => {
const nextProps = {
thirdPartyAuthContext,
registrationFormData: {
name: 'John Doe',
username: 'john_doe',
email: 'john.doe@example.com',
password: 'password1',
country: 'Pakistan',
emailErrorSuggestion: 'test@gmail.com',
},
};
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('RegistrationPage').instance().shouldComponentUpdate(nextProps);
expect(registrationPage.find('RegistrationPage').state('name')).toEqual('John Doe');
expect(registrationPage.find('RegistrationPage').state('username')).toEqual('john_doe');
expect(registrationPage.find('RegistrationPage').state('email')).toEqual('john.doe@example.com');
expect(registrationPage.find('RegistrationPage').state('emailErrorSuggestion')).toEqual('test@gmail.com');
expect(registrationPage.find('RegistrationPage').state('password')).toEqual('password1');
expect(registrationPage.find('RegistrationPage').state('country')).toEqual('Pakistan');
});
it('should display opt-in/opt-out checkbox', () => { it('should display opt-in/opt-out checkbox', () => {
mergeConfig({ mergeConfig({
MARKETING_EMAILS_OPT_IN: 'true', MARKETING_EMAILS_OPT_IN: 'true',
@@ -774,6 +800,115 @@ describe('RegistrationPage', () => {
MARKETING_EMAILS_OPT_IN: '', MARKETING_EMAILS_OPT_IN: '',
}); });
}); });
// ******** persist state tests ********
it('should clear form field errors in redux store on onFocus', () => {
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#name').simulate('focus');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
it('should set username in redux store if usernameSuggestion is clicked', () => {
registrationFormData = {
...registrationFormData,
username: 'testname',
};
store = mockStore({
...initialState,
register: {
...initialState.register,
usernameSuggestions: ['testname', 't.name', 'test_0'],
},
});
store.dispatch = jest.fn(store.dispatch);
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
registerPage.find('button.username-suggestion').at(0).simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
it('should set email in redux store if emailSuggestion is clicked', () => {
registrationFormData = {
...registrationFormData,
email: 'test@gmail.com',
};
store = mockStore({
...initialState,
register: {
...initialState.register,
},
});
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('blur', { target: { value: 'test@gmail.con', name: 'email' } });
registrationPage.find('RegistrationPage').setState({ emailErrorSuggestion: 'test@gmail.com' });
registrationPage.find('.alert-link').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
it('should clear username in redux store if usernameSuggestion close button is clicked', () => {
store = mockStore({
...initialState,
register: {
...initialState.register,
usernameSuggestions: ['testname', 't.name', 'test_0'],
},
});
store.dispatch = jest.fn(store.dispatch);
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registerPage.find('input#name').simulate('change', { target: { value: 'test name', name: 'name' } });
registerPage.find('button.suggested-username-close-button').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
it('should clear emailErrorSuggestion in redux store if close button is clicked', () => {
registrationFormData = {
...registrationFormData,
email: '',
};
store = mockStore({
...initialState,
register: {
...initialState.register,
},
});
store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input#email').simulate('blur', { target: { value: 'test@gmail.con', name: 'email' } });
registrationPage.find('RegistrationPage').setState({ emailErrorSuggestion: 'test@gmail.com' });
registrationPage.find('.alert-close').at(0).simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
it('should set errors in redux store if form payload is invalid', () => {
registrationFormData = {
...registrationFormData,
errors: {
...registrationFormData.errors,
name: 'Enter your full name',
},
};
const payload = {
name: '',
username: 'john_doe',
email: 'john.doe@example.com',
password: 'password1',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
is_authn_mfe: true,
};
store.dispatch = jest.fn(store.dispatch);
const registerPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
populateRequiredFields(registerPage, payload);
registerPage.find('button.btn-brand').simulate('click');
expect(store.dispatch).toHaveBeenCalledWith(setRegistrationFormData(registrationFormData));
});
}); });
describe('TestDynamicFields', () => { describe('TestDynamicFields', () => {