Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[unit-test]: overflow menu #17875

Merged
merged 30 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c2bf0fa
test: increase unit test coverage of overflow menu
divya-281 Oct 24, 2024
e84f1ed
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 24, 2024
33b8b09
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 26, 2024
2373149
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 28, 2024
2b4cbbd
test: increase coverage
divya-281 Oct 28, 2024
3197c74
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 28, 2024
37bdd65
Merge branch 'main' into overflow-menu-unit-tests
preetibansalui Oct 29, 2024
36e3015
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 30, 2024
ec2387a
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Oct 30, 2024
9b14412
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 4, 2024
b95fba0
test: increase covergae
divya-281 Nov 4, 2024
e7c120f
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 5, 2024
2890054
test: add test for escape button
divya-281 Nov 5, 2024
27fbb40
test: add test for arrowup focus
divya-281 Nov 5, 2024
2423ac9
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 6, 2024
1223b9b
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 6, 2024
34d76cb
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 11, 2024
192d5ba
test: increase coverage
divya-281 Nov 11, 2024
ef42589
test: increase coverage
divya-281 Nov 11, 2024
4cebc1e
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 12, 2024
a6011d4
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 12, 2024
89e67ea
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 14, 2024
a6889b8
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 15, 2024
cbe9ec9
refactor: commenting unused code
divya-281 Nov 15, 2024
2603d63
Update packages/react/src/components/OverflowMenu/OverflowMenu.tsx
tay1orjones Nov 15, 2024
582be5a
Merge branch 'main' into overflow-menu-unit-tests
tay1orjones Nov 15, 2024
3e7d4ac
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 18, 2024
5a0593b
Merge branch 'main' into overflow-menu-unit-tests
alisonjoseph Nov 18, 2024
91dc6a2
Merge branch 'main' into overflow-menu-unit-tests
divya-281 Nov 19, 2024
f3493fd
Merge remote-tracking branch 'origin/main' into overflow-menu-unit-tests
divya-281 Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 194 additions & 3 deletions packages/react/src/components/OverflowMenu/OverflowMenu-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

import { Filter } from '@carbon/icons-react';
import OverflowMenu from './OverflowMenu';
import OverflowMenuItem from '../OverflowMenuItem';
import { Filter } from '@carbon/icons-react';
import React from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen, fireEvent } from '@testing-library/react';

describe('OverflowMenu', () => {
describe('Renders as expected', () => {
const closeMenuMock = jest.fn();
it('should support a custom `className` prop on the button element', () => {
render(
<OverflowMenu open aria-label="Overflow menu" className="extra-class">
Expand Down Expand Up @@ -211,4 +213,193 @@ describe('OverflowMenu', () => {
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
it('should not open menu when disabled', async () => {
render(
<OverflowMenu aria-label="Overflow menu" className="extra-class" disabled>
<OverflowMenuItem className="test-child" itemText="one" />
<OverflowMenuItem className="test-child" itemText="two" />
</OverflowMenu>
);

const button = screen.getByRole('button');
await userEvent.click(button);
expect(button).toHaveAttribute('aria-expanded', 'false');
});
it('should close the menu when clicking outside', async () => {
render(
<div>
<OverflowMenu aria-label="Overflow menu" className="extra-class">
<OverflowMenuItem className="test-child" itemText="one" />
<OverflowMenuItem className="test-child" itemText="two" />
</OverflowMenu>
<div data-testid="outside-element">Outside Element</div>
</div>
);

const button = screen.getByRole('button');
await userEvent.click(button);
expect(button).toHaveAttribute('aria-expanded', 'true');
await userEvent.click(screen.getByTestId('outside-element'));
expect(button).toHaveAttribute('aria-expanded', 'false');
});
it('should set aria-label for the icon using iconDescription prop', () => {
const iconDescription = 'custom icon description';
render(
<OverflowMenu
aria-label="Overflow menu"
className="extra-class"
iconDescription={iconDescription}>
<OverflowMenuItem className="test-child" itemText="one" />
<OverflowMenuItem className="test-child" itemText="two" />
</OverflowMenu>
);
const button = screen.getByRole('button', { name: iconDescription });
const svgIcon = button.querySelector('.cds--overflow-menu__icon');
expect(svgIcon).toHaveAttribute('aria-label', iconDescription);
});
it('should align menu based on direction prop', async () => {
const { rerender } = render(
<OverflowMenu
direction="top"
iconDescription="custom-icon"
className="extra-class">
<OverflowMenuItem className="test-child" itemText="one" />
<OverflowMenuItem className="test-child" itemText="two" />
</OverflowMenu>
);
const button = screen.getByRole('button', { name: 'custom-icon' });
fireEvent.click(button);
const menu = await waitFor(() =>
screen.getByRole('menu', { hidden: true })
);
expect(menu).toHaveAttribute('data-floating-menu-direction', 'top');

rerender(
<OverflowMenu
direction="bottom"
iconDescription="custom-icon"
className="extra-class">
<OverflowMenuItem className="test-child" itemText="one" />
<OverflowMenuItem className="test-child" itemText="two" />
</OverflowMenu>
);
const newMenu = await waitFor(() =>
screen.getByRole('menu', { hidden: true })
);
expect(newMenu).toHaveAttribute('data-floating-menu-direction', 'bottom');
});
it('focuses the next enabled menu item when pressing ArrowDown', async () => {
render(
<OverflowMenu iconDescription="custom-icon" className="extra-class">
<OverflowMenuItem itemText="Item 1" data-testid="menu-item-1" />
<OverflowMenuItem
itemText="Item 2"
disabled
data-testid="menu-item-2"
/>
<OverflowMenuItem itemText="Item 3" data-testid="menu-item-3" />
</OverflowMenu>
);
const button = screen.getByRole('button', { name: 'custom-icon' });
fireEvent.click(button);

const menuItem1 = screen.getByText('Item 1').closest('button');
const menuItem3 = screen.getByText('Item 3').closest('button');

menuItem1.focus();
fireEvent.keyDown(menuItem1, { key: 'ArrowDown', code: 'ArrowDown' });
expect(menuItem3).toHaveFocus();
});
it('focuses the next enabled menu item when pressing ArrowUp', async () => {
render(
<OverflowMenu iconDescription="custom-icon" className="extra-class">
<OverflowMenuItem itemText="Item 1" data-testid="menu-item-1" />
<OverflowMenuItem
itemText="Item 2"
disabled
data-testid="menu-item-2"
/>
<OverflowMenuItem itemText="Item 3" data-testid="menu-item-3" />
</OverflowMenu>
);
const button = screen.getByRole('button', { name: 'custom-icon' });
fireEvent.click(button);

const menuItem1 = screen.getByText('Item 1').closest('button');
const menuItem3 = screen.getByText('Item 3').closest('button');

menuItem3.focus();
expect(menuItem3).toHaveFocus();
fireEvent.keyDown(menuItem3, { key: 'ArrowUp', code: 'ArrowUp' });
expect(menuItem1).toHaveFocus();
});
it('focuses the last enabled item when moving backwards from the first enabled item (case -1)', () => {
render(
<OverflowMenu iconDescription="custom-icon" className="extra-class">
<OverflowMenuItem itemText="Item 1" data-testid="menu-item-1" />
<OverflowMenuItem
itemText="Item 2"
disabled
data-testid="menu-item-2"
/>
<OverflowMenuItem itemText="Item 3" data-testid="menu-item-3" />
</OverflowMenu>
);

const button = screen.getByRole('button', { name: 'custom-icon' });
fireEvent.click(button);

const menuItem1 = screen.getByText('Item 1').closest('button');
const menuItem3 = screen.getByText('Item 3').closest('button');
menuItem1.focus();
expect(menuItem1).toHaveFocus();
fireEvent.keyDown(menuItem1, { key: 'ArrowUp', code: 'ArrowUp' });
expect(menuItem3).toHaveFocus();
});

it('focuses the first enabled item when moving forward from the last enabled item (case enabledIndices.length)', () => {
render(
<OverflowMenu iconDescription="custom-icon" className="extra-class">
<OverflowMenuItem itemText="Item 1" data-testid="menu-item-1" />
<OverflowMenuItem
itemText="Item 2"
disabled
data-testid="menu-item-2"
/>
<OverflowMenuItem itemText="Item 3" data-testid="menu-item-3" />
</OverflowMenu>
);

const button = screen.getByRole('button', { name: 'custom-icon' });
fireEvent.click(button);

const menuItem1 = screen.getByText('Item 1').closest('button');
const menuItem3 = screen.getByText('Item 3').closest('button');
menuItem3.focus();
expect(menuItem3).toHaveFocus();
fireEvent.keyDown(menuItem3, { key: 'ArrowDown', code: 'ArrowDown' });
expect(menuItem1).toHaveFocus();
});
it('closes the menu on Escape key press', async () => {
render(
<OverflowMenu open iconDescription="custom-icon" className="extra-class">
<OverflowMenuItem itemText="Item 1" data-testid="menu-item-1" />
<OverflowMenuItem
itemText="Item 2"
disabled
data-testid="menu-item-2"
/>
<OverflowMenuItem itemText="Item 3" data-testid="menu-item-3" />
</OverflowMenu>
);
const button = screen.getByRole('button', { name: 'custom-icon' });
expect(button).toHaveClass('cds--overflow-menu--open');

const menu = await waitFor(() =>
screen.getByRole('menu', { hidden: true })
);
fireEvent.keyDown(menu, { key: 'Escape', code: 'Escape' });
expect(button).not.toHaveClass('cds--overflow-menu--open');
expect(button).toHaveFocus();
});
});
30 changes: 11 additions & 19 deletions packages/react/src/components/OverflowMenu/OverflowMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@
* LICENSE file in the root directory of this source tree.
*/

import invariant from 'invariant';
import PropTypes from 'prop-types';
import React, { ComponentType } from 'react';
import classNames from 'classnames';
import ClickListener from '../../internal/ClickListener';
import FloatingMenu, {
DIRECTION_TOP,
DIRECTION_BOTTOM,
DIRECTION_TOP,
} from '../../internal/FloatingMenu';
import React, { ComponentType } from 'react';
import { matches as keyCodeMatches, keys } from '../../internal/keyboard';

import ClickListener from '../../internal/ClickListener';
import { IconButton } from '../IconButton';
import { OverflowMenuVertical } from '@carbon/icons-react';
import { keys, matches as keyCodeMatches } from '../../internal/keyboard';
import mergeRefs from '../../tools/mergeRefs';
import { PrefixContext } from '../../internal/usePrefix';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import deprecate from '../../prop-types/deprecate';
import { IconButton } from '../IconButton';
import setupGetInstanceId from '../../tools/setupGetInstanceId';
import invariant from 'invariant';
import mergeRefs from '../../tools/mergeRefs';
import { noopFn } from '../../internal/noopFn';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

Expand Down Expand Up @@ -81,15 +82,6 @@ export const getMenuOffset = (menuBody, direction, trigger, flip) => {
top: 0,
};
}
case 'left':
case 'right': {
// TODO: Ensure `trigger` is there for `<OverflowMenu open>`
const triggerHeight = !trigger ? 0 : trigger.offsetHeight;
return {
left: 0,
top: (!flip ? 1 : -1) * (menuHeight / 2 - triggerHeight / 2),
};
}

default:
break;
Expand Down
Loading