From c9d197d6aea4aa29dfcc4e91cf5ed2642b522f4d Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sun, 5 Jan 2025 23:34:59 +0800 Subject: [PATCH 1/4] feat: retire deprecated api --- src/Menu.tsx | 10 +- src/MenuItem.tsx | 3 - src/interface.ts | 10 +- src/utils/nodeUtil.tsx | 3 +- tests/Collapsed.spec.tsx | 241 ++++-- tests/Focus.spec.tsx | 254 ++++-- tests/Keyboard.spec.tsx | 193 +++-- tests/Menu.spec.tsx | 821 ++++++++++++++----- tests/MenuItem.spec.tsx | 165 +++- tests/Private.spec.tsx | 31 +- tests/React18.spec.tsx | 51 +- tests/Responsive.spec.tsx | 62 +- tests/SubMenu.spec.tsx | 474 ++++++++--- tests/__snapshots__/Menu.spec.tsx.snap | 32 +- tests/__snapshots__/MenuItem.spec.tsx.snap | 17 +- tests/__snapshots__/Responsive.spec.tsx.snap | 6 +- 16 files changed, 1708 insertions(+), 665 deletions(-) diff --git a/src/Menu.tsx b/src/Menu.tsx index edad0c5b..d3ece685 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -62,9 +62,6 @@ export interface MenuProps rootClassName?: string; items?: ItemType[]; - /** @deprecated Please use `items` instead */ - children?: React.ReactNode; - disabled?: boolean; /** @private Disable auto overflow. Pls note the prop name may refactor since we do not final decided. */ disabledOverflow?: boolean; @@ -173,7 +170,6 @@ const Menu = React.forwardRef((props, ref) => { className, tabIndex = 0, items, - children, direction, id, @@ -248,10 +244,10 @@ const Menu = React.forwardRef((props, ref) => { measureChildList: React.ReactElement[], ] = React.useMemo( () => [ - parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls), - parseItems(children, items, EMPTY_LIST, {}, prefixCls), + parseItems(items, EMPTY_LIST, _internalComponents, prefixCls), + parseItems(items, EMPTY_LIST, {}, prefixCls), ], - [children, items, _internalComponents], + [items, _internalComponents], ); const [mounted, setMounted] = React.useState(false); diff --git a/src/MenuItem.tsx b/src/MenuItem.tsx index fb83fece..47671dfe 100644 --- a/src/MenuItem.tsx +++ b/src/MenuItem.tsx @@ -28,9 +28,6 @@ export interface MenuItemProps /** @private Do not use. Private warning empty usage */ warnKey?: boolean; - - /** @deprecated No place to use this. Should remove */ - attribute?: Record; } // Since Menu event provide the `info.item` which point to the MenuItem node instance. diff --git a/src/interface.ts b/src/interface.ts index f47873d1..9174e48e 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -53,6 +53,10 @@ export interface MenuItemType extends ItemSharedProps { key: React.Key; + title?: string; + + role?: React.AriaRole; + // >>>>> Active onMouseEnter?: MenuHoverEventHandler; onMouseLeave?: MenuHoverEventHandler; @@ -62,6 +66,8 @@ export interface MenuItemType extends ItemSharedProps { } export interface MenuItemGroupType extends ItemSharedProps { + key?: React.Key; + type: 'group'; label?: React.ReactNode; @@ -139,4 +145,6 @@ export type MenuRef = { // ======================== Component ======================== export type ComponentType = 'submenu' | 'item' | 'group' | 'divider'; -export type Components = Partial>>; +export type Components = Partial< + Record> +>; diff --git a/src/utils/nodeUtil.tsx b/src/utils/nodeUtil.tsx index c0b81106..bf8fec05 100644 --- a/src/utils/nodeUtil.tsx +++ b/src/utils/nodeUtil.tsx @@ -64,13 +64,12 @@ function convertItemsToNodes( } export function parseItems( - children: React.ReactNode | undefined, items: ItemType[] | undefined, keyPath: string[], components: Components, prefixCls?: string, ) { - let childNodes = children; + let childNodes; const mergedComponents: Required = { divider: Divider, diff --git a/tests/Collapsed.spec.tsx b/tests/Collapsed.spec.tsx index ee6f1c7d..e6d6c158 100644 --- a/tests/Collapsed.spec.tsx +++ b/tests/Collapsed.spec.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence */ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('Menu.Collapsed', () => { describe('inlineCollapse and siderCollapsed', () => { @@ -15,13 +15,31 @@ describe('Menu.Collapsed', () => { it('should always follow openKeys when mode is switched', () => { const genMenu = (props?) => ( - - - Option 1 - Option 2 - - menu2 - + ); const { container, rerender } = render(genMenu()); @@ -49,13 +67,28 @@ describe('Menu.Collapsed', () => { it('should always follow submenu popup hidden when mode is switched', () => { const genMenu = (props?) => ( - - - - Option 1 - - - + ); const { container, rerender } = render(genMenu()); @@ -102,15 +135,31 @@ describe('Menu.Collapsed', () => { it('should always follow openKeys when inlineCollapsed is switched', () => { const genMenu = (props?) => ( - - - Option - - - Option - Option - - + Option, + }, + { + key: '1', + label: 'submenu1', + children: [ + { + key: 'submenu1', + label: 'Option', + }, + { + key: 'submenu2', + label: 'Option', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -155,15 +204,31 @@ describe('Menu.Collapsed', () => { it('inlineCollapsed should works well when specify a not existed default openKeys', () => { const genMenu = (props?) => ( - - - Option - - - Option - Option - - + Option, + }, + { + key: '1', + label: 'submenu1', + children: [ + { + key: 'submenu1', + label: 'Option', + }, + { + key: 'submenu2', + label: 'Option', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -176,9 +241,6 @@ describe('Menu.Collapsed', () => { jest.runAllTimers(); }); - // wrapper - // .find('Overflow') - // .simulate('transitionEnd', { propertyName: 'width' }); fireEvent.transitionEnd(container.querySelector('.rc-menu-root'), { propertyName: 'width', }); @@ -189,7 +251,6 @@ describe('Menu.Collapsed', () => { }); // Hover to show - // wrapper.find('.rc-menu-submenu-title').at(0).simulate('mouseEnter'); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); act(() => { @@ -222,24 +283,38 @@ describe('Menu.Collapsed', () => { mode="inline" inlineCollapsed getPopupContainer={node => node.parentNode as HTMLElement} - > - item - - item - - - item - - - item - - - item - - - item - - , + items={[ + { + key: 'menu1', + label: 'item', + }, + { + key: 'menu2', + label: 'item', + title: 'title', + }, + { + key: 'menu3', + label: 'item', + title: undefined, + }, + { + key: 'menu4', + label: 'item', + title: null, + }, + { + key: 'menu5', + label: 'item', + title: '', + }, + { + key: 'menu6', + label: 'item', + title: false as unknown as string, + }, + ]} + />, ); expect( @@ -259,13 +334,27 @@ describe('Menu.Collapsed', () => { defaultSelectedKeys={['1']} openKeys={['3']} {...props} - > - Option 1 - Option 2 - - Option 4 - - + items={[ + { + key: '1', + label: 'Option 1', + }, + { + key: '2', + label: 'Option 2', + }, + { + key: '3', + label: 'Option 3', + children: [ + { + key: '4', + label: 'Option 4', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -305,13 +394,27 @@ describe('Menu.Collapsed', () => { defaultSelectedKeys={['1']} openKeys={['3']} {...props} - > - Option 1 - Option 2 - - Option 4 - - + items={[ + { + key: '1', + label: 'Option 1', + }, + { + key: '2', + label: 'Option 2', + }, + { + key: '3', + label: 'Option 3', + children: [ + { + key: '4', + label: 'Option 4', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); diff --git a/tests/Focus.spec.tsx b/tests/Focus.spec.tsx index 5457895f..9de3a60d 100644 --- a/tests/Focus.spec.tsx +++ b/tests/Focus.spec.tsx @@ -2,8 +2,9 @@ import { act, fireEvent, render } from '@testing-library/react'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import React from 'react'; -import Menu, { MenuItem, MenuItemGroup, MenuRef, SubMenu } from '../src'; +import Menu, { MenuRef } from '../src'; +// TODO: use userEvent instead of fireEvent for better focus testing describe('Focus', () => { beforeAll(() => { // Mock to force make menu item visible @@ -29,11 +30,23 @@ describe('Focus', () => { it('Get focus', async () => { const { container } = await act(async () => render( - - - 1 - - , + , ), ); @@ -52,14 +65,29 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId } = await act(async () => render( - - - Disabled child - - - Light - - , + , ), ); @@ -74,12 +102,22 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId } = await act(async () => render( - - Light - - Cat - - , + , ), ); act(() => menuRef.current.focus()); @@ -93,15 +131,34 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId } = await act(async () => render( - - - - Disabled child - - - Light - - , + , ), ); @@ -116,17 +173,33 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId } = await act(async () => render( - - - - group-child-1 - - - group-child-2 - - - Light - , + , ), ); @@ -141,25 +214,45 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId } = await act(async () => render( - - - - group-child-1 - - - - nested-group-child-1 - - - nested-group-child-2 - - - group-child-3 - - , + , ), ); @@ -174,15 +267,40 @@ describe('Focus', () => { const menuRef = React.createRef(); const { getByTestId, getByTitle } = await act(async () => render( - - - Disabled child - - - Submenu child - - Light - , + , ), ); diff --git a/tests/Keyboard.spec.tsx b/tests/Keyboard.spec.tsx index 73544b0f..30dfbcf2 100644 --- a/tests/Keyboard.spec.tsx +++ b/tests/Keyboard.spec.tsx @@ -1,10 +1,9 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, act } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import React from 'react'; -import { act } from 'react-dom/test-utils'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; describe('Menu.Keyboard', () => { @@ -47,11 +46,22 @@ describe('Menu.Keyboard', () => { it('no data-menu-id by init', () => { const { container } = render( - - - Bamboo - - , + , ); expect(container.children).toMatchSnapshot(); @@ -65,11 +75,12 @@ describe('Menu.Keyboard', () => { render() { return ( - - {this.state.items.map(i => ( - {i} - ))} - + ({ + key: i, + label: i, + }))} + /> ); } } @@ -95,13 +106,34 @@ describe('Menu.Keyboard', () => { it('Skip disabled item', () => { const { container } = render( - - - 1 - - 2 - - , + , ); // Next item @@ -124,11 +156,21 @@ describe('Menu.Keyboard', () => { it('Enter to open menu and active first item', () => { const { container } = render( - - - 1 - - , + , ); // Active first sub menu @@ -150,13 +192,30 @@ describe('Menu.Keyboard', () => { ) { it(`direction ${direction}`, () => { const { container, unmount } = render( - - - - Little - - - , + , ); // Active first @@ -206,12 +265,26 @@ describe('Menu.Keyboard', () => { it('inline keyboard', () => { const { container } = render( - - Light - - Little - - , + , ); // Nothing happen when no control key @@ -244,10 +317,19 @@ describe('Menu.Keyboard', () => { it('Focus last one', () => { const { container } = render( - - Light - Bamboo - , + , ); keyDown(container, KeyCode.UP); @@ -256,11 +338,15 @@ describe('Menu.Keyboard', () => { it('Focus to link direct', () => { const { container } = render( - - - Light - - , + Light, + }, + ]} + />, ); const focusSpy = jest.spyOn(container.querySelector('a'), 'focus'); @@ -271,9 +357,16 @@ describe('Menu.Keyboard', () => { it('no dead loop', async () => { const { container } = render( - - Little - , + , ); keyDown(container, KeyCode.DOWN); diff --git a/tests/Menu.spec.tsx b/tests/Menu.spec.tsx index fc7326ef..4cc5d732 100644 --- a/tests/Menu.spec.tsx +++ b/tests/Menu.spec.tsx @@ -1,12 +1,11 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ import type { MenuMode } from '@/interface'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, act } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import { resetWarned } from 'rc-util/lib/warning'; import React from 'react'; -import { act } from 'react-dom/test-utils'; import type { MenuRef } from '../src'; -import Menu, { Divider, MenuItem, MenuItemGroup, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; jest.mock('@rc-component/trigger', () => { @@ -49,23 +48,57 @@ describe('Menu', () => { className="myMenu" openAnimation="fade" {...props} - > - - 1 - - 2 - - 3 - - 4 - - 5 - - - - 6 - - + items={[ + { + key: 'g1', + type: 'group', + label: 'g1', + children: [ + { + key: '1', + label: '1', + }, + { + type: 'divider', + }, + { + key: '2', + label: '2', + }, + ], + }, + { + key: '3', + label: '3', + }, + { + key: 'g2', + type: 'group', + label: 'g2', + children: [ + { + key: '4', + label: '4', + }, + { + key: '5', + label: '5', + disabled: true, + }, + ], + }, + { + key: subKey, + label: 'submenu', + children: [ + { + key: '6', + label: '6', + }, + ], + }, + ]} + /> ); } @@ -105,9 +138,16 @@ describe('Menu', () => { it(`${mode} menu that has a submenu with undefined children without error`, () => { expect(() => render( - - - , + , ), ).not.toThrow(); }); @@ -122,18 +162,40 @@ describe('Menu', () => { it('should support Fragment', () => { const { container } = render( - - - 6 - - 6 - <> - - 6 - - 6 - - , + , ); expect(container.children).toMatchSnapshot(); }); @@ -142,17 +204,27 @@ describe('Menu', () => { describe('render role listbox', () => { function createMenu() { return ( - - - 1 - - - 2 - - - 3 - - + ); } @@ -164,10 +236,20 @@ describe('Menu', () => { it('set activeKey', () => { const genMenu = (props?) => ( - - 1 - 2 - + ); const { container, rerender } = render(genMenu()); @@ -181,40 +263,40 @@ describe('Menu', () => { it('active first item', () => { const { container } = render( - - 1 - 2 - , + , ); expect(container.querySelector('.rc-menu-item')).toHaveClass( 'rc-menu-item-active', ); }); - it('should render none menu item children', () => { - expect(() => { - render( - - 1 - 2 - string - {'string'} - {null} - {undefined} - {12345} -
- -
, - ); - }).not.toThrow(); - }); - it('select multiple items', () => { const { container } = render( - - 1 - 2 - , + , ); fireEvent.click(container.querySelector('.rc-menu-item')); @@ -275,10 +357,20 @@ describe('Menu', () => { it('can be controlled by selectedKeys', () => { const genMenu = (props?) => ( - - 1 - 2 - + ); const { container, rerender } = render(genMenu()); expect(container.querySelector('li').className).toContain('-selected'); @@ -290,9 +382,15 @@ describe('Menu', () => { it('empty selectedKeys not to throw', () => { render( - - foo - , + , ); }); @@ -300,9 +398,17 @@ describe('Menu', () => { const onSelect = jest.fn(); const genMenu = (props?) => ( - - Bamboo - + ); const { container, rerender } = render(genMenu()); @@ -321,10 +427,19 @@ describe('Menu', () => { it('select default item', () => { const { container } = render( - - 1 - 2 - , + , ); expect(container.querySelector('li').className).toContain('-selected'); }); @@ -333,10 +448,19 @@ describe('Menu', () => { // don't use selectedKeys as string // it is a compatible feature for https://github.com/ant-design/ant-design/issues/29429 const { container } = render( - - 1 - 2 - , + , ); expect(container.querySelector('li').className).not.toContain('-selected'); expect(container.querySelectorAll('li')[1].className).toContain( @@ -347,14 +471,34 @@ describe('Menu', () => { describe('openKeys', () => { it('can be controlled by openKeys', () => { const genMenu = (props?) => ( - - - 1 - - - 2 - - + ); const { container, rerender } = render(genMenu()); @@ -382,26 +526,47 @@ describe('Menu', () => { openKeys={undefined} selectedKeys={['1']} mode="inline" - > - - - - 123123 - - - - , + items={[ + { + key: 'submenu', + type: 'submenu', + label: '1231', + children: [ + { + key: 'item1', + label: ( + + 123123 + + ), + }, + ], + }, + ]} + />, ); expect(container.innerHTML).toBeTruthy(); }); it('null of openKeys', () => { const { container } = render( - - - Light - - , + , ); expect(container.innerHTML).toBeTruthy(); }); @@ -409,14 +574,33 @@ describe('Menu', () => { it('open default submenu', () => { const { container } = render( - - - 1 - - - 2 - - , + , ); act(() => { @@ -434,10 +618,19 @@ describe('Menu', () => { it('fires select event', () => { const handleSelect = jest.fn(); const { container } = render( - - 1 - 2 - , + , ); fireEvent.click(container.querySelector('.rc-menu-item')); expect(handleSelect.mock.calls[0][0].key).toBe('1'); @@ -450,13 +643,31 @@ describe('Menu', () => { const handleClick = jest.fn(); const { container } = render( - - 1 - 2 - - 3 - - , + , ); act(() => { @@ -482,10 +693,20 @@ describe('Menu', () => { it('fires deselect event', () => { const handleDeselect = jest.fn(); const { container } = render( - - 1 - 2 - , + , ); const item = container.querySelector('.rc-menu-item'); fireEvent.click(item); @@ -495,24 +716,44 @@ describe('Menu', () => { it('active by mouse enter', () => { const { container } = render( - - item - disabled - item2 - , + , ); - // wrapper.find('li').last().simulate('mouseEnter'); fireEvent.mouseEnter(last(container.querySelectorAll('.rc-menu-item'))); - // expect(wrapper.isActive(2)).toBeTruthy(); isActive(container, 2); }); it('active by key down', () => { const genMenu = (props?) => ( - - 1 - 2 - + ); const { container, rerender } = render(genMenu()); @@ -530,9 +771,16 @@ describe('Menu', () => { it('defaultActiveFirst', () => { const { container } = render( - - foo - , + , ); isActive(container, 0); }); @@ -550,12 +798,26 @@ describe('Menu', () => { }; render( - - menuItem - - menuItem - - , + , ); expect(global.triggerProps.builtinPlacements.leftTop).toEqual( @@ -572,11 +834,23 @@ describe('Menu', () => { it('defaultMotions should work correctly', () => { const genMenu = (props?) => ( - - - - - + ); const { rerender } = render(genMenu()); @@ -605,11 +879,19 @@ describe('Menu', () => { defaultMotions={defaultMotions} motion={{ motionName: 'bambooLight' }} {...props} - > - - - - + items={[ + { + key: 'bamboo', + type: 'submenu', + children: [ + { + key: 'light', + label: '', + }, + ], + }, + ]} + /> ); const { rerender } = render(genMenu()); @@ -628,11 +910,22 @@ describe('Menu', () => { it('inline does not affect vertical motion', () => { const genMenu = (props?) => ( - - - - - + ); const { rerender } = render(genMenu({ mode: 'vertical' })); @@ -646,10 +939,20 @@ describe('Menu', () => { it('onMouseEnter should work', () => { const onMouseEnter = jest.fn(); const { container } = render( - - Navigation One - Navigation Two - , + , ); fireEvent.mouseEnter(container.querySelector('.rc-menu-root')); @@ -658,11 +961,23 @@ describe('Menu', () => { it('Nest children active should bump to top', async () => { const { container } = render( - - - Light - - , + , ); expect(container.querySelector('.rc-menu-submenu-active')).toBeTruthy(); @@ -672,9 +987,14 @@ describe('Menu', () => { const errorSpy = jest.spyOn(console, 'error'); const { unmount } = render( - - Bamboo - , + , ); unmount(); @@ -697,11 +1017,20 @@ describe('Menu', () => { mode="vertical" onOpenChange={onOpenChange} {...props} - > - - Light - - , + items={[ + { + key: 'bamboo', + type: 'submenu', + label: 'Bamboo', + children: [ + { + key: 'light', + label: 'Light', + }, + ], + }, + ]} + />, ); // Open menu @@ -723,11 +1052,24 @@ describe('Menu', () => { const onOpenChange = jest.fn(); const { container } = render( - - - Light - - , + , ); // Open menu @@ -743,9 +1085,15 @@ describe('Menu', () => { it('should support ref', () => { const menuRef = React.createRef(); const { container } = render( - - Light - , + , ); expect(menuRef.current?.list).toBe(container.querySelector('ul')); @@ -756,11 +1104,23 @@ describe('Menu', () => { it('should render a divider with role="separator"', () => { const menuRef = React.createRef(); const { container } = render( - - Light - - Cat - , + , ); // Get the divider element with the rc-menu-item-divider class const divider = container.querySelector('.rc-menu-item-divider'); @@ -768,6 +1128,7 @@ describe('Menu', () => { // Assert that the divider element with rc-menu-item-divider class has role="separator" expect(divider).toHaveAttribute('role', 'separator'); }); + it('expandIcon should be hidden when setting null or false', () => { const App = ({ expand, @@ -776,18 +1137,46 @@ describe('Menu', () => { expand?: React.ReactNode; subExpand?: React.ReactNode; }) => ( - - - 0-1 - 0-2 - - , - - 0-1 - 0-2 - - ,Cat - + ); const { container, rerender } = render( diff --git a/tests/MenuItem.spec.tsx b/tests/MenuItem.spec.tsx index d5cc0d12..18371353 100644 --- a/tests/MenuItem.spec.tsx +++ b/tests/MenuItem.spec.tsx @@ -2,7 +2,7 @@ import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import React from 'react'; -import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src'; +import Menu from '../src'; describe('MenuItem', () => { const subMenuIconText = 'SubMenuIcon'; @@ -27,9 +27,17 @@ describe('MenuItem', () => { describe('custom icon', () => { it('should render custom arrow icon correctly.', () => { const { container } = render( - - 1 - , + , ); const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${menuItemIconText}`); @@ -38,12 +46,22 @@ describe('MenuItem', () => { it('should render custom arrow icon correctly (with children props).', () => { const targetText = 'target'; const { container } = render( - - {targetText}}> - 1 - - 2 - , + {targetText}, + }, + { + key: '2', + label: '2', + }, + ]} + />, ); const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${targetText}`); @@ -53,11 +71,16 @@ describe('MenuItem', () => { it('not fires select event when disabled', () => { const handleSelect = jest.fn(); const { container } = render( - - - Item content - - , + Item content, + }, + ]} + />, ); fireEvent.click(container.querySelector('.xx')); @@ -67,9 +90,16 @@ describe('MenuItem', () => { describe('menuItem events', () => { function renderMenu(props, itemProps) { return render( - - - , + , ); } @@ -120,21 +150,42 @@ describe('MenuItem', () => { }; const { container } = render( - - - 1 - - - - 3 - - - - - 4 - - - , + , ); expect(container.children).toMatchSnapshot(); @@ -153,9 +204,15 @@ describe('MenuItem', () => { describe('overwrite default role', () => { it('should set role to none if null', () => { const { container } = render( - - test - , + , ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -163,9 +220,15 @@ describe('MenuItem', () => { it('should set role to none if none', () => { const { container } = render( - - test - , + , ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -173,9 +236,15 @@ describe('MenuItem', () => { it('should set role to listitem', () => { const { container } = render( - - test - , + , ); expect(container.querySelector('li')).toMatchSnapshot(); @@ -183,9 +252,15 @@ describe('MenuItem', () => { it('should set role to option', () => { const { container } = render( - - test - , + , ); expect(container.querySelector('li')).toMatchSnapshot(); diff --git a/tests/Private.spec.tsx b/tests/Private.spec.tsx index 06bdac9b..4f426afe 100644 --- a/tests/Private.spec.tsx +++ b/tests/Private.spec.tsx @@ -2,7 +2,7 @@ import { render } from '@testing-library/react'; import classnames from 'classnames'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('Private Props', () => { it('_internalRenderMenuItem', () => { @@ -13,9 +13,13 @@ describe('Private Props', () => { className: classnames(node.props.className, 'inject-cls'), }) } - > - 1 - , + items={[ + { + key: '1', + label: '1', + }, + ]} + />, ); expect(container.querySelector('.inject-cls')).toBeTruthy(); @@ -31,11 +35,20 @@ describe('Private Props', () => { className: classnames(node.props.className, 'inject-cls'), }) } - > - - 1-1 - - , + items={[ + { + key: '1', + type: 'submenu', + label: '1', + children: [ + { + key: '1-1', + label: '1-1', + }, + ], + }, + ]} + />, ); expect(container.querySelector('.inject-cls')).toBeTruthy(); diff --git a/tests/React18.spec.tsx b/tests/React18.spec.tsx index b505027d..51bfdd2d 100644 --- a/tests/React18.spec.tsx +++ b/tests/React18.spec.tsx @@ -2,7 +2,7 @@ import { act, render } from '@testing-library/react'; import React from 'react'; import type { MenuProps } from '../src'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; describe('React18', () => { function runAllTimer() { @@ -24,17 +24,44 @@ describe('React18', () => { function createMenu(props?: MenuProps) { return ( - - - 1 - - 2 - - - - 2 - - + ); } diff --git a/tests/Responsive.spec.tsx b/tests/Responsive.spec.tsx index 9d1e6ff6..acaa3705 100644 --- a/tests/Responsive.spec.tsx +++ b/tests/Responsive.spec.tsx @@ -4,7 +4,7 @@ import ResizeObserver from 'rc-resize-observer'; import KeyCode from 'rc-util/lib/KeyCode'; import { spyElementPrototype } from 'rc-util/lib/test/domHook'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { OVERFLOW_KEY } from '../src/hooks/useKeyRecords'; import { last } from './util'; @@ -84,11 +84,25 @@ describe('Menu.Responsive', () => { it('ssr render full', () => { const { container } = render( - - Light - Bamboo - Little - , + , ); expect(container.children).toMatchSnapshot(); @@ -103,13 +117,28 @@ describe('Menu.Responsive', () => { activeKey="little" onOpenChange={onOpenChange} {...props} - > - Light - Bamboo - - Little - - + items={[ + { + key: 'light', + label: 'Light', + }, + { + key: 'bamboo', + label: 'Bamboo', + }, + { + key: 'home', + label: 'Home', + type: 'submenu', + children: [ + { + key: 'little', + label: 'Little', + }, + ], + }, + ]} + /> ); const { container, rerender } = render(genMenu()); @@ -150,13 +179,6 @@ describe('Menu.Responsive', () => { }); spy.mockRestore(); - // Should show the rest icon - // expect( - // last(container.querySelectorAll('.rc-menu-overflow-item-rest')), - // ).not.toHaveStyle({ - // opacity: '0', - // }); - // Should set active on rest expect( last(container.querySelectorAll('.rc-menu-overflow-item-rest')), diff --git a/tests/SubMenu.spec.tsx b/tests/SubMenu.spec.tsx index f77e6d76..78cf8441 100644 --- a/tests/SubMenu.spec.tsx +++ b/tests/SubMenu.spec.tsx @@ -2,7 +2,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import { resetWarned } from 'rc-util/lib/warning'; import React from 'react'; -import Menu, { MenuItem, SubMenu } from '../src'; +import Menu from '../src'; import { isActive, last } from './util'; jest.mock('@rc-component/trigger', () => { @@ -48,17 +48,44 @@ describe('SubMenu', () => { function createMenu(props?) { return ( - - - 1 - - 2 - - - - 2 - - + ); } @@ -68,11 +95,23 @@ describe('SubMenu', () => { it("don't show submenu when disabled", () => { const { container } = render( - - - 1 - - , + , ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); @@ -81,11 +120,24 @@ describe('SubMenu', () => { it('offsets the submenu popover', () => { render( - - - 1 - - , + , ); const { popupAlign } = global.triggerProps; @@ -98,12 +150,24 @@ describe('SubMenu', () => { mode="vertical" itemIcon={itemIcon} expandIcon={SubMenuIconNode} - > - - 1 - 2 - - , + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + children: [ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ], + }, + ]} + />, ); const wrapperWithExpandIconFunction = render( @@ -111,12 +175,24 @@ describe('SubMenu', () => { mode="vertical" itemIcon={itemIcon} expandIcon={() => SubMenuIconNode} - > - - 1 - 2 - - , + items={[ + { + key: 's', + label: 'submenu', + type: 'submenu', + children: [ + { + key: '1', + label: '1', + }, + { + key: '2', + label: '2', + }, + ], + }, + ]} + />, ); const subMenuText = container.querySelector( @@ -132,16 +208,25 @@ describe('SubMenu', () => { it('should Not render custom arrow icon in horizontal mode.', () => { const { container } = render( - - SubMenuIconNode} - > - 1 - - , + SubMenuIconNode, + children: [ + { + key: '1', + label: '1', + }, + ], + }, + ]} + />, ); const childText = container.querySelector( @@ -178,11 +263,21 @@ describe('SubMenu', () => { describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { it('toggles when mouse enter and leave', () => { const { container } = render( - - - 1 - - , + , ); // Enter @@ -222,45 +317,69 @@ describe('SubMenu', () => { it('fires openChange event', () => { const handleOpenChange = jest.fn(); const { container } = render( - - 1 - - 2 - - 3 - - - , + , ); // First fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); runAllTimer(); - expect(handleOpenChange).toHaveBeenCalledWith(['tmp_key-1']); + expect(handleOpenChange).toHaveBeenCalledWith(['s1']); // Second fireEvent.mouseEnter( container.querySelectorAll('.rc-menu-submenu-title')[1], ); runAllTimer(); - expect(handleOpenChange).toHaveBeenCalledWith([ - 'tmp_key-1', - 'tmp_key-tmp_key-1-1', - ]); + expect(handleOpenChange).toHaveBeenCalledWith(['s1', 's2']); }); describe('undefined key', () => { it('warning item', () => { resetWarned(); - const errorSpy = jest .spyOn(console, 'error') .mockImplementation(() => {}); render( - - 1 - , + , ); expect(errorSpy).toHaveBeenCalledWith( @@ -272,15 +391,20 @@ describe('SubMenu', () => { it('warning sub menu', () => { resetWarned(); - const errorSpy = jest .spyOn(console, 'error') .mockImplementation(() => {}); render( - - - , + , ); expect(errorSpy).toHaveBeenCalledWith( @@ -292,19 +416,21 @@ describe('SubMenu', () => { it('should not warning', () => { resetWarned(); - const errorSpy = jest .spyOn(console, 'error') .mockImplementation(() => {}); render( - - - , + , ); expect(errorSpy).not.toHaveBeenCalled(); - errorSpy.mockRestore(); }); }); @@ -336,16 +462,24 @@ describe('SubMenu', () => { const onMouseEnter = jest.fn(); const onMouseLeave = jest.fn(); const { container } = render( - - - 1 - - , + , ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); expect(onMouseEnter).toHaveBeenCalledTimes(1); @@ -396,15 +530,24 @@ describe('SubMenu', () => { }); it('should take style prop', () => { - const App = () => ( - - - 1 - - + const { container } = render( + , ); - - const { container } = render(); expect(container.querySelector('.rc-menu')).toHaveStyle({ backgroundColor: 'black', }); @@ -412,11 +555,18 @@ describe('SubMenu', () => { it('not pass style into sub menu item', () => { const { container } = render( - - - 1 - - , + , ); expect(container.querySelector('.rc-menu-item')).toHaveStyle({ @@ -428,14 +578,35 @@ describe('SubMenu', () => { const onOpenChange = jest.fn(); const { container } = render( - - - Little - - - Sub - - , + , ); // Disabled @@ -449,11 +620,24 @@ describe('SubMenu', () => { it('popup className should correct', () => { const { container } = render( - - - - - , + , ); runAllTimer(); @@ -469,19 +653,34 @@ describe('SubMenu', () => { it('should support rootClassName', () => { const { container } = render( - - - - submenu7 - - - - 2 - - - 3 - - , + , ); expect(container.children).toMatchSnapshot(); @@ -508,15 +707,22 @@ describe('SubMenu', () => { it('submenu should support popupStyle', () => { const { container } = render( - - - 1 - - , + , ); fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); diff --git a/tests/__snapshots__/Menu.spec.tsx.snap b/tests/__snapshots__/Menu.spec.tsx.snap index 633a94e3..3e7344f5 100644 --- a/tests/__snapshots__/Menu.spec.tsx.snap +++ b/tests/__snapshots__/Menu.spec.tsx.snap @@ -134,11 +134,11 @@ HTMLCollection [ role="none" > + items={children} + /> ); } diff --git a/docs/examples/custom-icon.tsx b/docs/examples/custom-icon.tsx index 7b1f075b..a9f37faf 100644 --- a/docs/examples/custom-icon.tsx +++ b/docs/examples/custom-icon.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-console, no-param-reassign */ import * as React from 'react'; -import Menu, { SubMenu, Item as MenuItem, Divider } from '../../src'; +import Menu from 'rc-menu'; import '../../assets/index.less'; +import type { ItemType } from '@/interface'; const getSvgIcon = (style = {}, text?: React.ReactNode) => { if (text) { @@ -67,50 +68,126 @@ class Demo extends React.Component { console.log(info); }; - renderNestSubMenu = (props = {}) => ( - offset sub menu 2} - key="4" - popupOffset={[10, 15]} - {...props} - > - inner inner - - sub menu 3}> - - inner inner - inner inner2 - - inn - sub menu 4} key="4-2-2"> - inner inner - inner inner2 - - - inner inner - inner inner2 - - - - ); + renderNestSubMenu = (props = {}) => ({ + key: '4', + type: 'submenu', + label: offset sub menu 2, + popupOffset: [10, 15], + children: [ + { + key: '4-1', + label: 'inner inner', + }, + { + type: 'divider', + }, + { + key: '4-2', + type: 'submenu', + label: sub menu 3, + children: [ + { + key: '4-2-0', + type: 'submenu', + label: 'sub 4-2-0', + children: [ + { + key: '4-2-0-1', + label: 'inner inner', + }, + { + key: '4-2-0-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-1', + label: 'inn', + }, + { + key: '4-2-2', + type: 'submenu', + label: sub menu 4, + children: [ + { + key: '4-2-2-1', + label: 'inner inner', + }, + { + key: '4-2-2-2', + label: 'inner inner2', + }, + ], + }, + { + key: '4-2-3', + type: 'submenu', + label: 'sub 4-2-3', + children: [ + { + key: '4-2-3-1', + label: 'inner inner', + }, + { + key: '4-2-3-2', + label: 'inner inner2', + }, + ], + }, + ], + }, + ], + ...props, + }); - renderCommonMenu = (props = {}) => ( - - sub menu} key="1"> - 0-1 - 0-2 - - {this.renderNestSubMenu()} - 1 - outer - disabled - outer3 - - ); + renderCommonMenu = (props = {}) => { + const items: ItemType[] = [ + { + key: '1', + type: 'submenu', + label: sub menu, + children: [ + { + key: '1-1', + label: '0-1', + }, + { + key: '1-2', + label: '0-2', + }, + ], + }, + // @ts-ignore + this.renderNestSubMenu(), + { + key: '2', + label: '1', + }, + { + key: '3', + label: 'outer', + }, + { + key: '44', + label: 'disabled', + disabled: true, + }, + { + key: '5', + label: 'outer3', + }, + ]; + + return ( + + ); + }; render() { const verticalMenu = this.renderCommonMenu({ diff --git a/docs/examples/debug.tsx b/docs/examples/debug.tsx index a9db1e87..488833a7 100644 --- a/docs/examples/debug.tsx +++ b/docs/examples/debug.tsx @@ -2,8 +2,8 @@ import React, { useRef } from 'react'; import type { CSSMotionProps } from 'rc-motion'; -import Menu, { ItemGroup as MenuItemGroup, MenuItem } from '../../src'; -import type { MenuProps } from '../../src'; +import Menu from 'rc-menu'; +import type { MenuProps } from 'rc-menu'; import '../../assets/index.less'; import '../../assets/menu.less'; import type { MenuInfo, MenuRef } from '@/interface'; @@ -69,9 +69,7 @@ export default () => { return ( <>
- - Light - +