diff --git a/src/register/RegistrationFields/CountryField/CountryField.jsx b/src/register/RegistrationFields/CountryField/CountryField.jsx index a3d01ad5..c1ffc4de 100644 --- a/src/register/RegistrationFields/CountryField/CountryField.jsx +++ b/src/register/RegistrationFields/CountryField/CountryField.jsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; import { FormAutosuggest, FormAutosuggestOption, FormControlFeedback } from '@edx/paragon'; +import classNames from 'classnames'; import PropTypes from 'prop-types'; import validateCountryField, { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY } from './validator'; @@ -60,7 +61,7 @@ const CountryField = (props) => { const { value } = event.target; const { countryCode, displayValue, error } = validateCountryField( - value.trim(), countryList, formatMessage(messages['empty.country.field.error']), + value.trim(), countryList, formatMessage(messages['empty.country.field.error']), formatMessage(messages['invalid.country.field.error']), ); onChangeHandler({ target: { name: 'country' } }, { countryCode, displayValue }); @@ -94,6 +95,7 @@ const CountryField = (props) => { aria-label="form autosuggest" name="country" value={selectedCountry.displayValue || ''} + className={classNames({ 'form-field-error': props.errorMessage })} onSelected={(value) => handleSelected(value)} onFocus={(e) => handleOnFocus(e)} onBlur={(e) => handleOnBlur(e)} diff --git a/src/register/RegistrationFields/CountryField/CountryField.test.jsx b/src/register/RegistrationFields/CountryField/CountryField.test.jsx index f442fd38..66a09aba 100644 --- a/src/register/RegistrationFields/CountryField/CountryField.test.jsx +++ b/src/register/RegistrationFields/CountryField/CountryField.test.jsx @@ -91,6 +91,16 @@ describe('CountryField', () => { ); }); + it('should run country field validation when country name is invalid', () => { + const countryField = mount(routerWrapper(reduxWrapper())); + countryField.find('input[name="country"]').simulate('blur', { target: { value: 'Pak', name: 'country' } }); + expect(props.handleErrorChange).toHaveBeenCalledTimes(1); + expect(props.handleErrorChange).toHaveBeenCalledWith( + 'country', + 'Country must match with an option available in the dropdown.', + ); + }); + it('should not run country field validation when onBlur is fired by drop-down arrow icon click', () => { const countryField = mount(routerWrapper(reduxWrapper())); countryField.find('input[name="country"]').simulate('blur', { diff --git a/src/register/RegistrationFields/CountryField/validator.js b/src/register/RegistrationFields/CountryField/validator.js index 0dd4c265..cb28bed4 100644 --- a/src/register/RegistrationFields/CountryField/validator.js +++ b/src/register/RegistrationFields/CountryField/validator.js @@ -1,10 +1,10 @@ export const COUNTRY_CODE_KEY = 'code'; export const COUNTRY_DISPLAY_KEY = 'name'; -const validateCountryField = (value, countryList, errorMessage) => { +const validateCountryField = (value, countryList, emptyErrorMessage, invalidCountryErrorMessage) => { let countryCode = ''; let displayValue = value; - let error = errorMessage; + let error = ''; if (value) { const normalizedValue = value.toLowerCase(); @@ -20,8 +20,11 @@ const validateCountryField = (value, countryList, errorMessage) => { if (selectedCountry) { countryCode = selectedCountry[COUNTRY_CODE_KEY]; displayValue = selectedCountry[COUNTRY_DISPLAY_KEY]; - error = ''; + } else { + error = invalidCountryErrorMessage; } + } else { + error = emptyErrorMessage; } return { error, countryCode, displayValue }; }; diff --git a/src/register/components/ConfigurableRegistrationForm.test.jsx b/src/register/components/ConfigurableRegistrationForm.test.jsx index eb6028f1..19aaff0f 100644 --- a/src/register/components/ConfigurableRegistrationForm.test.jsx +++ b/src/register/components/ConfigurableRegistrationForm.test.jsx @@ -285,6 +285,26 @@ describe('ConfigurableRegistrationForm', () => { expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(countryError); expect(registrationPage.find('#confirm_email-error').last().text()).toEqual(confirmEmailError); }); + + it('should show country field validation when country name is invalid', () => { + const invalidCountryError = 'Country must match with an option available in the dropdown.'; + + store = mockStore({ + ...initialState, + commonComponents: { + ...initialState.commonComponents, + fieldDescriptions: { + country: { name: 'country' }, + }, + }, + }); + const registrationPage = mount(routerWrapper(reduxWrapper())); + registrationPage.find('input[name="country"]').simulate('blur', { target: { value: 'Pak', name: 'country' } }); + + registrationPage.find('button.btn-brand').simulate('click'); + expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(invalidCountryError); + }); + it('should show error if email and confirm email fields do not match', () => { store = mockStore({ ...initialState, diff --git a/src/register/data/utils.js b/src/register/data/utils.js index ca060512..591640eb 100644 --- a/src/register/data/utils.js +++ b/src/register/data/utils.js @@ -48,6 +48,9 @@ export const isFormValid = ( if (!configurableFormFields?.country?.displayValue) { fieldErrors.country = formatMessage(messages['empty.country.field.error']); isValid = false; + } else if (!configurableFormFields?.country?.countryCode) { + fieldErrors.country = formatMessage(messages['invalid.country.field.error']); + isValid = false; } } Object.keys(fieldDescriptions).forEach(key => { diff --git a/src/register/messages.jsx b/src/register/messages.jsx index 0e7c41d1..463a6a2b 100644 --- a/src/register/messages.jsx +++ b/src/register/messages.jsx @@ -111,6 +111,11 @@ const messages = defineMessages({ defaultMessage: 'Select your country or region of residence', description: 'Error message when no country/region is selected', }, + 'invalid.country.field.error': { + id: 'invalid.country.field.error', + defaultMessage: 'Country must match with an option available in the dropdown.', + description: 'Error message when country is invalid', + }, 'email.do.not.match': { id: 'email.do.not.match', defaultMessage: 'The email addresses do not match.', diff --git a/src/sass/_registration.scss b/src/sass/_registration.scss index f11af0e0..6d3cb1bb 100644 --- a/src/sass/_registration.scss +++ b/src/sass/_registration.scss @@ -107,3 +107,11 @@ right: 0.5rem; } } + +.form-field-error { + border: 2px solid var(--danger-300, #CA3A2F) !important; + + input { + border: none; + } +}