fix: core profiler email opt in validation (#41152)

This commit is contained in:
nigeljamesstevenson 2023-11-01 17:10:49 +00:00 committed by GitHub
commit d61ff514c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 601 additions and 324 deletions

View File

@ -9,6 +9,7 @@ import {
Spinner, Spinner,
CheckboxControl, CheckboxControl,
} from '@wordpress/components'; } from '@wordpress/components';
import { FormInputValidation } from '@automattic/components';
import { SelectControl } from '@woocommerce/components'; import { SelectControl } from '@woocommerce/components';
import { Icon, chevronDown } from '@wordpress/icons'; import { Icon, chevronDown } from '@wordpress/icons';
import { import {
@ -18,6 +19,8 @@ import {
} from '@wordpress/element'; } from '@wordpress/element';
import { findCountryOption, getCountry } from '@woocommerce/onboarding'; import { findCountryOption, getCountry } from '@woocommerce/onboarding';
import { decodeEntities } from '@wordpress/html-entities'; import { decodeEntities } from '@wordpress/html-entities';
import { z } from 'zod';
import classNames from 'classnames';
/** /**
* Internal dependencies * Internal dependencies
@ -195,6 +198,8 @@ export const BusinessInfo = ( {
const [ hasSubmitted, setHasSubmitted ] = useState( false ); const [ hasSubmitted, setHasSubmitted ] = useState( false );
const [ isEmailInvalid, setIsEmailInvalid ] = useState( false );
const [ storeEmailAddress, setEmailAddress ] = useState( const [ storeEmailAddress, setEmailAddress ] = useState(
storeEmailAddressFromOnboardingProfile || currentUserEmail || '' storeEmailAddressFromOnboardingProfile || currentUserEmail || ''
); );
@ -203,6 +208,19 @@ export const BusinessInfo = ( {
isOptInMarketingFromOnboardingProfile || false isOptInMarketingFromOnboardingProfile || false
); );
const [ doValidate, setDoValidate ] = useState( false );
useEffect( () => {
if ( doValidate ) {
const parseEmail = z
.string()
.email()
.safeParse( storeEmailAddress );
setIsEmailInvalid( isOptInMarketing && ! parseEmail.success );
setDoValidate( false );
}
}, [ isOptInMarketing, doValidate, storeEmailAddress ] );
return ( return (
<div <div
className="woocommerce-profiler-business-information" className="woocommerce-profiler-business-information"
@ -375,10 +393,19 @@ export const BusinessInfo = ( {
{ emailMarketingExperimentAssignment === 'treatment' && ( { emailMarketingExperimentAssignment === 'treatment' && (
<> <>
<TextControl <TextControl
className="woocommerce-profiler-business-info-email-adddress" className={ classNames(
'woocommerce-profiler-business-info-email-adddress',
{ 'is-error': isEmailInvalid }
) }
onChange={ ( value ) => { onChange={ ( value ) => {
if ( isEmailInvalid ) {
setDoValidate( true ); // trigger validation as we want to feedback to the user as soon as it becomes valid
}
setEmailAddress( value ); setEmailAddress( value );
} } } }
onBlur={ () => {
setDoValidate( true );
} }
value={ decodeEntities( storeEmailAddress ) } value={ decodeEntities( storeEmailAddress ) }
label={ label={
<> <>
@ -398,6 +425,15 @@ export const BusinessInfo = ( {
'woocommerce' 'woocommerce'
) } ) }
/> />
{ isEmailInvalid && (
<FormInputValidation
isError
text={ __(
'This email is not valid.',
'woocommerce'
) }
/>
) }
<CheckboxControl <CheckboxControl
className="core-profiler__checkbox" className="core-profiler__checkbox"
label={ __( label={ __(
@ -405,7 +441,10 @@ export const BusinessInfo = ( {
'woocommerce' 'woocommerce'
) } ) }
checked={ isOptInMarketing } checked={ isOptInMarketing }
onChange={ setIsOptInMarketing } onChange={ ( isChecked ) => {
setIsOptInMarketing( isChecked );
setDoValidate( true );
} }
/> />
</> </>
) } ) }
@ -418,8 +457,7 @@ export const BusinessInfo = ( {
! storeCountry.key || ! storeCountry.key ||
( emailMarketingExperimentAssignment === ( emailMarketingExperimentAssignment ===
'treatment' && 'treatment' &&
isOptInMarketing && isEmailInvalid )
storeEmailAddress.length === 0 )
} }
onClick={ () => { onClick={ () => {
sendEvent( { sendEvent( {

View File

@ -408,5 +408,137 @@ describe( 'BusinessInfo', () => {
} ); } );
expect( emailInput ).toHaveValue( 'wordpress@automattic.com' ); expect( emailInput ).toHaveValue( 'wordpress@automattic.com' );
} ); } );
it( 'should not show an error for invalid email if isOptInMarketing is false', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
expect(
screen.queryByText( /This email is not valid./i )
).not.toBeInTheDocument();
} );
it( 'should validate the email field when isOptInMarketing is true', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
expect(
screen.getByText( /This email is not valid./i )
).toBeInTheDocument();
} );
it( 'should not show an error for invalid email if isOptInMarketing is true and email is valid', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'valid@email.com' );
} );
it( 'should show an error for invalid email if isOptInMarketing is checked after the invalid email has already been filled out', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
expect(
screen.queryByText( /This email is not valid./i )
).not.toBeInTheDocument();
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
expect(
screen.getByText( /This email is not valid./i )
).toBeInTheDocument();
} );
it( 'should hide the error after the invalid email has been corrected', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
expect(
screen.getByText( /This email is not valid./i )
).toBeInTheDocument();
userEvent.clear( emailInput );
userEvent.type( emailInput, 'valid@email.com' );
expect(
screen.queryByText( /This email is not valid./i )
).not.toBeInTheDocument();
} );
it( 'should not allow the continue button to be pressed if email is invalid and isOptInMarketing is checked', () => {
props.context.businessInfo.location = 'AW';
render( <BusinessInfo { ...props } /> );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
const continueButton = screen.getByRole( 'button', {
name: /Continue/i,
} );
expect( continueButton ).toBeDisabled();
} );
it( 'should allow the continue button to be pressed if email is invalid and isOptInMarketing is unchecked', () => {
props.context.businessInfo.location = 'AW';
props.context.onboardingProfile.is_store_country_set = true;
render( <BusinessInfo { ...props } /> );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'invalid email' );
const continueButton = screen.getByRole( 'button', {
name: /Continue/i,
} );
expect( continueButton ).not.toBeDisabled();
} );
it( 'should allow the continue button to be pressed if email is valid and isOptInMarketing is checked', () => {
props.context.businessInfo.location = 'AW';
props.context.onboardingProfile.is_store_country_set = true;
render( <BusinessInfo { ...props } /> );
const checkbox = screen.getByRole( 'checkbox', {
name: /Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox./i,
} );
userEvent.click( checkbox );
const emailInput = screen.getByRole( 'textbox', {
name: /Your email address/i,
} );
userEvent.type( emailInput, 'valid@email.com' );
const continueButton = screen.getByRole( 'button', {
name: /Continue/i,
} );
expect( continueButton ).not.toBeDisabled();
} );
} ); } );
} ); } );

View File

@ -24,6 +24,8 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
--color-error: #cc1818; // used by some @automattic/components
@include breakpoint( '<782px' ) { @include breakpoint( '<782px' ) {
padding: 0 20px; padding: 0 20px;
} }
@ -395,7 +397,6 @@
} }
// Business Info Page // Business Info Page
.woocommerce-profiler-business-information { .woocommerce-profiler-business-information {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -454,6 +455,17 @@
} }
} }
.woocommerce-profiler-business-info-email-adddress.is-error {
& .components-text-control__input {
box-shadow: 0 0 0 1px var(--color-error);
}
}
.form-input-validation.is-error span svg {
height: 20px;
width: 20px;
}
.woocommerce-profiler-select-control__country-spacer + .woocommerce-profiler-business-info-email-adddress { .woocommerce-profiler-select-control__country-spacer + .woocommerce-profiler-business-info-email-adddress {
margin-top: 8px; margin-top: 8px;
} }

View File

@ -53,6 +53,7 @@
"@automattic/explat-client": "^0.0.3", "@automattic/explat-client": "^0.0.3",
"@automattic/explat-client-react-helpers": "^0.0.4", "@automattic/explat-client-react-helpers": "^0.0.4",
"@automattic/interpolate-components": "^1.2.0", "@automattic/interpolate-components": "^1.2.0",
"@automattic/components": "^2.0.1",
"@react-spring/web": "^9.4.3", "@react-spring/web": "^9.4.3",
"@types/wordpress__blocks": "^11.0.7", "@types/wordpress__blocks": "^11.0.7",
"@woocommerce/api": "^0.2.0", "@woocommerce/api": "^0.2.0",

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix core profiler email opt in validation

File diff suppressed because it is too large Load Diff