Add `validateOnMount` prop to `ValidatedTextInput` (https://github.com/woocommerce/woocommerce-blocks/pull/8889)
* Add validateOnFirstFocus prop * Only run validation on first focus if validateOnFirstFocus is true * Rename validateOnFirstFocus to validateOnMount * Set TotalsCoupon to not validate when the input is mounted * Add tests for validation error handling * Fix test that was not making a good assertion * Add tests for validateOnMount functionality * Clean up validateOnMount logic, make the code more readable & efficient
This commit is contained in:
parent
d35254f537
commit
e30c0f5463
|
@ -128,6 +128,7 @@ export const TotalsCoupon = ( {
|
|||
setCouponValue( newCouponValue );
|
||||
} }
|
||||
focusOnMount={ true }
|
||||
validateOnMount={ false }
|
||||
showError={ false }
|
||||
/>
|
||||
<Button
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TotalsCoupon } from '..';
|
||||
|
||||
describe( 'TotalsCoupon', () => {
|
||||
it( "Shows a validation error when one is in the wc/store/validation data store and doesn't show one when there isn't", () => {
|
||||
const { rerender } = render( <TotalsCoupon instanceId={ 'coupon' } /> );
|
||||
const openCouponFormButton = screen.getByText( 'Add a coupon' );
|
||||
expect( openCouponFormButton ).toBeInTheDocument();
|
||||
userEvent.click( openCouponFormButton );
|
||||
expect(
|
||||
screen.queryByText( 'Invalid coupon code' )
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
const { setValidationErrors } = dispatch( VALIDATION_STORE_KEY );
|
||||
act( () => {
|
||||
setValidationErrors( {
|
||||
coupon: {
|
||||
hidden: false,
|
||||
message: 'Invalid coupon code',
|
||||
},
|
||||
} );
|
||||
} );
|
||||
rerender( <TotalsCoupon instanceId={ 'coupon' } /> );
|
||||
expect( screen.getByText( 'Invalid coupon code' ) ).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -6,12 +6,21 @@ import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
|||
import { dispatch, select } from '@wordpress/data';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useState } from '@wordpress/element';
|
||||
import * as wpData from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { __ValidatedTexInputWithoutId as ValidatedTextInput } from '../validated-text-input';
|
||||
|
||||
jest.mock( '@wordpress/data', () => ( {
|
||||
__esModule: true,
|
||||
...jest.requireActual( '@wordpress/data' ),
|
||||
useDispatch: jest.fn().mockImplementation( ( args ) => {
|
||||
return jest.requireActual( '@wordpress/data' ).useDispatch( args );
|
||||
} ),
|
||||
} ) );
|
||||
|
||||
describe( 'ValidatedTextInput', () => {
|
||||
it( 'Removes related validation error on change', async () => {
|
||||
render(
|
||||
|
@ -158,14 +167,141 @@ describe( 'ValidatedTextInput', () => {
|
|||
onChange={ ( value ) => setInputValue( value ) }
|
||||
value={ inputValue }
|
||||
label={ 'Test Input' }
|
||||
required={ true }
|
||||
/>
|
||||
);
|
||||
};
|
||||
render( <TestComponent /> );
|
||||
const textInputElement = await screen.getByLabelText( 'Test Input' );
|
||||
await userEvent.type( textInputElement, 'test' );
|
||||
await userEvent.type( textInputElement, '{selectall}{del}' );
|
||||
await textInputElement.blur();
|
||||
await expect(
|
||||
select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
|
||||
).not.toBe( 'Please enter a valid test input' );
|
||||
screen.queryByText( 'Please enter a valid test input' )
|
||||
).not.toBeNull();
|
||||
} );
|
||||
describe( 'correctly validates on mount', () => {
|
||||
it( 'validates when focusOnMount is true and validateOnMount is not set', async () => {
|
||||
const setValidationErrors = jest.fn();
|
||||
wpData.useDispatch.mockImplementation( ( storeName: string ) => {
|
||||
if ( storeName === VALIDATION_STORE_KEY ) {
|
||||
return {
|
||||
...jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName ),
|
||||
setValidationErrors,
|
||||
};
|
||||
}
|
||||
return jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName );
|
||||
} );
|
||||
|
||||
const TestComponent = () => {
|
||||
const [ inputValue, setInputValue ] = useState( '' );
|
||||
return (
|
||||
<ValidatedTextInput
|
||||
instanceId={ '6' }
|
||||
id={ 'test-input' }
|
||||
onChange={ ( value ) => setInputValue( value ) }
|
||||
value={ inputValue }
|
||||
label={ 'Test Input' }
|
||||
required={ true }
|
||||
focusOnMount={ true }
|
||||
/>
|
||||
);
|
||||
};
|
||||
await render( <TestComponent /> );
|
||||
const textInputElement = await screen.getByLabelText(
|
||||
'Test Input'
|
||||
);
|
||||
await expect( textInputElement ).toHaveFocus();
|
||||
await expect( setValidationErrors ).toHaveBeenCalledWith( {
|
||||
'test-input': {
|
||||
message: 'Please enter a valid test input',
|
||||
hidden: true,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
it( 'validates when focusOnMount is false, regardless of validateOnMount value', async () => {
|
||||
const setValidationErrors = jest.fn();
|
||||
wpData.useDispatch.mockImplementation( ( storeName: string ) => {
|
||||
if ( storeName === VALIDATION_STORE_KEY ) {
|
||||
return {
|
||||
...jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName ),
|
||||
setValidationErrors,
|
||||
};
|
||||
}
|
||||
return jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName );
|
||||
} );
|
||||
|
||||
const TestComponent = ( { validateOnMount = false } ) => {
|
||||
const [ inputValue, setInputValue ] = useState( '' );
|
||||
return (
|
||||
<ValidatedTextInput
|
||||
instanceId={ '6' }
|
||||
id={ 'test-input' }
|
||||
onChange={ ( value ) => setInputValue( value ) }
|
||||
value={ inputValue }
|
||||
label={ 'Test Input' }
|
||||
required={ true }
|
||||
focusOnMount={ true }
|
||||
validateOnMount={ validateOnMount }
|
||||
/>
|
||||
);
|
||||
};
|
||||
const { rerender } = await render( <TestComponent /> );
|
||||
const textInputElement = await screen.getByLabelText(
|
||||
'Test Input'
|
||||
);
|
||||
await expect( textInputElement ).toHaveFocus();
|
||||
await expect( setValidationErrors ).not.toHaveBeenCalled();
|
||||
|
||||
await rerender( <TestComponent validateOnMount={ true } /> );
|
||||
await expect( textInputElement ).toHaveFocus();
|
||||
await expect( setValidationErrors ).not.toHaveBeenCalled();
|
||||
} );
|
||||
it( 'does not validate when validateOnMount is false and focusOnMount is true', async () => {
|
||||
const setValidationErrors = jest.fn();
|
||||
wpData.useDispatch.mockImplementation( ( storeName: string ) => {
|
||||
if ( storeName === VALIDATION_STORE_KEY ) {
|
||||
return {
|
||||
...jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName ),
|
||||
setValidationErrors,
|
||||
};
|
||||
}
|
||||
return jest
|
||||
.requireActual( '@wordpress/data' )
|
||||
.useDispatch( storeName );
|
||||
} );
|
||||
|
||||
const TestComponent = () => {
|
||||
const [ inputValue, setInputValue ] = useState( '' );
|
||||
return (
|
||||
<ValidatedTextInput
|
||||
instanceId={ '6' }
|
||||
id={ 'test-input' }
|
||||
onChange={ ( value ) => setInputValue( value ) }
|
||||
value={ inputValue }
|
||||
label={ 'Test Input' }
|
||||
required={ true }
|
||||
focusOnMount={ true }
|
||||
validateOnMount={ false }
|
||||
/>
|
||||
);
|
||||
};
|
||||
await render( <TestComponent /> );
|
||||
const textInputElement = await screen.getByLabelText(
|
||||
'Test Input'
|
||||
);
|
||||
await expect( textInputElement ).toHaveFocus();
|
||||
await expect( setValidationErrors ).not.toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -49,6 +49,8 @@ interface ValidatedTextInputProps
|
|||
customValidation?:
|
||||
| ( ( inputObject: HTMLInputElement ) => boolean )
|
||||
| undefined;
|
||||
// Whether validation should run when focused - only has an effect when focusOnMount is also true.
|
||||
validateOnMount?: boolean | undefined;
|
||||
}
|
||||
|
||||
const ValidatedTextInput = ( {
|
||||
|
@ -64,6 +66,7 @@ const ValidatedTextInput = ( {
|
|||
value = '',
|
||||
customValidation,
|
||||
label,
|
||||
validateOnMount = true,
|
||||
...rest
|
||||
}: ValidatedTextInputProps ): JSX.Element => {
|
||||
const [ isPristine, setIsPristine ] = useState( true );
|
||||
|
@ -164,9 +167,20 @@ const ValidatedTextInput = ( {
|
|||
if ( focusOnMount ) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
validateInput( true );
|
||||
|
||||
// if validateOnMount is false, only validate input if focusOnMount is also false
|
||||
if ( validateOnMount || ! focusOnMount ) {
|
||||
validateInput( true );
|
||||
}
|
||||
|
||||
setIsPristine( false );
|
||||
}, [ focusOnMount, isPristine, setIsPristine, validateInput ] );
|
||||
}, [
|
||||
validateOnMount,
|
||||
focusOnMount,
|
||||
isPristine,
|
||||
setIsPristine,
|
||||
validateInput,
|
||||
] );
|
||||
|
||||
// Remove validation errors when unmounted.
|
||||
useEffect( () => {
|
||||
|
|
Loading…
Reference in New Issue