Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync opencraft-release/quince.1 with Upstream 20240715-1721002210 #18

Open
wants to merge 5 commits into
base: opencraft-release/quince.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Authn | <%= process.env.SITE_NAME %></title>
<title><%= (process.env.SITE_NAME && process.env.SITE_NAME != 'null') ? 'Authentication | ' + process.env.SITE_NAME : 'Authentication' %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
Expand Down
3 changes: 2 additions & 1 deletion src/login/LoginPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class LoginPage extends React.Component {
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
const isSocialAuthActive = !!providers.length && !currentProvider;
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);

return (
<>
Expand All @@ -183,7 +184,7 @@ class LoginPage extends React.Component {
</Hyperlink>
)}

{thirdPartyAuthApiStatus === PENDING_STATE ? (
{thirdPartyAuthApiStatus === PENDING_STATE && isThirdPartyAuthActive ? (
<Skeleton className="tpa-skeleton mb-3" height={30} count={2} />
) : (
<>
Expand Down
13 changes: 7 additions & 6 deletions src/register/RegistrationFields/EmailField/EmailField.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -9,9 +9,9 @@ import PropTypes from 'prop-types';
import validateEmail from './validator';
import { FormGroup } from '../../../common-components';
import {
backupRegistrationFormBegin,
clearRegistrationBackendError,
fetchRealtimeValidations,
setEmailSuggestionInStore,
} from '../../data/actions';
import messages from '../../messages';

Expand Down Expand Up @@ -44,6 +44,10 @@ const EmailField = (props) => {

const [emailSuggestion, setEmailSuggestion] = useState({ ...backedUpFormData?.emailSuggestion });

useEffect(() => {
setEmailSuggestion(backedUpFormData.emailSuggestion);
}, [backedUpFormData.emailSuggestion]);

const handleOnBlur = (e) => {
const { value } = e.target;
const { fieldError, confirmEmailError, suggestion } = validateEmail(value, confirmEmailValue, formatMessage);
Expand All @@ -52,10 +56,7 @@ const EmailField = (props) => {
handleErrorChange('confirm_email', confirmEmailError);
}

dispatch(backupRegistrationFormBegin({
...backedUpFormData,
emailSuggestion: { ...suggestion },
}));
dispatch(setEmailSuggestionInStore(suggestion));
setEmailSuggestion(suggestion);

if (fieldError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ describe('EmailField', () => {
);

const initialState = {
register: {},
register: {
registrationFormData: {
emailSuggestion: {
suggestion: 'example@gmail.com',
type: 'warning',
},
},
},
};

beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/register/RegistrationFields/EmailField/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const validateEmailAddress = (value, username, domainName) => {
const validateEmail = (value, confirmEmailValue, formatMessage) => {
let fieldError = '';
let confirmEmailError = '';
let emailSuggestion = {};
let emailSuggestion = { suggestion: '', type: '' };

if (!value) {
fieldError = formatMessage(messages['empty.email.field.error']);
Expand Down
2 changes: 1 addition & 1 deletion src/register/RegistrationFields/NameField/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{
export const urlRegex = new RegExp(INVALID_NAME_REGEX);

const validateName = (value, formatMessage) => {
let fieldError;
let fieldError = '';
if (!value.trim()) {
fieldError = formatMessage(messages['empty.name.field.error']);
} else if (value && value.match(urlRegex)) {
Expand Down
6 changes: 4 additions & 2 deletions src/register/RegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
backupRegistrationFormBegin,
clearRegistrationBackendError,
registerNewUser,
setEmailSuggestionInStore,
setUserPipelineDataLoaded,
} from './data/actions';
import {
Expand Down Expand Up @@ -185,8 +186,8 @@ const RegistrationPage = (props) => {
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
if (registrationError[name]) {
dispatch(clearRegistrationBackendError(name));
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
}
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
setFormFields(prevState => ({ ...prevState, [name]: value }));
};

Expand Down Expand Up @@ -220,14 +221,15 @@ const RegistrationPage = (props) => {
}

// Validating form data before submitting
const { isValid, fieldErrors } = isFormValid(
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
payload,
registrationEmbedded ? temporaryErrors : errors,
configurableFormFields,
fieldDescriptions,
formatMessage,
);
setErrors({ ...fieldErrors });
dispatch(setEmailSuggestionInStore(emailSuggestion));

// returning if not valid
if (!isValid) {
Expand Down
47 changes: 47 additions & 0 deletions src/register/RegistrationPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,53 @@ describe('RegistrationPage', () => {
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...formPayload, country: 'PK' }));
});

it('should display an error when form is submitted with an invalid email', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const emailError = 'Enter a valid email address';

const formPayload = {
name: 'Petro',
username: 'petro_qa',
email: 'petro @example.com',
password: 'password1',
country: 'Ukraine',
honor_code: true,
totalRegistrationTime: 0,
};

store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('button.btn-brand').simulate('click');
expect(
registrationPage.find('div[feedback-for="email"]').text(),
).toEqual(emailError);
});

it('should display an error when form is submitted with an invalid username', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const usernameError = 'Usernames can only contain letters (A-Z, a-z), numerals (0-9), '
+ 'underscores (_), and hyphens (-). Usernames cannot contain spaces';

const formPayload = {
name: 'Petro',
username: 'petro qa',
email: 'petro@example.com',
password: 'password1',
country: 'Ukraine',
honor_code: true,
totalRegistrationTime: 0,
};

store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('button.btn-brand').simulate('click');
expect(
registrationPage.find('div[feedback-for="username"]').text(),
).toEqual(usernameError);
});

it('should submit form with marketing email opt in value', () => {
mergeConfig({
MARKETING_EMAILS_OPT_IN: 'true',
Expand Down
61 changes: 61 additions & 0 deletions src/register/components/ConfigurableRegistrationForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import configureStore from 'redux-mock-store';

import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
import { FIELDS } from '../data/constants';
import RegistrationPage from '../RegistrationPage';

jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
Expand All @@ -22,7 +23,20 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
}));

const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
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' } });
}
};

jest.mock('react-router-dom', () => {
const mockNavigation = jest.fn();
Expand Down Expand Up @@ -182,5 +196,52 @@ describe('ConfigurableRegistrationForm', () => {
[FIELDS.TERMS_OF_SERVICE]: true,
});
});

it('should show error if email and confirm email fields do not match on submit click', () => {
const formPayload = {
name: 'Petro',
username: 'petro_qa',
email: 'petro@example.com',
password: 'password1',
country: 'Ukraine',
honor_code: true,
totalRegistrationTime: 0,
};

store = mockStore({
...initialState,
commonComponents: {
...initialState.commonComponents,
fieldDescriptions: {
confirm_email: {
name: 'confirm_email', type: 'text', label: 'Confirm Email',
},
country: { name: 'country' },
},
},
});
const registrationPage = mount(routerWrapper(reduxWrapper(
<IntlRegistrationPage {...props} />,
)));

populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('input#confirm_email').simulate(
'change', { target: { value: 'test2@gmail.com', name: 'confirm_email' } },
);

const button = registrationPage.find('button.btn-brand');
button.simulate('click');

registrationPage.update();

const confirmEmailErrorElement = registrationPage.find('div#confirm_email-error');
expect(confirmEmailErrorElement.text()).toEqual('The email addresses do not match.');

const validationErrors = registrationPage.find('#validation-errors');
const firstValidationErrorText = validationErrors.first().text();
expect(firstValidationErrorText).toContain(
"We couldn't create your account.Please check your responses and try again.",
);
});
});
});
3 changes: 2 additions & 1 deletion src/register/components/ThirdPartyAuth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const ThirdPartyAuth = (props) => {
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
const isSocialAuthActive = !!providers.length && !currentProvider;
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);

return (
<>
Expand All @@ -34,7 +35,7 @@ const ThirdPartyAuth = (props) => {
</div>
)}

{thirdPartyAuthApiStatus === PENDING_STATE ? (
{thirdPartyAuthApiStatus === PENDING_STATE && isThirdPartyAuthActive ? (
<Skeleton className="tpa-skeleton" height={36} count={2} />
) : (
<>
Expand Down
7 changes: 7 additions & 0 deletions src/register/data/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_N
export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_SUGGESTIONS';
export const REGISTRATION_CLEAR_BACKEND_ERROR = 'REGISTRATION_CLEAR_BACKEND_ERROR';
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';

// Backup registration form
Expand Down Expand Up @@ -37,6 +38,12 @@ export const fetchRealtimeValidationsFailure = () => ({
type: REGISTER_FORM_VALIDATIONS.FAILURE,
});

// Set email field frontend validations
export const setEmailSuggestionInStore = (emailSuggestion) => ({
type: REGISTER_SET_EMAIL_SUGGESTIONS,
payload: { emailSuggestion },
});

// Register
export const registerNewUser = registrationInfo => ({
type: REGISTER_NEW_USER.BASE,
Expand Down
13 changes: 12 additions & 1 deletion src/register/data/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
REGISTER_SET_COUNTRY_CODE, REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTER_SET_COUNTRY_CODE,
REGISTER_SET_EMAIL_SUGGESTIONS,
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTRATION_CLEAR_BACKEND_ERROR,
} from './actions';
import {
Expand Down Expand Up @@ -119,6 +121,15 @@ const reducer = (state = defaultState, action = {}) => {
userPipelineDataLoaded: value,
};
}
case REGISTER_SET_EMAIL_SUGGESTIONS: {
return {
...state,
registrationFormData: {
...state.registrationFormData,
emailSuggestion: action.payload.emailSuggestion,
},
};
}
default:
return {
...state,
Expand Down
24 changes: 24 additions & 0 deletions src/register/data/tests/reducers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
REGISTER_SET_COUNTRY_CODE,
REGISTER_SET_EMAIL_SUGGESTIONS,
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTRATION_CLEAR_BACKEND_ERROR,
} from '../actions';
Expand Down Expand Up @@ -64,6 +65,29 @@ describe('Registration Reducer Tests', () => {
},
);
});

it('should set email suggestions', () => {
const emailSuggestion = {
type: 'test type',
suggestion: 'test suggestion',
};
const action = {
type: REGISTER_SET_EMAIL_SUGGESTIONS,
payload: { emailSuggestion },
};

expect(reducer(defaultState, action)).toEqual(
{
...defaultState,
registrationFormData: {
...defaultState.registrationFormData,
emailSuggestion: {
type: 'test type', suggestion: 'test suggestion',
},
},
});
});

it('should set redirect url dashboard on registration success action', () => {
const payload = {
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
Expand Down
Loading
Loading