diff --git a/packages/components/src/angle-picker-control/angle-circle.js b/packages/components/src/angle-picker-control/angle-circle.js
index 6122cda215daf1..f2a761284d96e2 100644
--- a/packages/components/src/angle-picker-control/angle-circle.js
+++ b/packages/components/src/angle-picker-control/angle-circle.js
@@ -31,7 +31,8 @@ function AngleCircle( { value, onChange, ...props } ) {
// Prevent (drag) mouse events from selecting and accidentally
// triggering actions from other elements.
event.preventDefault();
-
+ // Ensure the input isn't focused as preventDefault would leave it
+ document.activeElement.blur();
onChange( getAngle( centerX, centerY, event.clientX, event.clientY ) );
};
diff --git a/packages/components/src/box-control/test/index.js b/packages/components/src/box-control/test/index.js
index 613aac6092fe65..81b1ab7bd2bf68 100644
--- a/packages/components/src/box-control/test/index.js
+++ b/packages/components/src/box-control/test/index.js
@@ -28,6 +28,7 @@ describe( 'BoxControl', () => {
const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );
+ input.focus();
fireEvent.change( input, { target: { value: '100%' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );
@@ -41,14 +42,17 @@ describe( 'BoxControl', () => {
const { container, getByText } = render( );
const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );
+ const reset = getByText( /Reset/ );
+ input.focus();
fireEvent.change( input, { target: { value: '100px' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );
expect( input.value ).toBe( '100' );
expect( unitSelect.value ).toBe( 'px' );
- fireEvent.click( getByText( /Reset/ ) );
+ reset.focus();
+ fireEvent.click( reset );
expect( input.value ).toBe( '' );
expect( unitSelect.value ).toBe( 'px' );
@@ -68,14 +72,17 @@ describe( 'BoxControl', () => {
const { container, getByText } = render( );
const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );
+ const reset = getByText( /Reset/ );
+ input.focus();
fireEvent.change( input, { target: { value: '100px' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );
expect( input.value ).toBe( '100' );
expect( unitSelect.value ).toBe( 'px' );
- fireEvent.click( getByText( /Reset/ ) );
+ reset.focus();
+ fireEvent.click( reset );
expect( input.value ).toBe( '' );
expect( unitSelect.value ).toBe( 'px' );
@@ -102,14 +109,17 @@ describe( 'BoxControl', () => {
const { container, getByText } = render( );
const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );
+ const reset = getByText( /Reset/ );
+ input.focus();
fireEvent.change( input, { target: { value: '100px' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );
expect( input.value ).toBe( '100' );
expect( unitSelect.value ).toBe( 'px' );
- fireEvent.click( getByText( /Reset/ ) );
+ reset.focus();
+ fireEvent.click( reset );
expect( input.value ).toBe( '' );
expect( unitSelect.value ).toBe( 'px' );
diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js
index bd6cb82d8be982..e56e86e8a384b5 100644
--- a/packages/components/src/input-control/index.js
+++ b/packages/components/src/input-control/index.js
@@ -33,9 +33,7 @@ export function InputControl(
isPressEnterToChange = false,
label,
labelPosition = 'top',
- onBlur = noop,
onChange = noop,
- onFocus = noop,
onValidate = noop,
onKeyDown = noop,
prefix,
@@ -51,16 +49,6 @@ export function InputControl(
const id = useUniqueId( idProp );
const classes = classNames( 'components-input-control', className );
- const handleOnBlur = ( event ) => {
- onBlur( event );
- setIsFocused( false );
- };
-
- const handleOnFocus = ( event ) => {
- onFocus( event );
- setIsFocused( true );
- };
-
return (
state,
value: valueProp,
...props
@@ -63,35 +66,27 @@ function InputField(
const { _event, value, isDragging, isDirty } = state;
- const valueRef = useRef( value );
const dragCursor = useDragCursor( isDragging, dragDirection );
- useEffect( () => {
- /**
- * Handles syncing incoming value changes with internal state.
- * This effectively enables a "controlled" state.
- * https://reactjs.org/docs/forms.html#controlled-components
- */
- if ( valueProp !== valueRef.current ) {
- update( valueProp );
- valueRef.current = valueProp;
-
- // Quick return to avoid firing the onChange callback
+ /*
+ * Syncs value state using the focus state to determine the direction.
+ * Without focus it updates the value from the props. With focus it
+ * propagates the value and event through onChange.
+ */
+ useUpdateEffect( () => {
+ if ( valueProp === value ) {
return;
}
-
- /**
- * Fires the onChange callback when internal state value changes.
- */
- if ( value !== valueRef.current && ! isDirty ) {
+ if ( ! isFocused ) {
+ update( valueProp );
+ } else if ( ! isDirty ) {
onChange( value, { event: _event } );
-
- valueRef.current = value;
}
- }, [ value, isDirty, valueProp ] );
+ }, [ value, isDirty, isFocused, valueProp ] );
const handleOnBlur = ( event ) => {
onBlur( event );
+ setIsFocused( false );
/**
* If isPressEnterToChange is set, this commits the value to
@@ -108,6 +103,7 @@ function InputField(
const handleOnFocus = ( event ) => {
onFocus( event );
+ setIsFocused( true );
};
const handleOnChange = ( event ) => {
diff --git a/packages/components/src/input-control/test/index.js b/packages/components/src/input-control/test/index.js
index 86615318497fc6..4198a2308fd473 100644
--- a/packages/components/src/input-control/test/index.js
+++ b/packages/components/src/input-control/test/index.js
@@ -49,7 +49,7 @@ describe( 'InputControl', () => {
render( );
const input = getInput();
-
+ input.focus();
fireEvent.change( input, { target: { value: 'There' } } );
expect( input.value ).toBe( 'There' );
@@ -59,21 +59,23 @@ describe( 'InputControl', () => {
it( 'should work as a controlled component', () => {
const spy = jest.fn();
const { rerender } = render(
-
+
);
const input = getInput();
- fireEvent.change( input, { target: { value: 'State' } } );
+ input.focus();
+ fireEvent.change( input, { target: { value: 'two' } } );
- // Assuming is controlled...
+ // Ensuring is controlled
+ fireEvent.blur( input );
// Updating the value
- rerender( );
+ rerender( );
- expect( input.value ).toBe( 'New' );
+ expect( input.value ).toBe( 'three' );
- /**
+ /*
* onChange called only once. onChange is not called when a
* parent component explicitly passed a (new value) change down to
* the .
@@ -89,7 +91,7 @@ describe( 'InputControl', () => {
const input = getInput();
- // Assuming is controlled...
+ // Assuming is controlled (not focused)
// Updating the value
rerender( );
diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js
index 8c006647bd2ff3..883b7f6d9591fd 100644
--- a/packages/components/src/number-control/test/index.js
+++ b/packages/components/src/number-control/test/index.js
@@ -1,8 +1,7 @@
/**
* External dependencies
*/
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act, Simulate } from 'react-dom/test-utils';
+import { render, screen, fireEvent } from '@testing-library/react';
/**
* WordPress dependencies
@@ -13,24 +12,16 @@ import { UP, DOWN, ENTER } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
-import NumberControl from '../';
+import BaseNumberControl from '../';
-let container = null;
+const getInput = () => screen.getByTestId( 'input' );
-function getInput() {
- return container.querySelector( 'input' );
-}
-
-beforeEach( () => {
- container = document.createElement( 'div' );
- document.body.appendChild( container );
-} );
+const fireKeyDown = ( data ) =>
+ fireEvent.keyDown( document.activeElement || document.body, data );
-afterEach( () => {
- unmountComponentAtNode( container );
- container.remove();
- container = null;
-} );
+const NumberControl = ( props ) => (
+
+);
function StatefulNumberControl( props ) {
const [ value, setValue ] = useState( props.value );
@@ -48,86 +39,56 @@ function StatefulNumberControl( props ) {
describe( 'NumberControl', () => {
describe( 'Basic rendering', () => {
it( 'should render', () => {
- act( () => {
- render( , container );
- } );
-
- const input = getInput();
-
- expect( input ).not.toBeNull();
+ render( );
+ expect( getInput() ).not.toBeNull();
} );
it( 'should render custom className', () => {
- act( () => {
- render( , container );
- } );
-
- const input = container.querySelector( '.hello' );
-
- expect( input ).toBeTruthy();
+ render( );
+ expect( getInput() ).toBeTruthy();
} );
} );
describe( 'onChange handling', () => {
it( 'should provide onChange callback with number value', () => {
const spy = jest.fn();
- act( () => {
- render(
- ,
- container
- );
- } );
-
- const input = getInput();
-
- input.value = 10;
- act( () => {
- Simulate.change( input );
- } );
+ render(
+ spy( v ) } />
+ );
- const changeValue = spy.mock.calls[ 0 ][ 0 ];
+ const input = getInput();
+ input.focus();
+ fireEvent.change( input, { target: { value: 10 } } );
- expect( changeValue ).toBe( '10' );
+ expect( spy ).toHaveBeenCalledWith( '10' );
} );
} );
describe( 'Validation', () => {
it( 'should clamp value within range on ENTER keypress', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const input = getInput();
- input.value = -100;
-
- act( () => {
- Simulate.change( input );
- Simulate.keyDown( input, { keyCode: ENTER } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: -100 } } );
+ fireKeyDown( { keyCode: ENTER } );
/**
* This is zero because the value has been adjusted to
* respect the min/max range of the input.
*/
+
expect( input.value ).toBe( '0' );
} );
it( 'should parse to number value on ENTER keypress', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
- input.value = '10 abc';
-
- act( () => {
- Simulate.change( input );
- Simulate.keyDown( input, { keyCode: ENTER } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: '10 abc' } } );
+ fireKeyDown( { keyCode: ENTER } );
expect( input.value ).toBe( '0' );
} );
@@ -136,122 +97,83 @@ describe( 'NumberControl', () => {
describe( 'Key UP interactions', () => {
it( 'should fire onKeyDown callback', () => {
const spy = jest.fn();
- act( () => {
- render(
- ,
- container
- );
- } );
- const input = getInput();
+ render( );
- act( () => {
- Simulate.keyDown( input, { keyCode: UP } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: UP } );
expect( spy ).toHaveBeenCalled();
} );
it( 'should increment by step on key UP press', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP } );
expect( input.value ).toBe( '6' );
} );
it( 'should increment from a negative value', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP } );
expect( input.value ).toBe( '-4' );
} );
it( 'should increment by shiftStep on key UP + shift press', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP, shiftKey: true } );
expect( input.value ).toBe( '20' );
} );
it( 'should increment by custom shiftStep on key UP + shift press', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP, shiftKey: true } );
expect( input.value ).toBe( '100' );
} );
it( 'should increment but be limited by max on shiftStep', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP, shiftKey: true } );
expect( input.value ).toBe( '99' );
} );
it( 'should not increment by shiftStep if disabled', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: UP, shiftKey: true } );
expect( input.value ).toBe( '6' );
} );
@@ -260,119 +182,82 @@ describe( 'NumberControl', () => {
describe( 'Key DOWN interactions', () => {
it( 'should fire onKeyDown callback', () => {
const spy = jest.fn();
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
- const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: DOWN } );
expect( spy ).toHaveBeenCalled();
} );
it( 'should decrement by step on key DOWN press', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN } );
expect( input.value ).toBe( '4' );
} );
it( 'should decrement from a negative value', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN } );
expect( input.value ).toBe( '-6' );
} );
it( 'should decrement by shiftStep on key DOWN + shift press', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN, shiftKey: true } );
expect( input.value ).toBe( '0' );
} );
it( 'should decrement by custom shiftStep on key DOWN + shift press', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN, shiftKey: true } );
expect( input.value ).toBe( '-100' );
} );
it( 'should decrement but be limited by min on shiftStep', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN, shiftKey: true } );
expect( input.value ).toBe( '4' );
} );
it( 'should not decrement by shiftStep if disabled', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } );
- } );
+ input.focus();
+ fireKeyDown( { keyCode: DOWN, shiftKey: true } );
expect( input.value ).toBe( '4' );
} );
diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md
index c42614583dc2e1..f5e38538369823 100644
--- a/packages/components/src/range-control/README.md
+++ b/packages/components/src/range-control/README.md
@@ -213,8 +213,7 @@ const MyRangeControl() {
#### onChange
-A function that receives the new value.
-If allowReset is true, when onChange is called without any parameter passed it should reset the value.
+A function that receives the new value. The value will be less than `max` and more than `min` unless a reset (enabled by `allowReset`) has occured. In which case the value will be either that of `resetFallbackValue` if it has been specified or otherwise `undefined`.
- Type: `function`
- Required: Yes
@@ -222,14 +221,22 @@ If allowReset is true, when onChange is called without any parameter passed it s
#### min
-The minimum value accepted. If smaller values are inserted onChange will not be called and the value gets reverted when blur event fires.
+The minimum `value` allowed.
- Type: `Number`
- Required: No
+- Default: 0
- Platform: Web | Mobile
#### max
+The maximum `value` allowed.
+
+- Type: `Number`
+- Required: No
+- Default: 100
+- Platform: Web | Mobile
+
#### railColor
Customizes the (background) color of the rail element.
@@ -238,12 +245,6 @@ Customizes the (background) color of the rail element.
- Required: No
- Platform: Web
-The maximum value accepted. If higher values are inserted onChange will not be called and the value gets reverted when blur event fires.
-
-- Type: `Number`
-- Required: No
-- Platform: Web | Mobile
-
#### renderTooltipContent
A way to customize the rendered UI of the value. Example:
diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js
index 536b0d84ba0a86..90f55c3c0cd555 100644
--- a/packages/components/src/range-control/index.js
+++ b/packages/components/src/range-control/index.js
@@ -26,13 +26,13 @@ import {
ActionRightWrapper,
AfterIconWrapper,
BeforeIconWrapper,
+ InputNumber,
Root,
Track,
ThumbWrapper,
Thumb,
Wrapper,
} from './styles/range-control-styles';
-import InputField from './input-field';
import { useRTL } from '../utils/rtl';
function RangeControl(
@@ -77,6 +77,7 @@ function RangeControl(
value: valueProp,
initial: initialPosition,
} );
+ const isResetPendent = useRef( false );
const [ showTooltip, setShowTooltip ] = useState( showTooltipProp );
const [ isFocused, setIsFocused ] = useState( false );
@@ -119,17 +120,33 @@ function RangeControl(
const handleOnRangeChange = ( event ) => {
const nextValue = parseFloat( event.target.value );
- handleOnChange( nextValue );
+ setValue( nextValue );
+ onChange( nextValue );
};
const handleOnChange = ( nextValue ) => {
- if ( isNaN( nextValue ) ) {
- handleOnReset();
- return;
+ nextValue = parseFloat( nextValue );
+ setValue( nextValue );
+ /*
+ * Calls onChange only when nextValue is numeric
+ * otherwise may queue a reset for the blur event.
+ */
+ if ( ! isNaN( nextValue ) ) {
+ if ( nextValue < min || nextValue > max ) {
+ nextValue = floatClamp( nextValue, min, max );
+ }
+ onChange( nextValue );
+ isResetPendent.current = false;
+ } else if ( allowReset ) {
+ isResetPendent.current = true;
}
+ };
- setValue( nextValue );
- onChange( nextValue );
+ const handleOnInputNumberBlur = () => {
+ if ( isResetPendent.current ) {
+ handleOnReset();
+ isResetPendent.current = false;
+ }
};
const handleOnReset = () => {
@@ -256,14 +273,16 @@ function RangeControl(
) }
{ withInputField && (
- to be updated independently before the
- * value is applied and propagated. This independent updating action is
- * necessary to accommodate individual keystroke values that may not
- * be considered "valid" (e.g. within the min - max range).
- */
- const [ value, setValue ] = useControlledState( valueProp );
-
- const handleOnReset = ( event ) => {
- onReset( event );
- setValue( '' );
- };
-
- const handleOnCommit = ( event ) => {
- const nextValue = parseFloat( event.target.value );
-
- if ( isNaN( nextValue ) ) {
- handleOnReset();
- return;
- }
-
- setValue( nextValue );
- onChange( nextValue );
- };
-
- const handleOnBlur = ( event ) => {
- onBlur( event );
- handleOnCommit( event );
- };
-
- const handleOnChange = ( next ) => {
- handleOnCommit( { target: { value: next } } );
- };
-
- const handleOnKeyDown = ( event ) => {
- const { keyCode } = event;
- onKeyDown( event );
-
- if ( keyCode === ENTER ) {
- event.preventDefault();
- handleOnCommit( event );
- }
- };
-
- return (
-
- );
-}
diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js
index e3d75083f4e4f4..2ccb8d90293fa9 100644
--- a/packages/components/src/range-control/test/index.js
+++ b/packages/components/src/range-control/test/index.js
@@ -27,10 +27,11 @@ describe( 'RangeControl', () => {
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
+ rangeInput.focus();
fireEvent.change( rangeInput, { target: { value: '5' } } );
+ numberInput.focus();
fireEvent.change( numberInput, { target: { value: '10' } } );
- fireEvent.blur( numberInput );
expect( onChange ).toHaveBeenCalledWith( 5 );
expect( onChange ).toHaveBeenCalledWith( 10 );
@@ -57,7 +58,7 @@ describe( 'RangeControl', () => {
} );
describe( 'validation', () => {
- it( 'should not apply new value is lower than minimum', () => {
+ it( 'should not apply if new value is lower than minimum', () => {
const { container } = render( );
const rangeInput = getRangeInput( container );
@@ -69,7 +70,7 @@ describe( 'RangeControl', () => {
expect( rangeInput.value ).not.toBe( '10' );
} );
- it( 'should not apply new value is greater than maximum', () => {
+ it( 'should not apply if new value is greater than maximum', () => {
const { container } = render( );
const rangeInput = getRangeInput( container );
@@ -81,20 +82,38 @@ describe( 'RangeControl', () => {
expect( rangeInput.value ).not.toBe( '21' );
} );
- it( 'should call onChange if new value is valid', () => {
+ it( 'should not call onChange if new value is invalid', () => {
const onChange = jest.fn();
const { container } = render(
);
+ const numberInput = getNumberInput( container );
+
+ numberInput.focus();
+ fireEvent.change( numberInput, { target: { value: '25e' } } );
+
+ expect( onChange ).not.toHaveBeenCalled();
+ } );
+
+ it( 'should keep invalid values in number input until loss of focus', () => {
+ const onChange = jest.fn();
+ const { container } = render(
+
+ );
+
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
- fireEvent.change( numberInput, { target: { value: '15' } } );
- fireEvent.blur( numberInput );
+ numberInput.focus();
+ fireEvent.change( numberInput, { target: { value: '-1.1' } } );
+
+ expect( numberInput.value ).toBe( '-1.1' );
+ expect( rangeInput.value ).toBe( '-1' );
- expect( onChange ).toHaveBeenCalledWith( 15 );
- expect( rangeInput.value ).toBe( '15' );
+ fireEvent.blur( numberInput );
+ expect( onChange ).toHaveBeenCalledWith( -1 );
+ expect( numberInput.value ).toBe( '-1' );
} );
it( 'should validate when provided a max or min of zero', () => {
@@ -105,6 +124,7 @@ describe( 'RangeControl', () => {
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
+ numberInput.focus();
fireEvent.change( numberInput, { target: { value: '1' } } );
fireEvent.blur( numberInput );
@@ -119,19 +139,15 @@ describe( 'RangeControl', () => {
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
- fireEvent.change( numberInput, { target: { value: '-101' } } );
- fireEvent.blur( numberInput );
+ numberInput.focus();
+ fireEvent.change( numberInput, { target: { value: '-101' } } );
expect( rangeInput.value ).toBe( '-100' );
fireEvent.change( numberInput, { target: { value: '-49' } } );
- fireEvent.blur( numberInput );
-
expect( rangeInput.value ).toBe( '-50' );
fireEvent.change( numberInput, { target: { value: '-50' } } );
- fireEvent.blur( numberInput );
-
expect( rangeInput.value ).toBe( '-50' );
} );
@@ -148,14 +164,13 @@ describe( 'RangeControl', () => {
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
+ numberInput.focus();
fireEvent.change( numberInput, { target: { value: '0.125' } } );
- fireEvent.blur( numberInput );
expect( onChange ).toHaveBeenCalledWith( 0.125 );
expect( rangeInput.value ).toBe( '0.125' );
fireEvent.change( numberInput, { target: { value: '0.225' } } );
- fireEvent.blur( numberInput );
expect( onChange ).toHaveBeenCalledWith( 0.225 );
expect( rangeInput.value ).toBe( '0.225' );
@@ -229,13 +244,14 @@ describe( 'RangeControl', () => {
const rangeInput = getRangeInput( container );
const numberInput = getNumberInput( container );
+ rangeInput.focus();
fireEvent.change( rangeInput, { target: { value: 13 } } );
expect( rangeInput.value ).toBe( '13' );
expect( numberInput.value ).toBe( '13' );
+ numberInput.focus();
fireEvent.change( numberInput, { target: { value: 7 } } );
- fireEvent.blur( numberInput );
expect( rangeInput.value ).toBe( '7' );
expect( numberInput.value ).toBe( '7' );
diff --git a/packages/components/src/unit-control/test/index.js b/packages/components/src/unit-control/test/index.js
index 8eac58f0dfdbaf..21d102a6f97e34 100644
--- a/packages/components/src/unit-control/test/index.js
+++ b/packages/components/src/unit-control/test/index.js
@@ -1,46 +1,32 @@
/**
* External dependencies
*/
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act, Simulate } from 'react-dom/test-utils';
-import { render as testRender } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
/**
* WordPress dependencies
*/
-import { UP, DOWN } from '@wordpress/keycodes';
+import { UP, DOWN, ENTER } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
import UnitControl from '../';
-let container = null;
-
-beforeEach( () => {
- container = document.createElement( 'div' );
- document.body.appendChild( container );
-} );
-
-afterEach( () => {
- unmountComponentAtNode( container );
- container.remove();
- container = null;
-} );
-
const getComponent = () =>
- container.querySelector( '.components-unit-control' );
+ document.body.querySelector( '.components-unit-control' );
const getInput = () =>
- container.querySelector( '.components-unit-control input' );
+ document.body.querySelector( '.components-unit-control input' );
const getSelect = () =>
- container.querySelector( '.components-unit-control select' );
+ document.body.querySelector( '.components-unit-control select' );
+
+const fireKeyDown = ( data ) =>
+ fireEvent.keyDown( document.activeElement || document.body, data );
describe( 'UnitControl', () => {
describe( 'Basic rendering', () => {
it( 'should render', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
const select = getSelect();
@@ -49,9 +35,7 @@ describe( 'UnitControl', () => {
} );
it( 'should render custom className', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const el = getComponent();
@@ -59,9 +43,7 @@ describe( 'UnitControl', () => {
} );
it( 'should not render select, if units are disabled', () => {
- act( () => {
- render( , container );
- } );
+ render( );
const input = getInput();
const select = getSelect();
@@ -72,61 +54,39 @@ describe( 'UnitControl', () => {
describe( 'Value', () => {
it( 'should update value on change', () => {
- let state = 50;
- const setState = ( nextState ) => ( state = nextState );
+ let state = '50px';
+ const setState = jest.fn( ( value ) => ( state = value ) );
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const input = getInput();
+ input.focus();
+ fireEvent.change( input, { target: { value: 62 } } );
- act( () => {
- Simulate.change( input, { target: { value: 62 } } );
- } );
-
+ expect( setState ).toHaveBeenCalledTimes( 1 );
expect( state ).toBe( '62px' );
} );
it( 'should increment value on UP press', () => {
- let state = 50;
+ let state = '50px';
const setState = ( nextState ) => ( state = nextState );
- act( () => {
- render(
- ,
- container
- );
- } );
-
- const input = getInput();
+ render( );
- act( () => {
- Simulate.keyDown( input, { keyCode: UP } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: UP } );
expect( state ).toBe( '51px' );
} );
it( 'should increment value on UP + SHIFT press, with step', () => {
- let state = 50;
+ let state = '50px';
const setState = ( nextState ) => ( state = nextState );
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
- const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: UP, shiftKey: true } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: UP, shiftKey: true } );
expect( state ).toBe( '60px' );
} );
@@ -135,18 +95,10 @@ describe( 'UnitControl', () => {
let state = 50;
const setState = ( nextState ) => ( state = nextState );
- act( () => {
- render(
- ,
- container
- );
- } );
-
- const input = getInput();
+ render( );
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: DOWN } );
expect( state ).toBe( '49px' );
} );
@@ -155,18 +107,10 @@ describe( 'UnitControl', () => {
let state = 50;
const setState = ( nextState ) => ( state = nextState );
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
- const input = getInput();
-
- act( () => {
- Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } );
- } );
+ getInput().focus();
+ fireKeyDown( { keyCode: DOWN, shiftKey: true } );
expect( state ).toBe( '40px' );
} );
@@ -177,18 +121,11 @@ describe( 'UnitControl', () => {
let state = 'px';
const setState = ( nextState ) => ( state = nextState );
- act( () => {
- render(
- ,
- container
- );
- } );
+ render( );
const select = getSelect();
-
- act( () => {
- Simulate.change( select, { target: { value: 'em' } } );
- } );
+ select.focus();
+ fireEvent.change( select, { target: { value: 'em' } } );
expect( state ).toBe( 'em' );
} );
@@ -198,9 +135,8 @@ describe( 'UnitControl', () => {
{ value: 'pt', label: 'pt', default: 0 },
{ value: 'vmax', label: 'vmax', default: 10 },
];
- act( () => {
- render( , container );
- } );
+
+ render( );
const select = getSelect();
const options = select.querySelectorAll( 'option' );
@@ -221,29 +157,24 @@ describe( 'UnitControl', () => {
{ value: 'pt', label: 'pt', default: 25 },
{ value: 'vmax', label: 'vmax', default: 75 },
];
- act( () => {
- render(
- ,
- container
- );
- } );
+
+ render(
+
+ );
const select = getSelect();
+ select.focus();
- act( () => {
- Simulate.change( select, { target: { value: 'vmax' } } );
- } );
+ fireEvent.change( select, { target: { value: 'vmax' } } );
expect( state ).toBe( '75vmax' );
- act( () => {
- Simulate.change( select, { target: { value: 'pt' } } );
- } );
+ fireEvent.change( select, { target: { value: 'pt' } } );
expect( state ).toBe( '25pt' );
} );
@@ -256,146 +187,136 @@ describe( 'UnitControl', () => {
{ value: 'pt', label: 'pt', default: 25 },
{ value: 'vmax', label: 'vmax', default: 75 },
];
- act( () => {
- render(
- ,
- container
- );
- } );
+
+ render(
+
+ );
const select = getSelect();
+ select.focus();
- act( () => {
- Simulate.change( select, { target: { value: 'vmax' } } );
- } );
+ fireEvent.change( select, { target: { value: 'vmax' } } );
expect( state ).toBe( '50vmax' );
- act( () => {
- Simulate.change( select, { target: { value: 'pt' } } );
- } );
+ fireEvent.change( select, { target: { value: 'pt' } } );
expect( state ).toBe( '50pt' );
} );
} );
+
describe( 'Unit Parser', () => {
let state = '10px';
- const setState = ( nextState ) => ( state = nextState );
+ const setState = jest.fn( ( nextState ) => ( state = nextState ) );
it( 'should parse unit from input', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.change( input, { target: { value: '55 em' } } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: '55 em' } } );
+ fireKeyDown( { keyCode: ENTER } );
expect( state ).toBe( '55em' );
} );
it( 'should parse PX unit from input', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.change( input, { target: { value: '61 PX' } } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: '61 PX' } } );
+ fireKeyDown( { keyCode: ENTER } );
expect( state ).toBe( '61px' );
} );
it( 'should parse EM unit from input', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.change( input, { target: { value: '55 em' } } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: '55 em' } } );
+ fireKeyDown( { keyCode: ENTER } );
expect( state ).toBe( '55em' );
} );
it( 'should parse % unit from input', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.change( input, { target: { value: '-10 %' } } );
- } );
+ input.focus();
+ fireEvent.change( input, { target: { value: '-10 %' } } );
+ fireKeyDown( { keyCode: ENTER } );
expect( state ).toBe( '-10%' );
} );
it( 'should parse REM unit from input', () => {
- act( () => {
- render(
- ,
- container
- );
- } );
+ render(
+
+ );
const input = getInput();
-
- act( () => {
- Simulate.change( input, {
- target: { value: '123 rEm ' },
- } );
+ input.focus();
+ fireEvent.change( input, {
+ target: { value: '123 rEm ' },
} );
+ fireKeyDown( { keyCode: ENTER } );
expect( state ).toBe( '123rem' );
} );
it( 'should update unit after initial render and with new unit prop', () => {
- const { container: testContainer, rerender } = testRender(
-
- );
+ const { rerender } = render( );
- const select = testContainer.querySelector( 'select' );
+ const select = getSelect();
expect( select.value ).toBe( '%' );
- rerender( );
+ rerender( );
expect( select.value ).toBe( 'em' );
} );
it( 'should fallback to default unit if parsed unit is invalid', () => {
- const { container: testContainer } = testRender(
-
- );
-
- const select = testContainer.querySelector( 'select' );
+ render( );
- expect( select.value ).toBe( 'px' );
+ expect( getSelect().value ).toBe( 'px' );
} );
} );
} );