Skip to content

Commit 52fafbd

Browse files
authored
fix(unstable_Menu): close menu after clicking inside Popup/Modal (#2504)
1 parent c7e1cb0 commit 52fafbd

File tree

2 files changed

+96
-15
lines changed

2 files changed

+96
-15
lines changed

src/components/lab/Menu/Menu.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@ const b = block('lab-menu');
4444
function MenuPopupContent({
4545
open,
4646
onRequestClose,
47+
isNested,
4748
children,
4849
className,
4950
style,
5051
qa,
5152
}: Pick<MenuProps, 'children' | 'className' | 'style' | 'qa'> & {
5253
open: boolean;
5354
onRequestClose: () => void;
55+
isNested: boolean;
5456
}) {
5557
const tree = useFloatingTree();
5658
const nodeId = useFloatingParentNodeId();
@@ -61,7 +63,7 @@ function MenuPopupContent({
6163

6264
function handleTreeClick() {
6365
// Closing only the root Menu so the closing animation runs once for all menus due to shared portal container
64-
if (!parentId) {
66+
if (!isNested) {
6567
onRequestClose();
6668
}
6769
}
@@ -80,7 +82,7 @@ function MenuPopupContent({
8082
tree.events.off('click', handleTreeClick);
8183
tree.events.off('menuopen', handleSubMenuOpen);
8284
};
83-
}, [onRequestClose, tree, nodeId, parentId]);
85+
}, [onRequestClose, tree, nodeId, parentId, isNested]);
8486

8587
React.useEffect(() => {
8688
if (open && tree) {
@@ -296,6 +298,7 @@ export function Menu({
296298
<MenuPopupContent
297299
open={isOpen}
298300
onRequestClose={handleContentRequestClose}
301+
isNested={isNested}
299302
className={className}
300303
style={style}
301304
qa={qa}

src/components/lab/Menu/__tests__/Menu.test.tsx

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import userEvent from '@testing-library/user-event';
22

33
import {act, getAllByRole, render, screen, waitFor} from '../../../../../test-utils/utils';
4+
import {Modal} from '../../../Modal';
5+
import {Popup} from '../../../Popup';
46
import {Menu} from '../Menu';
57

68
const TRIGGER_QA = 'trigger';
79
const MENU_QA = 'menu';
810
const SUBMENU_QA = 'submenu';
911

10-
function renderSimpleMenu() {
11-
return render(
12+
function SimpleMenu() {
13+
return (
1214
<Menu trigger={<Menu.Trigger qa={TRIGGER_QA} />} qa={MENU_QA}>
1315
<Menu.Item>Item 1</Menu.Item>
1416
<Menu.Item>Item 2</Menu.Item>
1517
<Menu.Item>Item 3</Menu.Item>
16-
</Menu>,
18+
</Menu>
1719
);
1820
}
1921

20-
function renderComplexMenu() {
21-
return render(
22+
function ComplexMenu() {
23+
return (
2224
<Menu trigger={<Menu.Trigger qa={TRIGGER_QA} />} qa={MENU_QA}>
2325
<Menu.Item>Item 1</Menu.Item>
2426
<Menu.Item>Item 2</Menu.Item>
@@ -29,20 +31,20 @@ function renderComplexMenu() {
2931
<Menu.Item>Item 4</Menu.Item>
3032
</Menu>
3133
</Menu.Item>
32-
</Menu>,
34+
</Menu>
3335
);
3436
}
3537

3638
describe('Menu', () => {
3739
test('should render default trigger', () => {
38-
renderSimpleMenu();
40+
render(<SimpleMenu />);
3941
const trigger = screen.getByTestId(TRIGGER_QA);
4042

4143
expect(trigger).toBeVisible();
4244
});
4345

4446
test('should open menu by click', async () => {
45-
renderSimpleMenu();
47+
render(<SimpleMenu />);
4648
const user = userEvent.setup();
4749
const trigger = screen.getByTestId(TRIGGER_QA);
4850

@@ -57,7 +59,7 @@ describe('Menu', () => {
5759
test.each(['{Space}', '{Enter}', '{ArrowDown}', '{ArrowUp}'])(
5860
'should open menu by keyboard %s',
5961
async (key) => {
60-
renderSimpleMenu();
62+
render(<SimpleMenu />);
6163
const user = userEvent.setup();
6264
const trigger = screen.getByTestId(TRIGGER_QA);
6365

@@ -80,7 +82,55 @@ describe('Menu', () => {
8082
);
8183

8284
test('should close menu by selecting an item', async () => {
83-
renderSimpleMenu();
85+
render(<SimpleMenu />);
86+
const user = userEvent.setup();
87+
const trigger = screen.getByTestId(TRIGGER_QA);
88+
89+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
90+
91+
await user.click(trigger);
92+
await waitFor(() => {
93+
expect(screen.getByTestId(MENU_QA)).toBeVisible();
94+
});
95+
96+
const items = screen.getAllByRole('menuitem');
97+
98+
await user.click(items[1]);
99+
await waitFor(() => {
100+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
101+
});
102+
});
103+
104+
test('should close menu by selecting an item inside Popup', async () => {
105+
render(
106+
<Popup open>
107+
<SimpleMenu />
108+
</Popup>,
109+
);
110+
const user = userEvent.setup();
111+
const trigger = screen.getByTestId(TRIGGER_QA);
112+
113+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
114+
115+
await user.click(trigger);
116+
await waitFor(() => {
117+
expect(screen.getByTestId(MENU_QA)).toBeVisible();
118+
});
119+
120+
const items = screen.getAllByRole('menuitem');
121+
122+
await user.click(items[1]);
123+
await waitFor(() => {
124+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
125+
});
126+
});
127+
128+
test('should close menu by selecting an item inside Modal', async () => {
129+
render(
130+
<Modal open>
131+
<SimpleMenu />
132+
</Modal>,
133+
);
84134
const user = userEvent.setup();
85135
const trigger = screen.getByTestId(TRIGGER_QA);
86136

@@ -100,7 +150,7 @@ describe('Menu', () => {
100150
});
101151

102152
test('should have correct keyboard navigation', async () => {
103-
renderSimpleMenu();
153+
render(<SimpleMenu />);
104154
const user = userEvent.setup();
105155
const trigger = screen.getByTestId(TRIGGER_QA);
106156

@@ -130,7 +180,7 @@ describe('Menu', () => {
130180
});
131181

132182
test('should open submenu by hover', async () => {
133-
renderComplexMenu();
183+
render(<ComplexMenu />);
134184
const user = userEvent.setup();
135185
const trigger = screen.getByTestId(TRIGGER_QA);
136186

@@ -152,7 +202,7 @@ describe('Menu', () => {
152202
});
153203

154204
test('should open/close submenu by keyboard', async () => {
155-
renderComplexMenu();
205+
render(<ComplexMenu />);
156206
const user = userEvent.setup();
157207
const trigger = screen.getByTestId(TRIGGER_QA);
158208

@@ -186,4 +236,32 @@ describe('Menu', () => {
186236
});
187237
expect(items[2]).toHaveClass(/active/);
188238
});
239+
240+
test('should not open by hover inside Popup', async () => {
241+
render(
242+
<Popup open>
243+
<SimpleMenu />
244+
</Popup>,
245+
);
246+
const user = userEvent.setup();
247+
const trigger = screen.getByTestId(TRIGGER_QA);
248+
249+
await user.hover(trigger);
250+
251+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
252+
});
253+
254+
test('should not open by hover inside Modal', async () => {
255+
render(
256+
<Modal open>
257+
<SimpleMenu />
258+
</Modal>,
259+
);
260+
const user = userEvent.setup();
261+
const trigger = screen.getByTestId(TRIGGER_QA);
262+
263+
await user.hover(trigger);
264+
265+
expect(screen.queryByTestId(MENU_QA)).not.toBeInTheDocument();
266+
});
189267
});

0 commit comments

Comments
 (0)