From e1546acb1487c21ff8a7f09ca64a62dc4e1944b2 Mon Sep 17 00:00:00 2001
From: Mubbshar Anwar <78487564+mubbsharanwar@users.noreply.github.com>
Date: Thu, 3 Jun 2021 18:21:18 +0500
Subject: [PATCH] Clear field error on focus (#298)
Clear field related error messages for all forms on focus in event
VAN-505
---
.../AuthnValidationFormGroup.jsx | 166 ------------------
src/common-components/PasswordField.jsx | 11 +-
src/common-components/index.jsx | 1 -
.../tests/AuthnValidationFormGroup.test.jsx | 46 -----
.../tests/FormField.test.jsx | 2 +
.../tests/ForgotPasswordPage.test.jsx | 12 ++
src/login/LoginPage.jsx | 10 +-
src/login/tests/LoginPage.test.jsx | 16 +-
src/register/RegistrationPage.jsx | 11 ++
src/register/tests/RegistrationPage.test.jsx | 26 +++
src/reset-password/ResetPasswordPage.jsx | 6 +
.../tests/ResetPasswordPage.test.jsx | 4 +
12 files changed, 94 insertions(+), 217 deletions(-)
delete mode 100644 src/common-components/AuthnValidationFormGroup.jsx
delete mode 100644 src/common-components/tests/AuthnValidationFormGroup.test.jsx
diff --git a/src/common-components/AuthnValidationFormGroup.jsx b/src/common-components/AuthnValidationFormGroup.jsx
deleted file mode 100644
index cc52478c..00000000
--- a/src/common-components/AuthnValidationFormGroup.jsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import {
- Form,
- Input,
- ValidationFormGroup,
-} from '@edx/paragon';
-
-const AuthnCustomValidationFormGroup = (props) => {
- const {
- onBlur, onChange, onClick, onFocus,
- } = props;
- const [showHelpText, setShowHelpText] = useState(false);
- const [showLabelText, setShowLabelText] = useState(false);
-
- // handler code that need to be invoked via input
- const onClickHandler = (e, clickCb) => {
- setShowHelpText(true);
- setShowLabelText(true);
- if (clickCb) {
- clickCb(e);
- }
- };
- const onBlurHandler = (e, blurCb) => {
- setShowHelpText(false);
- setShowLabelText(false);
- if (blurCb) {
- blurCb(e);
- }
- };
- const onChangeHandler = (e, changeCb) => {
- if (changeCb) {
- changeCb(e);
- }
- };
- const onFocusHandler = (e, focusCb) => {
- if (focusCb) {
- focusCb(e);
- }
- };
- const onOptionalHandler = (e, clickCb) => { clickCb(e); };
-
- const showLabel = () => {
- let className;
- if (props.optionalFieldCheckbox || (!showLabelText && (props.value !== '' || props.type === 'select'))) {
- className = 'sr-only';
- } else if (showLabelText) {
- className = 'pt-10 h6';
- } else {
- className = 'pt-10 focus-out';
- }
-
- return (
-
{props.label}
- );
- };
- const showOptional = () => {
- const additionalField = props.optionalFieldCheckbox ? (
- onOptionalHandler(e, onClick)}>
- {props.checkboxMessage}
-
- ) : ;
- return additionalField;
- };
-
- const inputProps = {
- name: props.name,
- id: props.for,
- type: props.type,
- value: props.value,
- className: props.inputFieldStyle,
- 'aria-invalid': props.ariaInvalid,
- autoComplete: 'on',
- };
- inputProps.onChange = (e) => onChangeHandler(e, onChange);
- inputProps.onClick = (e) => onClickHandler(e, onClick);
- inputProps.onBlur = (e) => onBlurHandler(e, onBlur);
- inputProps.onFocus = (e) => onFocusHandler(e, onFocus);
-
- if (props.type === 'select') {
- inputProps.options = props.selectOptions;
- inputProps.className = props.value === '' ? `${props.inputFieldStyle} text-muted` : props.inputFieldStyle;
- }
- if (props.type === 'checkbox') {
- inputProps.checked = props.isChecked;
- }
-
- const validationGroupProps = {
- for: props.for,
- };
- if (!props.optionalFieldCheckbox) {
- validationGroupProps.invalid = props.invalid;
- validationGroupProps.invalidMessage = props.invalidMessage;
- validationGroupProps.helpText = showHelpText ? props.helpText : '';
- } else {
- validationGroupProps.className = props.optionalFieldCheckbox ? 'custom-control pt-10 mb-0' : '';
- }
- if (props.className) {
- validationGroupProps.className = props.className;
- }
-
- return (
-
- {showLabel()}
-
- {showOptional()}
-
- );
-};
-
-AuthnCustomValidationFormGroup.defaultProps = {
- name: '',
- for: '',
- label: '',
- optionalFieldCheckbox: false,
- type: '',
- value: '',
- invalid: false,
- ariaInvalid: false,
- invalidMessage: '',
- inputFieldStyle: '',
- helpText: '',
- className: '',
- onClick: null,
- onBlur: null,
- onChange: null,
- onFocus: null,
- isChecked: false,
- checkboxMessage: '',
- selectOptions: null,
-};
-
-AuthnCustomValidationFormGroup.propTypes = {
- name: PropTypes.string,
- for: PropTypes.string,
- label: PropTypes.string,
- type: PropTypes.string,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.bool,
- ]),
- invalid: PropTypes.bool,
- ariaInvalid: PropTypes.bool,
- invalidMessage: PropTypes.string,
- helpText: PropTypes.string,
- className: PropTypes.string,
- inputFieldStyle: PropTypes.string,
- isChecked: PropTypes.bool,
- optionalFieldCheckbox: PropTypes.bool,
- onClick: PropTypes.func,
- onBlur: PropTypes.func,
- onChange: PropTypes.func,
- onFocus: PropTypes.func,
- checkboxMessage: PropTypes.string,
- selectOptions: PropTypes.arrayOf(PropTypes.shape({
- key: PropTypes.string,
- value: PropTypes.string,
- })),
-};
-
-export default AuthnCustomValidationFormGroup;
diff --git a/src/common-components/PasswordField.jsx b/src/common-components/PasswordField.jsx
index 7484e27e..3a1793f1 100644
--- a/src/common-components/PasswordField.jsx
+++ b/src/common-components/PasswordField.jsx
@@ -21,6 +21,13 @@ const PasswordField = (props) => {
setShowTooltip(props.showRequirements && false);
};
+ const handleFocus = (e) => {
+ if (props.handleFocus) {
+ props.handleFocus(e);
+ }
+ setTimeout(() => setShowTooltip(props.showRequirements && true), 150);
+ };
+
const HideButton = (
);
@@ -54,7 +61,7 @@ const PasswordField = (props) => {
type={isPasswordHidden ? 'password' : 'text'}
name={props.name}
value={props.value}
- onFocus={() => setTimeout(() => setShowTooltip(props.showRequirements && true), 150)}
+ onFocus={handleFocus}
onBlur={handleBlur}
onChange={props.handleChange}
controlClassName={props.borderClass}
@@ -73,6 +80,7 @@ PasswordField.defaultProps = {
borderClass: '',
errorMessage: '',
handleBlur: null,
+ handleFocus: null,
handleChange: () => {},
showRequirements: true,
};
@@ -82,6 +90,7 @@ PasswordField.propTypes = {
errorMessage: PropTypes.string,
floatingLabel: PropTypes.string.isRequired,
handleBlur: PropTypes.func,
+ handleFocus: PropTypes.func,
handleChange: PropTypes.func,
intl: intlShape.isRequired,
name: PropTypes.string.isRequired,
diff --git a/src/common-components/index.jsx b/src/common-components/index.jsx
index a4463735..d57bf808 100644
--- a/src/common-components/index.jsx
+++ b/src/common-components/index.jsx
@@ -7,7 +7,6 @@ export { default as SocialAuthProviders } from './SocialAuthProviders';
export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert';
export { default as InstitutionLogistration } from './InstitutionLogistration';
export { RenderInstitutionButton } from './InstitutionLogistration';
-export { default as AuthnValidationFormGroup } from './AuthnValidationFormGroup';
export { default as APIFailureMessage } from './APIFailureMessage';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';
diff --git a/src/common-components/tests/AuthnValidationFormGroup.test.jsx b/src/common-components/tests/AuthnValidationFormGroup.test.jsx
deleted file mode 100644
index 0c765e54..00000000
--- a/src/common-components/tests/AuthnValidationFormGroup.test.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react';
-import { mount } from 'enzyme';
-
-import AuthnCustomValidationFormGroup from '../AuthnValidationFormGroup';
-
-describe('AuthnCustomValidationFormGroup', () => {
- let props = {
- label: 'Email Label',
- for: 'email',
- name: 'email',
- type: 'email',
- value: '',
- helpText: 'Email field help text',
- };
-
- it('should show label in place of placeholder when field is empty', () => {
- const validationFormGroup = mount();
- expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 focus-out');
- });
-
- it('should show label on top of field when field is focused in', () => {
- const validationFormGroup = mount();
-
- validationFormGroup.find('input').simulate('focus');
- expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label pt-10 h6');
- });
-
- it('should keep label hidden for checkbox field', () => {
- props = {
- ...props,
- type: 'checkbox',
- optionalFieldCheckbox: true,
- };
- const validationFormGroup = mount();
- expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only');
- });
-
- it('should keep label hidden when input field is not empty', () => {
- props = {
- ...props,
- value: 'test',
- };
- const validationFormGroup = mount();
- expect(validationFormGroup.find('label').prop('className')).toEqual('pgn__form-label sr-only');
- });
-});
diff --git a/src/common-components/tests/FormField.test.jsx b/src/common-components/tests/FormField.test.jsx
index 7cf0c918..c78678d8 100644
--- a/src/common-components/tests/FormField.test.jsx
+++ b/src/common-components/tests/FormField.test.jsx
@@ -13,6 +13,7 @@ describe('FormGroup', () => {
helpText: ['Email field help text'],
name: 'email',
value: '',
+ handleFocus: jest.fn(),
};
it('should show help text on field focus', () => {
@@ -33,6 +34,7 @@ describe('PasswordField', () => {
floatingLabel: 'Password',
name: 'password',
value: 'password123',
+ handleFocus: jest.fn(),
};
});
diff --git a/src/forgot-password/tests/ForgotPasswordPage.test.jsx b/src/forgot-password/tests/ForgotPasswordPage.test.jsx
index aee861ee..1445bf4e 100644
--- a/src/forgot-password/tests/ForgotPasswordPage.test.jsx
+++ b/src/forgot-password/tests/ForgotPasswordPage.test.jsx
@@ -121,6 +121,18 @@ describe('ForgotPasswordPage', () => {
expect(forgotPasswordPage.find('.pgn__form-text-invalid').text()).toEqual(validationMessage);
});
+ it('should clear error message on focus event', async () => {
+ const validationMessage = 'Enter your email';
+ const forgotPasswordPage = mount(reduxWrapper());
+ await act(async () => { await forgotPasswordPage.find('button.btn-brand').simulate('click'); });
+
+ forgotPasswordPage.update();
+ expect(forgotPasswordPage.find('.pgn__form-text-invalid').text()).toEqual(validationMessage);
+
+ forgotPasswordPage.find('input#email').simulate('focus');
+ expect(forgotPasswordPage.find('#email-invalid-feedback').exists()).toEqual(false);
+ });
+
it('check cookie rendered', () => {
const forgotPage = mount(reduxWrapper());
expect(forgotPage.find()).toBeTruthy();
diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx
index a04371ea..59aaee2c 100644
--- a/src/login/LoginPage.jsx
+++ b/src/login/LoginPage.jsx
@@ -97,6 +97,12 @@ class LoginPage extends React.Component {
this.props.loginRequest(payload);
}
+ handleOnFocus = (e) => {
+ const { errors } = this.state;
+ errors[e.target.name] = '';
+ this.setState({ errors });
+ }
+
validateEmail(email) {
const { errors } = this.state;
@@ -210,9 +216,10 @@ class LoginPage extends React.Component {
{this.props.resetPassword && !this.props.loginError ? : null}