diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9f3731a282d262..6ac86a4e483bc9 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug Fixes +- `ComboboxControl`: Fix ComboboxControl reset button when using the keyboard. ([#63410](https://github.com/WordPress/gutenberg/pull/63410)) - `Button`: Never apply `aria-disabled` to anchor ([#63376](https://github.com/WordPress/gutenberg/pull/63376)). ### Internal diff --git a/packages/components/src/combobox-control/index.tsx b/packages/components/src/combobox-control/index.tsx index d22bbba2a9d24a..a39a5dc1fc541a 100644 --- a/packages/components/src/combobox-control/index.tsx +++ b/packages/components/src/combobox-control/index.tsx @@ -267,6 +267,15 @@ function ComboboxControl( props: ComboboxControlProps ) { inputContainer.current?.focus(); }; + // Stop propagation of the keydown event when pressing Enter on the Reset + // button to prevent calling the onKeydown callback on the container div + // element which actually sets the selected suggestion. + const handleResetStopPropagation: React.KeyboardEventHandler< + HTMLButtonElement + > = ( event ) => { + event.stopPropagation(); + }; + // Update current selections when the filter input changes. useEffect( () => { const hasMatchingSuggestions = matchingSuggestions.length > 0; @@ -350,6 +359,7 @@ function ComboboxControl( props: ComboboxControlProps ) { // eslint-disable-next-line no-restricted-syntax disabled={ ! value } onClick={ handleOnReset } + onKeyDown={ handleResetStopPropagation } label={ __( 'Reset' ) } /> diff --git a/packages/components/src/combobox-control/test/index.tsx b/packages/components/src/combobox-control/test/index.tsx index 37802e4669c244..76ce9cc4724c54 100644 --- a/packages/components/src/combobox-control/test/index.tsx +++ b/packages/components/src/combobox-control/test/index.tsx @@ -89,7 +89,6 @@ describe.each( [ ); const label = getLabel( defaultLabelText ); - expect( label ).toBeInTheDocument(); expect( label ).toBeVisible(); } ); @@ -306,4 +305,131 @@ describe.each( [ expect( onChangeSpy ).toHaveBeenCalledWith( targetOption.value ); expect( input ).toHaveValue( targetOption.label ); } ); + + it( 'should render with Reset button disabled', () => { + render( + + ); + + const resetButton = screen.getByRole( 'button', { name: 'Reset' } ); + + expect( resetButton ).toBeVisible(); + expect( resetButton ).toBeDisabled(); + } ); + + it( 'should reset input when clicking the Reset button', async () => { + const user = await userEvent.setup(); + const targetOption = timezones[ 13 ]; + + render( + + ); + + // Pressing tab selects the input and shows the options. + await user.tab(); + // Type enough characters to ensure a predictable search result. + await user.keyboard( getOptionSearchString( targetOption ) ); + // Pressing Enter/Return selects the currently focused option. + await user.keyboard( '{Enter}' ); + + const input = getInput( defaultLabelText ); + + expect( input ).toHaveValue( targetOption.label ); + + const resetButton = screen.getByRole( 'button', { name: 'Reset' } ); + + expect( resetButton ).toBeEnabled(); + + await user.click( resetButton ); + + expect( input ).toHaveValue( '' ); + expect( resetButton ).toBeDisabled(); + expect( input ).toHaveFocus(); + } ); + + it( 'should reset input when pressing the Reset button with the Enter key', async () => { + const user = await userEvent.setup(); + const targetOption = timezones[ 13 ]; + + render( + + ); + + // Pressing tab selects the input and shows the options. + await user.tab(); + // Type enough characters to ensure a predictable search result. + await user.keyboard( getOptionSearchString( targetOption ) ); + // Pressing Enter/Return selects the currently focused option. + await user.keyboard( '{Enter}' ); + + const input = getInput( defaultLabelText ); + + expect( input ).toHaveValue( targetOption.label ); + + // Pressing tab moves focus to the Reset buttons + await user.tab(); + + const resetButton = screen.getByRole( 'button', { name: 'Reset' } ); + + // If the button has focus that implies it is enabled. + expect( resetButton ).toHaveFocus(); + + // Pressing Enter/Return resets the input. + await user.keyboard( '{Enter}' ); + + expect( input ).toHaveValue( '' ); + expect( resetButton ).toBeDisabled(); + expect( input ).toHaveFocus(); + } ); + + it( 'should reset input when pressing the Reset button with the Spacebar key', async () => { + const user = await userEvent.setup(); + const targetOption = timezones[ 13 ]; + + render( + + ); + + // Pressing tab selects the input and shows the options. + await user.tab(); + // Type enough characters to ensure a predictable search result. + await user.keyboard( getOptionSearchString( targetOption ) ); + // Pressing Enter/Return selects the currently focused option. + await user.keyboard( '{Enter}' ); + + const input = getInput( defaultLabelText ); + + expect( input ).toHaveValue( targetOption.label ); + + // Pressing tab moves focus to the Reset buttons. + await user.tab(); + + const resetButton = screen.getByRole( 'button', { name: 'Reset' } ); + + // If the button has focus that implies it is enabled. + expect( resetButton ).toHaveFocus(); + + // Pressing Spacebar resets the input. + await user.keyboard( '[Space]' ); + + expect( input ).toHaveValue( '' ); + expect( resetButton ).toBeDisabled(); + expect( input ).toHaveFocus(); + } ); } );