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' ); } ); } ); } );