Compare commits
4 Commits
frontend-b
...
embeddable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a378462480 | ||
|
|
0e7e748bc4 | ||
|
|
68be7c65d5 | ||
|
|
80e940f44e |
@@ -24,7 +24,7 @@ import { ForgotPasswordPage } from './forgot-password';
|
||||
import Logistration from './logistration/Logistration';
|
||||
import { ProgressiveProfiling } from './progressive-profiling';
|
||||
import { RecommendationsPage } from './recommendations';
|
||||
import { RegistrationPage } from './register';
|
||||
import { EmbeddableRegistrationPage } from './register';
|
||||
import { ResetPasswordPage } from './reset-password';
|
||||
|
||||
import './index.scss';
|
||||
@@ -41,7 +41,7 @@ const MainApp = () => (
|
||||
<Route path="/" element={<Navigate replace to={updatePathWithQueryParams(REGISTER_PAGE)} />} />
|
||||
<Route
|
||||
path={REGISTER_EMBEDDED_PAGE}
|
||||
element={<EmbeddedRegistrationRoute><RegistrationPage /></EmbeddedRegistrationRoute>}
|
||||
element={<EmbeddedRegistrationRoute><EmbeddableRegistrationPage /></EmbeddedRegistrationRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={LOGIN_PAGE}
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS, REDIRECT,
|
||||
AUTHN_PROGRESSIVE_PROFILING, RECOMMENDATIONS,
|
||||
} from '../data/constants';
|
||||
import { setCookie } from '../data/utils';
|
||||
|
||||
@@ -18,8 +18,6 @@ const RedirectLogistration = (props) => {
|
||||
redirectToRecommendationsPage,
|
||||
educationLevel,
|
||||
userId,
|
||||
registrationEmbedded,
|
||||
host,
|
||||
} = props;
|
||||
let finalRedirectUrl = '';
|
||||
|
||||
@@ -39,13 +37,6 @@ const RedirectLogistration = (props) => {
|
||||
// TODO: Do we still need this cookie?
|
||||
setCookie('van-504-returning-user', true);
|
||||
|
||||
if (registrationEmbedded) {
|
||||
window.parent.postMessage({
|
||||
action: REDIRECT,
|
||||
redirectUrl: getConfig().POST_REGISTRATION_REDIRECT_URL,
|
||||
}, host);
|
||||
return null;
|
||||
}
|
||||
const registrationResult = { redirectUrl: finalRedirectUrl, success };
|
||||
return (
|
||||
<Navigate
|
||||
@@ -92,8 +83,6 @@ RedirectLogistration.defaultProps = {
|
||||
optionalFields: {},
|
||||
redirectToRecommendationsPage: false,
|
||||
userId: null,
|
||||
registrationEmbedded: false,
|
||||
host: '',
|
||||
};
|
||||
|
||||
RedirectLogistration.propTypes = {
|
||||
@@ -106,8 +95,6 @@ RedirectLogistration.propTypes = {
|
||||
optionalFields: PropTypes.shape({}),
|
||||
redirectToRecommendationsPage: PropTypes.bool,
|
||||
userId: PropTypes.number,
|
||||
registrationEmbedded: PropTypes.bool,
|
||||
host: PropTypes.string,
|
||||
};
|
||||
|
||||
export default RedirectLogistration;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Form, Spinner, StatefulButton } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
@@ -37,7 +36,7 @@ import {
|
||||
COMPLETE_STATE, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../data/constants';
|
||||
import {
|
||||
getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, setCookie,
|
||||
getAllPossibleQueryParams, getTpaHint, getTpaProvider, setCookie,
|
||||
} from '../data/utils';
|
||||
|
||||
/**
|
||||
@@ -47,7 +46,6 @@ const RegistrationPage = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const registrationEmbedded = isHostAvailableInQueryParams();
|
||||
const platformName = getConfig().SITE_NAME;
|
||||
const flags = {
|
||||
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
|
||||
@@ -99,13 +97,6 @@ const RegistrationPage = (props) => {
|
||||
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
|
||||
const [formStartTime, setFormStartTime] = useState(null);
|
||||
// temporary error state for embedded experience because we don't want to show errors on blur
|
||||
const [temporaryErrors, setTemporaryErrors] = useState({ ...backedUpFormData.errors });
|
||||
|
||||
const { cta, host } = queryParams;
|
||||
const buttonLabel = cta
|
||||
? formatMessage(messages['create.account.cta.button'], { label: cta })
|
||||
: formatMessage(messages['create.account.for.free.button']);
|
||||
|
||||
/**
|
||||
* Set the userPipelineDetails data in formFields for only first time
|
||||
@@ -156,13 +147,9 @@ const RegistrationPage = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (backendValidations) {
|
||||
if (registrationEmbedded) {
|
||||
setTemporaryErrors(prevErrors => ({ ...prevErrors, ...backendValidations }));
|
||||
} else {
|
||||
setErrors(prevErrors => ({ ...prevErrors, ...backendValidations }));
|
||||
}
|
||||
setErrors(prevErrors => ({ ...prevErrors, ...backendValidations }));
|
||||
}
|
||||
}, [backendValidations, registrationEmbedded]);
|
||||
}, [backendValidations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationErrorCode) {
|
||||
@@ -206,23 +193,10 @@ const RegistrationPage = (props) => {
|
||||
};
|
||||
|
||||
const handleErrorChange = (fieldName, error) => {
|
||||
if (registrationEmbedded) {
|
||||
setTemporaryErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
if (error === '' && errors[fieldName] !== '') {
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
}
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
};
|
||||
|
||||
const registerUser = () => {
|
||||
@@ -237,7 +211,7 @@ const RegistrationPage = (props) => {
|
||||
// Validating form data before submitting
|
||||
const { isValid, fieldErrors } = isFormValid(
|
||||
payload,
|
||||
registrationEmbedded ? temporaryErrors : errors,
|
||||
errors,
|
||||
configurableFormFields,
|
||||
fieldDescriptions,
|
||||
formatMessage,
|
||||
@@ -288,13 +262,11 @@ const RegistrationPage = (props) => {
|
||||
<title>{formatMessage(messages['register.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
||||
</Helmet>
|
||||
<RedirectLogistration
|
||||
host={host}
|
||||
authenticatedUser={registrationResult.authenticatedUser}
|
||||
success={registrationResult.success}
|
||||
redirectUrl={registrationResult.redirectUrl}
|
||||
finishAuthUrl={finishAuthUrl}
|
||||
optionalFields={optionalFields}
|
||||
registrationEmbedded={registrationEmbedded}
|
||||
redirectToProgressiveProfilingPage={
|
||||
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && !!Object.keys(optionalFields.fields).length
|
||||
}
|
||||
@@ -304,12 +276,7 @@ const RegistrationPage = (props) => {
|
||||
<Spinner animation="border" variant="primary" id="tpa-spinner" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
'mw-xs mt-3',
|
||||
{ 'w-100 m-auto pt-4 main-content': registrationEmbedded },
|
||||
)}
|
||||
>
|
||||
<div className="mw-xs mt-3">
|
||||
<ThirdPartyAuthAlert
|
||||
currentProvider={currentProvider}
|
||||
platformName={platformName}
|
||||
@@ -365,7 +332,7 @@ const RegistrationPage = (props) => {
|
||||
email={formFields.email}
|
||||
fieldErrors={errors}
|
||||
formFields={configurableFormFields}
|
||||
setFieldErrors={registrationEmbedded ? setTemporaryErrors : setErrors}
|
||||
setFieldErrors={setErrors}
|
||||
setFormFields={setConfigurableFormFields}
|
||||
autoSubmitRegisterForm={autoSubmitRegForm}
|
||||
fieldDescriptions={fieldDescriptions}
|
||||
@@ -378,21 +345,19 @@ const RegistrationPage = (props) => {
|
||||
className="register-button mt-4 mb-4"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: buttonLabel,
|
||||
default: formatMessage(messages['create.account.for.free.button']),
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
{!registrationEmbedded && (
|
||||
<ThirdPartyAuth
|
||||
currentProvider={currentProvider}
|
||||
providers={providers}
|
||||
secondaryProviders={secondaryProviders}
|
||||
handleInstitutionLogin={handleInstitutionLogin}
|
||||
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
||||
/>
|
||||
)}
|
||||
<ThirdPartyAuth
|
||||
currentProvider={currentProvider}
|
||||
providers={providers}
|
||||
secondaryProviders={secondaryProviders}
|
||||
handleInstitutionLogin={handleInstitutionLogin}
|
||||
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -503,14 +503,6 @@ describe('RegistrationPage', () => {
|
||||
expect(registrationPage.find('.institutions__heading').text()).toEqual('Register with institution/campus credentials');
|
||||
});
|
||||
|
||||
it('should show button label based on cta query params value', () => {
|
||||
const buttonLabel = 'Register';
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL, search: `?cta=${buttonLabel}` };
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('button[type="submit"] span').first().text()).toEqual(buttonLabel);
|
||||
});
|
||||
|
||||
it('should not display password field when current provider is present', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
@@ -892,96 +884,6 @@ describe('RegistrationPage', () => {
|
||||
expect(registrationPage.find('.email-suggestion-alert-warning').first().text()).toEqual('john.doe@hotmail.com');
|
||||
});
|
||||
|
||||
// ********* Embedded experience tests *********/
|
||||
|
||||
it('should call the postMessage API when embedded variant is rendered', () => {
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
mergeConfig({
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: true,
|
||||
});
|
||||
|
||||
window.parent.postMessage = jest.fn();
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING), search: '?host=http://localhost/host-website' };
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
optionalFields: {
|
||||
extended_profile: {},
|
||||
fields: {
|
||||
level_of_education: { name: 'level_of_education', error_message: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const progressiveProfilingPage = mount(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />,
|
||||
));
|
||||
progressiveProfilingPage.update();
|
||||
expect(window.parent.postMessage).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not display validations error on blur event when embedded variant is rendered', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
|
||||
registrationPage.find('input#username').simulate('blur', { target: { value: '', name: 'username' } });
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
|
||||
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
|
||||
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set errors in temporary state when validations are returned by registration api', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
|
||||
|
||||
const usernameError = 'It looks like this username is already taken';
|
||||
const emailError = 'This email is already associated with an existing or previous account';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
username: [{ userMessage: usernameError }],
|
||||
email: [{ userMessage: emailError }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlRegistrationPage {...props} />),
|
||||
)).find('RegistrationPage');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear error on focus for embedded experience also', () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(REGISTER_PAGE),
|
||||
search: '?host=http://localhost/host-website',
|
||||
};
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
|
||||
registrationPage.find('input#password').simulate('focus');
|
||||
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show spinner instead of form while registering if autoSubmitRegForm is true', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
|
||||
261
src/register/components/EmbeddableRegistrationPage.jsx
Normal file
261
src/register/components/EmbeddableRegistrationPage.jsx
Normal file
@@ -0,0 +1,261 @@
|
||||
import React, {
|
||||
useEffect, useMemo, useState,
|
||||
} from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Form, StatefulButton } from '@edx/paragon';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
|
||||
import RegistrationFailure from './RegistrationFailure';
|
||||
import { PasswordField } from '../../common-components';
|
||||
import { getThirdPartyAuthContext as getRegistrationDataFromBackend } from '../../common-components/data/actions';
|
||||
import { REDIRECT } from '../../data/constants';
|
||||
import { getAllPossibleQueryParams, setCookie } from '../../data/utils';
|
||||
import { clearRegistrationBackendError, registerNewUser } from '../data/actions';
|
||||
import { FORM_SUBMISSION_ERROR } from '../data/constants';
|
||||
import { getBackendValidations, isFormValid, prepareRegistrationPayload } from '../data/utils';
|
||||
import messages from '../messages';
|
||||
import { EmailField, NameField, UsernameField } from '../RegistrationFields';
|
||||
|
||||
/**
|
||||
* Main Registration Page component
|
||||
*/
|
||||
const EmbeddableRegistrationPage = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const flags = {
|
||||
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
|
||||
showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS,
|
||||
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
||||
};
|
||||
|
||||
const {
|
||||
registrationFormData: backedUpFormData,
|
||||
registrationError,
|
||||
registrationError: {
|
||||
errorCode: registrationErrorCode,
|
||||
} = {},
|
||||
registrationResult,
|
||||
submitState,
|
||||
validations,
|
||||
} = useSelector(state => state.register);
|
||||
|
||||
const { fieldDescriptions } = useSelector(state => state.commonComponents);
|
||||
|
||||
const backendValidations = useMemo(
|
||||
() => getBackendValidations(registrationError, validations), [registrationError, validations],
|
||||
);
|
||||
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
||||
|
||||
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
||||
const [configurableFormFields, setConfigurableFormFields] = useState(
|
||||
{ ...backedUpFormData.configurableFormFields },
|
||||
);
|
||||
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
|
||||
const [formStartTime, setFormStartTime] = useState(null);
|
||||
// temporary error state for embedded experience because we don't want to show errors on blur
|
||||
const [temporaryErrors, setTemporaryErrors] = useState({ ...backedUpFormData.errors });
|
||||
|
||||
const { cta, host } = queryParams;
|
||||
const buttonLabel = cta
|
||||
? formatMessage(messages['create.account.cta.button'], { label: cta })
|
||||
: formatMessage(messages['create.account.for.free.button']);
|
||||
|
||||
useEffect(() => {
|
||||
if (!formStartTime) {
|
||||
sendPageEvent('login_and_registration', 'register');
|
||||
const payload = { ...queryParams, is_register_page: true };
|
||||
dispatch(getRegistrationDataFromBackend(payload));
|
||||
setFormStartTime(Date.now());
|
||||
}
|
||||
}, [dispatch, formStartTime, queryParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (backendValidations) {
|
||||
setTemporaryErrors(prevErrors => ({ ...prevErrors, ...backendValidations }));
|
||||
}
|
||||
}, [backendValidations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationErrorCode) {
|
||||
setErrorCode(prevState => ({ type: registrationErrorCode, count: prevState.count + 1 }));
|
||||
}
|
||||
}, [registrationErrorCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationResult.success) {
|
||||
sendTrackEvent('edx.bi.user.account.registered.client', {});
|
||||
|
||||
// Optimizely registration conversion event
|
||||
window.optimizely = window.optimizely || [];
|
||||
window.optimizely.push({
|
||||
type: 'event',
|
||||
eventName: 'authn-registration-conversion',
|
||||
});
|
||||
|
||||
// We probably don't need this cookie because this fires the same event as
|
||||
// above for optimizely using GTM.
|
||||
setCookie(getConfig().REGISTER_CONVERSION_COOKIE_NAME, true);
|
||||
// This is used by the "User Retention Rate Event" on GTM
|
||||
setCookie('authn-returning-user');
|
||||
|
||||
// Fire GTM event used for integration with impact.com
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
event: 'ImpactRegistrationEvent',
|
||||
});
|
||||
|
||||
window.parent.postMessage({
|
||||
action: REDIRECT,
|
||||
redirectUrl: encodeURIComponent(getConfig().POST_REGISTRATION_REDIRECT_URL),
|
||||
}, host);
|
||||
}
|
||||
}, [host, registrationResult]);
|
||||
|
||||
const handleOnChange = (event) => {
|
||||
const { name } = event.target;
|
||||
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
|
||||
if (registrationError[name]) {
|
||||
dispatch(clearRegistrationBackendError(name));
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
}
|
||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||
};
|
||||
|
||||
const handleErrorChange = (fieldName, error) => {
|
||||
setTemporaryErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
if (error === '' && errors[fieldName] !== '') {
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
[fieldName]: error,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const registerUser = () => {
|
||||
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
|
||||
let payload = { ...formFields };
|
||||
|
||||
// Validating form data before submitting
|
||||
const { isValid, fieldErrors } = isFormValid(
|
||||
payload,
|
||||
temporaryErrors,
|
||||
configurableFormFields,
|
||||
fieldDescriptions,
|
||||
formatMessage,
|
||||
);
|
||||
setErrors({ ...fieldErrors });
|
||||
|
||||
// returning if not valid
|
||||
if (!isValid) {
|
||||
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Preparing payload for submission
|
||||
payload = prepareRegistrationPayload(
|
||||
payload,
|
||||
configurableFormFields,
|
||||
flags.showMarketingEmailOptInCheckbox,
|
||||
totalRegistrationTime,
|
||||
queryParams);
|
||||
|
||||
// making register call
|
||||
dispatch(registerNewUser(payload));
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
registerUser();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{formatMessage(messages['register.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
||||
</Helmet>
|
||||
<div
|
||||
className="mw-xs mt-3 w-100 m-auto pt-4 main-content"
|
||||
>
|
||||
<RegistrationFailure
|
||||
errorCode={errorCode.type}
|
||||
failureCount={errorCode.count}
|
||||
/>
|
||||
<Form id="registration-form" name="registration-form">
|
||||
<NameField
|
||||
name="name"
|
||||
value={formFields.name}
|
||||
shouldFetchUsernameSuggestions={!formFields.username.trim()}
|
||||
handleChange={handleOnChange}
|
||||
handleErrorChange={handleErrorChange}
|
||||
errorMessage={errors.name}
|
||||
helpText={[formatMessage(messages['help.text.name'])]}
|
||||
floatingLabel={formatMessage(messages['registration.fullname.label'])}
|
||||
/>
|
||||
<EmailField
|
||||
name="email"
|
||||
value={formFields.email}
|
||||
confirmEmailValue={configurableFormFields?.confirm_email}
|
||||
handleErrorChange={handleErrorChange}
|
||||
handleChange={handleOnChange}
|
||||
errorMessage={errors.email}
|
||||
helpText={[formatMessage(messages['help.text.email'])]}
|
||||
floatingLabel={formatMessage(messages['registration.email.label'])}
|
||||
/>
|
||||
<UsernameField
|
||||
name="username"
|
||||
spellCheck="false"
|
||||
value={formFields.username}
|
||||
handleChange={handleOnChange}
|
||||
handleErrorChange={handleErrorChange}
|
||||
errorMessage={errors.username}
|
||||
helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]}
|
||||
floatingLabel={formatMessage(messages['registration.username.label'])}
|
||||
/>
|
||||
<PasswordField
|
||||
name="password"
|
||||
value={formFields.password}
|
||||
handleChange={handleOnChange}
|
||||
handleErrorChange={handleErrorChange}
|
||||
errorMessage={errors.password}
|
||||
floatingLabel={formatMessage(messages['registration.password.label'])}
|
||||
/>
|
||||
<ConfigurableRegistrationForm
|
||||
email={formFields.email}
|
||||
fieldErrors={errors}
|
||||
formFields={configurableFormFields}
|
||||
setFieldErrors={setTemporaryErrors}
|
||||
setFormFields={setConfigurableFormFields}
|
||||
autoSubmitRegisterForm={false}
|
||||
fieldDescriptions={fieldDescriptions}
|
||||
/>
|
||||
<StatefulButton
|
||||
id="register-user"
|
||||
name="register-user"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
className="register-button mt-4 mb-4"
|
||||
state={submitState}
|
||||
labels={{
|
||||
default: buttonLabel,
|
||||
pending: '',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbeddableRegistrationPage;
|
||||
@@ -9,8 +9,8 @@ import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
|
||||
import { FIELDS } from '../data/constants';
|
||||
import { FIELDS } from '../../data/constants';
|
||||
import ConfigurableRegistrationForm from '../ConfigurableRegistrationForm';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
@@ -0,0 +1,672 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import {
|
||||
configure, getLocale, injectIntl, IntlProvider,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
AUTHN_PROGRESSIVE_PROFILING, PENDING_STATE, REGISTER_PAGE,
|
||||
} from '../../../data/constants';
|
||||
import {
|
||||
clearRegistrationBackendError,
|
||||
registerNewUser,
|
||||
} from '../../data/actions';
|
||||
import {
|
||||
FIELDS,
|
||||
FORBIDDEN_REQUEST,
|
||||
INTERNAL_SERVER_ERROR,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
TPA_SESSION_EXPIRED,
|
||||
} from '../../data/constants';
|
||||
import { EmbeddableRegistrationPage } from '../../index';
|
||||
import RegistrationFailureMessage from '../RegistrationFailure';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendPageEvent: jest.fn(),
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||
getLocale: jest.fn(),
|
||||
}));
|
||||
|
||||
const IntlEmbeddableRegistrationPage = injectIntl(EmbeddableRegistrationPage);
|
||||
const IntlRegistrationFailure = injectIntl(RegistrationFailureMessage);
|
||||
const mockStore = configureStore();
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const mockNavigation = jest.fn();
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const Navigate = ({ to }) => {
|
||||
mockNavigation(to);
|
||||
return <div />;
|
||||
};
|
||||
|
||||
return {
|
||||
...jest.requireActual('react-router-dom'),
|
||||
Navigate,
|
||||
mockNavigate: mockNavigation,
|
||||
};
|
||||
});
|
||||
|
||||
describe('EmbeddableRegistrationPage', () => {
|
||||
mergeConfig({
|
||||
PRIVACY_POLICY: 'https://privacy-policy.com',
|
||||
TOS_AND_HONOR_CODE: 'https://tos-and-honot-code.com',
|
||||
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME,
|
||||
});
|
||||
|
||||
let props = {};
|
||||
let store = {};
|
||||
const registrationFormData = {
|
||||
configurableFormFields: {
|
||||
marketingEmailsOptIn: true,
|
||||
},
|
||||
formFields: {
|
||||
name: '', email: '', username: '', password: '',
|
||||
},
|
||||
emailSuggestion: {
|
||||
suggestion: '', type: '',
|
||||
},
|
||||
errors: {
|
||||
name: '', email: '', username: '', password: '',
|
||||
},
|
||||
};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
const routerWrapper = children => (
|
||||
<Router>
|
||||
{children}
|
||||
</Router>
|
||||
);
|
||||
|
||||
const thirdPartyAuthContext = {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
countryCode: null,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
register: {
|
||||
registrationResult: { success: false, redirectUrl: '' },
|
||||
registrationError: {},
|
||||
registrationFormData,
|
||||
usernameSuggestions: [],
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext,
|
||||
fieldDescriptions: {},
|
||||
optionalFields: {
|
||||
fields: {},
|
||||
extended_profile: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(initialState);
|
||||
configure({
|
||||
loggingService: { logError: jest.fn() },
|
||||
config: {
|
||||
ENVIRONMENT: 'production',
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME: 'yum',
|
||||
},
|
||||
messages: { 'es-419': {}, de: {}, 'en-us': {} },
|
||||
});
|
||||
|
||||
props = {};
|
||||
|
||||
window.location = { search: '' };
|
||||
window.parent.postMessage = jest.fn();
|
||||
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const populateRequiredFields = (registrationPage, payload, isThirdPartyAuth = false) => {
|
||||
registrationPage.find('input#name').simulate('change', { target: { value: payload.name, name: 'name' } });
|
||||
registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } });
|
||||
registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } });
|
||||
|
||||
registrationPage.find('input[name="country"]').simulate('change', { target: { value: payload.country, name: 'country' } });
|
||||
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: payload.country, name: 'country' } });
|
||||
|
||||
if (!isThirdPartyAuth) {
|
||||
registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } });
|
||||
}
|
||||
};
|
||||
|
||||
describe('Test Embeddable Registration Page', () => {
|
||||
mergeConfig({
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS: true,
|
||||
});
|
||||
|
||||
const emptyFieldValidation = {
|
||||
name: 'Enter your full name',
|
||||
username: 'Username must be between 2 and 30 characters',
|
||||
email: 'Enter your email',
|
||||
password: 'Password criteria has not been met',
|
||||
country: 'Select your country or region of residence',
|
||||
};
|
||||
|
||||
// ******** test registration form submission ********
|
||||
|
||||
it('should submit form for valid input', () => {
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL, search: '?next=/course/demo-course-url' };
|
||||
|
||||
const payload = {
|
||||
name: 'John Doe',
|
||||
username: 'john_doe',
|
||||
email: 'john.doe@gmail.com',
|
||||
password: 'password1',
|
||||
country: 'Pakistan',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
next: '/course/demo-course-url',
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
});
|
||||
|
||||
it('should submit form with marketing email opt in value', () => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
});
|
||||
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
|
||||
const payload = {
|
||||
name: 'John Doe',
|
||||
username: 'john_doe',
|
||||
email: 'john.doe@gmail.com',
|
||||
password: 'password1',
|
||||
country: 'Pakistan',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
marketing_emails_opt_in: true,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not dispatch registerNewUser on empty form Submission', () => {
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).not.toHaveBeenCalledWith(registerNewUser({}));
|
||||
});
|
||||
|
||||
// ******** test registration form validations ********
|
||||
|
||||
it('should show error messages for required fields on empty form submission', () => {
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="name"]').text()).toEqual(emptyFieldValidation.name);
|
||||
expect(registrationPage.find('div[feedback-for="username"]').text()).toEqual(emptyFieldValidation.username);
|
||||
expect(registrationPage.find('div[feedback-for="email"]').text()).toEqual(emptyFieldValidation.email);
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country);
|
||||
|
||||
const alertBanner = 'We couldn\'t create your account.Please check your responses and try again.';
|
||||
expect(registrationPage.find('#validation-errors').first().text()).toEqual(alertBanner);
|
||||
});
|
||||
|
||||
it('should clear error on focus', () => {
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
|
||||
registrationPage.find('input#password').simulate('focus');
|
||||
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear registration backend error on change', () => {
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
const emailError = 'This email is already associated with an existing or previous account';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
email: [{ userMessage: emailError }],
|
||||
},
|
||||
},
|
||||
});
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlEmbeddableRegistrationPage {...props} />,
|
||||
))).find('EmbeddableRegistrationPage');
|
||||
|
||||
registrationPage.find('input#email').simulate('change', { target: { value: 'test1@gmail.com', name: 'email' } });
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('email'));
|
||||
});
|
||||
|
||||
// ******** test alert messages ********
|
||||
|
||||
it('should match internal server error message', () => {
|
||||
const expectedMessage = 'We couldn\'t create your account.An error has occurred. Try refreshing the page, or check your internet connection.';
|
||||
props = {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match registration api rate limit error message', () => {
|
||||
const expectedMessage = 'We couldn\'t create your account.Too many failed registration attempts. Try again later.';
|
||||
props = {
|
||||
errorCode: FORBIDDEN_REQUEST,
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa session expired error message', () => {
|
||||
const expectedMessage = 'We couldn\'t create your account.Registration using Google has timed out.';
|
||||
props = {
|
||||
context: {
|
||||
provider: 'Google',
|
||||
},
|
||||
errorCode: TPA_SESSION_EXPIRED,
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa authentication failed error message', () => {
|
||||
const expectedMessageSubstring = 'We are sorry, you are not authorized to access';
|
||||
props = {
|
||||
context: {
|
||||
provider: 'Google',
|
||||
},
|
||||
errorCode: TPA_AUTHENTICATION_FAILURE,
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toContain(expectedMessageSubstring);
|
||||
});
|
||||
|
||||
// ******** test form buttons and fields ********
|
||||
|
||||
it('should match default button state', () => {
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('button[type="submit"] span').first().text()).toEqual('Create an account for free');
|
||||
});
|
||||
|
||||
it('should match pending button state', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
submitState: PENDING_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
const button = registrationPage.find('button[type="submit"] span').first();
|
||||
|
||||
expect(button.find('.sr-only').text()).toEqual('pending');
|
||||
});
|
||||
|
||||
it('should display opt-in/opt-out checkbox', () => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
});
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('div.form-field--checkbox').length).toEqual(1);
|
||||
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should show button label based on cta query params value', () => {
|
||||
const buttonLabel = 'Register';
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL, search: `?cta=${buttonLabel}` };
|
||||
const registrationPage = mount(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('button[type="submit"] span').first().text()).toEqual(buttonLabel);
|
||||
});
|
||||
|
||||
it('should check registration conversion cookie', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(document.cookie).toMatch(`${getConfig().REGISTER_CONVERSION_COOKIE_NAME}=true`);
|
||||
});
|
||||
|
||||
// ******** miscellaneous tests ********
|
||||
|
||||
it('should send page event when register page is rendered', () => {
|
||||
mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'register');
|
||||
});
|
||||
|
||||
it('should send track event when user has successfully registered', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
redirectUrl: 'https://test.com/testing-dashboard/',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
renderer.create(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.user.account.registered.client', {});
|
||||
});
|
||||
|
||||
it('should display error message based on the error code returned by API', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />))).find('EmbeddableRegistrationPage');
|
||||
expect(registrationPage.find('div#validation-errors').first().text()).toContain(
|
||||
'An error has occurred. Try refreshing the page, or check your internet connection.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the postMessage API on registration success for embedded experience', () => {
|
||||
mergeConfig({
|
||||
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: true,
|
||||
});
|
||||
|
||||
window.parent.postMessage = jest.fn();
|
||||
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(AUTHN_PROGRESSIVE_PROFILING),
|
||||
search: '?host=http://localhost/host-website',
|
||||
};
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationResult: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
optionalFields: {
|
||||
extended_profile: [],
|
||||
fields: {
|
||||
level_of_education: { name: 'level_of_education', error_message: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const progressiveProfilingPage = mount(reduxWrapper(
|
||||
<IntlEmbeddableRegistrationPage {...props} />,
|
||||
));
|
||||
progressiveProfilingPage.update();
|
||||
expect(window.parent.postMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not display validations error on blur event when embedded variant is rendered', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
|
||||
const registrationPage = mount(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />));
|
||||
|
||||
registrationPage.find('input#username').simulate('blur', { target: { value: '', name: 'username' } });
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
|
||||
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
|
||||
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set errors in temporary state when validations are returned by registration api', () => {
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL.concat(REGISTER_PAGE), search: '?host=http://localhost/host-website' };
|
||||
|
||||
const usernameError = 'It looks like this username is already taken';
|
||||
const emailError = 'This email is already associated with an existing or previous account';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
username: [{ userMessage: usernameError }],
|
||||
email: [{ userMessage: emailError }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(
|
||||
<IntlEmbeddableRegistrationPage {...props} />),
|
||||
)).find('EmbeddableRegistrationPage');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeFalsy();
|
||||
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should clear error on focus for embedded experience also', () => {
|
||||
delete window.location;
|
||||
window.location = {
|
||||
href: getConfig().BASE_URL.concat(REGISTER_PAGE),
|
||||
search: '?host=http://localhost/host-website',
|
||||
};
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password);
|
||||
|
||||
registrationPage.find('input#password').simulate('focus');
|
||||
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Configurable Fields', () => {
|
||||
mergeConfig({
|
||||
ENABLE_DYNAMIC_REGISTRATION_FIELDS: true,
|
||||
SHOW_CONFIGURABLE_EDX_FIELDS: true,
|
||||
});
|
||||
|
||||
it('should render fields returned by backend', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
profession: { name: 'profession', type: 'text', label: 'Profession' },
|
||||
terms_of_service: {
|
||||
name: FIELDS.TERMS_OF_SERVICE,
|
||||
error_message: 'You must agree to the Terms and Service agreement of our site',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
expect(registrationPage.find('#profession').exists()).toBeTruthy();
|
||||
expect(registrationPage.find('#tos').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should submit form with fields returned by backend in payload', () => {
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
profession: { name: 'profession', type: 'text', label: 'Profession' },
|
||||
},
|
||||
extendedProfile: ['profession'],
|
||||
},
|
||||
});
|
||||
|
||||
const payload = {
|
||||
name: 'John Doe',
|
||||
username: 'john_doe',
|
||||
email: 'john.doe@example.com',
|
||||
password: 'password1',
|
||||
country: 'Pakistan',
|
||||
honor_code: true,
|
||||
profession: 'Engineer',
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(registrationPage, payload);
|
||||
registrationPage.find('input#profession').simulate('change', { target: { value: 'Engineer', name: 'profession' } });
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
|
||||
});
|
||||
|
||||
it('should show error messages for required fields on empty form submission', () => {
|
||||
const professionError = 'Enter your profession';
|
||||
const countryError = 'Select your country or region of residence';
|
||||
const confirmEmailError = 'Enter your email';
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
profession: {
|
||||
name: 'profession', type: 'text', label: 'Profession', error_message: professionError,
|
||||
},
|
||||
confirm_email: {
|
||||
name: 'confirm_email', type: 'text', label: 'Confirm Email', error_message: confirmEmailError,
|
||||
},
|
||||
country: { name: 'country' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError);
|
||||
expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(countryError);
|
||||
expect(registrationPage.find('#confirm_email-error').last().text()).toEqual(confirmEmailError);
|
||||
});
|
||||
|
||||
it('should show error if email and confirm email fields do not match', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
registrationFormData: {
|
||||
...initialState.register.registrationFormData,
|
||||
configurableFormFields: {
|
||||
...initialState.register.registrationFormData.configurableFormFields,
|
||||
confirm_email: 'test2@yopmail.com',
|
||||
},
|
||||
formFields: {
|
||||
...initialState.register.registrationFormData.formFields,
|
||||
email: 'test1@yopmail.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
confirm_email: {
|
||||
name: 'confirm_email', type: 'text', label: 'Confirm Email',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('input#confirm_email').simulate('blur');
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
expect(registrationPage.find('div#confirm_email-error').text()).toEqual('The email addresses do not match.');
|
||||
});
|
||||
|
||||
it('should run validations for configurable focused field on form submission', () => {
|
||||
const professionError = 'Enter your profession';
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
profession: {
|
||||
name: 'profession', type: 'text', label: 'Profession', error_message: professionError,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlEmbeddableRegistrationPage {...props} />)));
|
||||
registrationPage.find('input#profession').simulate('focus', { target: { value: '', name: 'profession' } });
|
||||
registrationPage.find('button.btn-brand').simulate('click');
|
||||
|
||||
expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as RegistrationPage } from './RegistrationPage';
|
||||
export { default as EmbeddableRegistrationPage } from './components/EmbeddableRegistrationPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/reducers';
|
||||
|
||||
Reference in New Issue
Block a user