Skip to content

Commit

Permalink
DropdownMenu: use KeyboardEvent.code, refactor tests to model RTL and…
Browse files Browse the repository at this point in the history
… user-event (#43439)

* DropdownMenu: use KeyboardEvent.code instead of KetboardEvent.keyCode

* DropdownMenu: refactor tests to use modern RTl and user-event

* CHANGELOG

* Fix unit test to also use code instead of keyCode

* Update packages/components/CHANGELOG.md

Co-authored-by: Marin Atanasov <[email protected]>

* Make sure menu items are rendered inside the menu element

* Remove unused `onKeyDown` from native component

Co-authored-by: Marin Atanasov <[email protected]>
  • Loading branch information
ciampo and tyxla authored Aug 25, 2022
1 parent 53da761 commit 032d654
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { shallow, mount } from 'enzyme';
*/
import { useSelect } from '@wordpress/data';
import { registerBlockType, unregisterBlockType } from '@wordpress/blocks';
import { DOWN } from '@wordpress/keycodes';
import { Button } from '@wordpress/components';
import { copy } from '@wordpress/icons';

Expand Down Expand Up @@ -180,7 +179,7 @@ describe( 'BlockSwitcherDropdownMenu', () => {
const onToggleStub = jest.fn();
const mockKeyDown = {
preventDefault: () => {},
keyCode: DOWN,
code: 'ArrowDown',
};

afterEach( () => {
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
- `Tooltip`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)).
- `TreeGrid`: Refactor away from `_.includes()` ([#43518](https://github.com/WordPress/gutenberg/pull/43518/)).
- `FormTokenField`: use `KeyboardEvent.code`, refactor tests to modern RTL and `user-event` ([#43442](https://github.com/WordPress/gutenberg/pull/43442/)).
- `DropdownMenu`: use `KeyboardEvent.code`, refactor tests to model RTL and `user-event` ([#43439](https://github.com/WordPress/gutenberg/pull/43439/)).

### Experimental

Expand Down
3 changes: 1 addition & 2 deletions packages/components/src/dropdown-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { DOWN } from '@wordpress/keycodes';
import { menu } from '@wordpress/icons';

/**
Expand Down Expand Up @@ -87,7 +86,7 @@ function DropdownMenu( dropdownMenuProps ) {
return;
}

if ( ! isOpen && event.keyCode === DOWN ) {
if ( ! isOpen && event.code === 'ArrowDown' ) {
event.preventDefault();
onToggle();
}
Expand Down
13 changes: 0 additions & 13 deletions packages/components/src/dropdown-menu/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Platform } from 'react-native';
/**
* WordPress dependencies
*/
import { DOWN } from '@wordpress/keycodes';
import { BottomSheet, PanelBody } from '@wordpress/components';
import { withPreferredColorScheme } from '@wordpress/compose';
import { menu } from '@wordpress/icons';
Expand Down Expand Up @@ -76,12 +75,6 @@ function DropdownMenu( {
className={ classnames( 'components-dropdown-menu', className ) }
popoverProps={ mergedPopoverProps }
renderToggle={ ( { isOpen, onToggle } ) => {
const openOnArrowDown = ( event ) => {
if ( ! isOpen && event.keyCode === DOWN ) {
event.preventDefault();
onToggle();
}
};
const mergedToggleProps = mergeProps(
{
className: classnames(
Expand All @@ -104,12 +97,6 @@ function DropdownMenu( {
mergedToggleProps.onClick( event );
}
} }
onKeyDown={ ( event ) => {
openOnArrowDown( event );
if ( mergedToggleProps.onKeyDown ) {
mergedToggleProps.onKeyDown( event );
}
} }
aria-haspopup="true"
aria-expanded={ isOpen }
label={ label }
Expand Down
112 changes: 54 additions & 58 deletions packages/components/src/dropdown-menu/test/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* External dependencies
*/
import { fireEvent, render } from '@testing-library/react';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

/**
* WordPress dependencies
*/
import { DOWN } from '@wordpress/keycodes';
import { arrowLeft, arrowRight, arrowUp, arrowDown } from '@wordpress/icons';

/**
Expand All @@ -15,19 +15,27 @@ import { arrowLeft, arrowRight, arrowUp, arrowDown } from '@wordpress/icons';
import DropdownMenu from '../';
import { MenuItem } from '../../';

function getMenuToggleButton( container ) {
return container.querySelector( '.components-dropdown-menu__toggle' );
}
function getNavigableMenu( container ) {
return container.querySelector( '.components-dropdown-menu__menu' );
}

describe( 'DropdownMenu', () => {
const children = ( { onClose } ) => <MenuItem onClick={ onClose } />;
it( 'should not render when neither controls nor children are assigned', () => {
render( <DropdownMenu /> );

// The button toggle should not even be rendered
expect( screen.queryByRole( 'button' ) ).not.toBeInTheDocument();
} );

it( 'should not render when controls are empty and children is not specified', () => {
render( <DropdownMenu controls={ [] } /> );

// The button toggle should not even be rendered
expect( screen.queryByRole( 'button' ) ).not.toBeInTheDocument();
} );

it( 'should open menu when pressing arrow down on the toggle and the controls prop is used to define menu items', async () => {
const user = userEvent.setup( {
advanceTimers: jest.advanceTimersByTime,
} );

let controls;
beforeEach( () => {
controls = [
const controls = [
{
title: 'Up',
icon: arrowUp,
Expand All @@ -49,62 +57,50 @@ describe( 'DropdownMenu', () => {
onClick: jest.fn(),
},
];
} );

describe( 'basic rendering', () => {
it( 'should render a null element when neither controls nor children are assigned', () => {
const { container } = render( <DropdownMenu /> );
render( <DropdownMenu controls={ controls } /> );

expect( container.firstChild ).toBeNull();
} );
// Move focus on the toggle button
await user.tab();

it( 'should render a null element when controls are empty and children is not specified', () => {
const { container } = render( <DropdownMenu controls={ [] } /> );
await user.keyboard( '[ArrowDown]' );

expect( container.firstChild ).toBeNull();
let menu;
await waitFor( () => {
menu = screen.getByRole( 'menu' );
return expect( menu ).toBeVisible();
} );

it( 'should open menu on arrow down (controls)', () => {
const {
container: { firstChild: dropdownMenuContainer },
} = render( <DropdownMenu controls={ controls } /> );

const button = getMenuToggleButton( dropdownMenuContainer );
button.focus();
fireEvent.keyDown( button, {
keyCode: DOWN,
preventDefault: () => {},
} );
const menu = getNavigableMenu( dropdownMenuContainer );
expect( menu ).toBeTruthy();

expect(
dropdownMenuContainer.querySelectorAll(
'.components-dropdown-menu__menu-item'
)
).toHaveLength( controls.length );
} );
expect( within( menu ).getAllByRole( 'menuitem' ) ).toHaveLength(
controls.length
);
} );

it( 'should open menu on arrow down (children)', () => {
const {
container: { firstChild: dropdownMenuContainer },
} = render( <DropdownMenu children={ children } /> );
it( 'should open menu when pressing arrow down on the toggle and the children prop is used to define menu items', async () => {
const user = userEvent.setup( {
advanceTimers: jest.advanceTimersByTime,
} );

const button = getMenuToggleButton( dropdownMenuContainer );
button.focus();
fireEvent.keyDown( button, {
keyCode: DOWN,
preventDefault: () => {},
} );
render(
<DropdownMenu
children={ ( { onClose } ) => <MenuItem onClick={ onClose } /> }
/>
);

expect( getNavigableMenu( dropdownMenuContainer ) ).toBeTruthy();
const button = screen.getByRole( 'button' );
button.focus();

const menuItem = dropdownMenuContainer.querySelector(
'.components-menu-item__button'
);
fireEvent.click( menuItem );
await user.keyboard( '[ArrowDown]' );

expect( getNavigableMenu( dropdownMenuContainer ) ).toBeNull();
let menu;
await waitFor( () => {
menu = screen.getByRole( 'menu' );
return expect( menu ).toBeVisible();
} );

// Clicking the menu item will close the dropdown menu
await user.click( within( menu ).getByRole( 'menuitem' ) );

expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
} );
} );

0 comments on commit 032d654

Please sign in to comment.