diff --git a/jest.config.js b/jest.config.js index 86627c33..dae874c4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,4 @@ module.exports = { - setupFiles: ['./tests/setup.js'], - snapshotSerializers: [require.resolve('enzyme-to-json/serializer')], -}; + setupFiles: ['/tests/setup.js'], + setupFilesAfterEnv: ['/tests/setupFilesAfterEnv.ts'] +}; \ No newline at end of file diff --git a/package.json b/package.json index d74442f0..5c43f17a 100644 --- a/package.json +++ b/package.json @@ -9,37 +9,37 @@ "ui", "react-menu" ], - "files": [ - "es", - "lib", - "assets/*.css", - "assets/*.less" - ], - "main": "./lib/index", - "module": "./es/index", "homepage": "http://github.com/react-component/menu", - "maintainers": [ - "yiminghe@gmail.com", - "hualei5280@gmail.com" - ], + "bugs": { + "url": "http://github.com/react-component/menu/issues" + }, "repository": { "type": "git", "url": "git@github.com:react-component/menu.git" }, - "bugs": { - "url": "http://github.com/react-component/menu/issues" - }, "license": "MIT", + "maintainers": [ + "yiminghe@gmail.com", + "hualei5280@gmail.com" + ], + "main": "./lib/index", + "module": "./es/index", + "files": [ + "es", + "lib", + "assets/*.css", + "assets/*.less" + ], "scripts": { - "start": "dumi dev", + "compile": "father build && lessc assets/index.less assets/index.css", + "coverage": "father test --coverage", "docs:build": "dumi build", "docs:deploy": "gh-pages -d .doc", - "compile": "father build && lessc assets/index.less assets/index.css", - "prepublishOnly": "npm run compile && np --yolo --no-publish", "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js", - "test": "father test", - "coverage": "father test --coverage", - "now-build": "npm run build" + "now-build": "npm run build", + "prepublishOnly": "npm run compile && np --yolo --no-publish", + "start": "dumi dev", + "test": "father test" }, "dependencies": { "@babel/runtime": "^7.10.1", @@ -51,24 +51,22 @@ "shallowequal": "^1.1.0" }, "devDependencies": { - "@types/enzyme": "^3.10.8", + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.0.0", "@types/jest": "^26.0.23", "@types/react": "^16.8.19", "@types/react-dom": "^16.8.4", "@types/warning": "^3.0.0", "cross-env": "^7.0.0", "dumi": "^1.1.0", - "enzyme": "^3.3.0", - "enzyme-adapter-react-16": "^1.0.2", - "enzyme-to-json": "^3.4.0", "eslint": "^7.0.0", "father": "^2.22.0", "father-build": "^1.18.6", "gh-pages": "^3.1.0", "less": "^3.10.3", "np": "^6.0.0", - "react": "^16.9.0", - "react-dom": "^16.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", "regenerator-runtime": "^0.13.7", "typescript": "^4.0.5" }, diff --git a/tests/Collapsed.spec.js b/tests/Collapsed.spec.js index 666ad26a..7ccbfa43 100644 --- a/tests/Collapsed.spec.js +++ b/tests/Collapsed.spec.js @@ -1,7 +1,5 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence */ -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mount } from 'enzyme'; +import { act, fireEvent, render } from '@testing-library/react'; import Menu, { MenuItem, SubMenu } from '../src'; describe('Menu.Collapsed', () => { @@ -15,38 +13,42 @@ describe('Menu.Collapsed', () => { }); it('should always follow openKeys when mode is switched', () => { - const wrapper = mount( - + const genMenu = props => ( + Option 1 Option 2 menu2 - , + ); + const { container, rerender } = render(genMenu()); + // Inline - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', + ); // Vertical - wrapper.setProps({ mode: 'vertical' }); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); + rerender(genMenu({ mode: 'vertical' })); + act(() => { + jest.runAllTimers(); + }); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', + ); // Inline - wrapper.setProps({ mode: 'inline' }); - wrapper.update(); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); + rerender(genMenu({ mode: 'inline' })); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', + ); }); it('should always follow openKeys when inlineCollapsed is switched', () => { - const wrapper = mount( - + const genMenu = props => ( + Option @@ -54,53 +56,52 @@ describe('Menu.Collapsed', () => { Option Option - , + + ); + + const { container, rerender } = render(genMenu()); + expect(container.querySelector('ul.rc-menu-sub')).toHaveClass( + 'rc-menu-inline', + ); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', ); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-inline'), - ).toBe(true); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); - wrapper.setProps({ inlineCollapsed: true }); + rerender(genMenu({ inlineCollapsed: true })); // 动画结束后套样式; act(() => { jest.runAllTimers(); - wrapper.update(); }); - wrapper - .find('Overflow') - .simulate('transitionEnd', { propertyName: 'width' }); + fireEvent.transitionEnd(container.querySelector('.rc-menu-root'), { + propertyName: 'width', + }); // Flush SubMenu raf state update act(() => { jest.runAllTimers(); - wrapper.update(); }); - expect( - wrapper.find('ul.rc-menu-root').at(0).hasClass('rc-menu-vertical'), - ).toBe(true); - expect(wrapper.find('ul.rc-menu-sub').length).toBe(0); + expect(container.querySelector('ul.rc-menu-root')).toHaveClass( + 'rc-menu-vertical', + ); + expect(container.querySelectorAll('ul.rc-menu-sub')).toHaveLength(0); - wrapper.setProps({ inlineCollapsed: false }); + rerender(genMenu({ inlineCollapsed: false })); act(() => { jest.runAllTimers(); - wrapper.update(); }); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-inline'), - ).toBe(true); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); + expect(container.querySelector('ul.rc-menu-sub')).toHaveClass( + 'rc-menu-inline', + ); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', + ); }); it('inlineCollapsed should works well when specify a not existed default openKeys', () => { - const wrapper = mount( - + const genMenu = props => ( + Option @@ -108,55 +109,60 @@ describe('Menu.Collapsed', () => { Option Option - , + ); - expect(wrapper.find('.rc-menu-sub').length).toBe(0); + + const { container, rerender } = render(genMenu()); + expect(container.querySelectorAll('.rc-menu-sub')).toHaveLength(0); // Do collapsed - wrapper.setProps({ inlineCollapsed: true }); + rerender(genMenu({ inlineCollapsed: true })); act(() => { jest.runAllTimers(); - wrapper.update(); }); - wrapper - .find('Overflow') - .simulate('transitionEnd', { propertyName: 'width' }); + // wrapper + // .find('Overflow') + // .simulate('transitionEnd', { propertyName: 'width' }); + fireEvent.transitionEnd(container.querySelector('.rc-menu-root'), { + propertyName: 'width', + }); // Wait internal raf work act(() => { jest.runAllTimers(); - wrapper.update(); }); // Hover to show - wrapper.find('.rc-menu-submenu-title').at(0).simulate('mouseEnter'); + // wrapper.find('.rc-menu-submenu-title').at(0).simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); act(() => { jest.runAllTimers(); - wrapper.update(); + }); + act(() => { + jest.runAllTimers(); }); - expect( - wrapper - .find('.rc-menu-submenu') - .at(0) - .hasClass('rc-menu-submenu-vertical'), - ).toBe(true); - expect( - wrapper.find('.rc-menu-submenu').at(0).hasClass('rc-menu-submenu-open'), - ).toBe(true); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-vertical'), - ).toBe(true); - expect( - wrapper.find('ul.rc-menu-sub').at(0).hasClass('rc-menu-hidden'), - ).toBe(false); + expect(container.querySelector('.rc-menu-submenu')).toHaveClass( + 'rc-menu-submenu-vertical', + ); + + expect(container.querySelector('.rc-menu-submenu')).toHaveClass( + 'rc-menu-submenu-open', + ); + + expect(container.querySelector('ul.rc-menu-sub')).toHaveClass( + 'rc-menu-vertical', + ); + expect(container.querySelector('ul.rc-menu-sub')).not.toHaveClass( + 'rc-menu-hidden', + ); }); it('inlineCollapsed MenuItem Tooltip can be removed', () => { - const wrapper = mount( + const { container } = render( { , ); - expect(wrapper.find(MenuItem).at(0).getDOMNode().title).toBe(''); - expect(wrapper.find(MenuItem).at(1).getDOMNode().title).toBe('title'); - expect(wrapper.find(MenuItem).at(2).getDOMNode().title).toBe(''); - expect(wrapper.find(MenuItem).at(3).getDOMNode().title).toBe(''); - expect(wrapper.find(MenuItem).at(4).getDOMNode().title).toBe(''); - expect(wrapper.find(MenuItem).at(4).getDOMNode().title).toBe(''); + + expect( + Array.from(container.querySelectorAll('.rc-menu-item')).map( + node => node.title, + ), + ).toEqual(['', 'title', '', '', '', '']); }); // https://github.com/ant-design/ant-design/issues/18825 // https://github.com/ant-design/ant-design/issues/8587 it('should keep selectedKeys in state when collapsed to 0px', () => { - const wrapper = mount( + const genMenu = props => ( Option 1 Option 2 Option 4 - , + ); + const { container, rerender } = render(genMenu()); + // Default expect( - wrapper.find('li.rc-menu-item-selected').getDOMNode().textContent, + container.querySelector('.rc-menu-item-selected').textContent, ).toBe('Option 1'); // Click to change select - wrapper.find('li.rc-menu-item').at(1).simulate('click'); + fireEvent.click(container.querySelectorAll('.rc-menu-item')[1]); expect( - wrapper.find('li.rc-menu-item-selected').getDOMNode().textContent, + container.querySelector('.rc-menu-item-selected').textContent, ).toBe('Option 2'); // Collapse it - wrapper.setProps({ inlineCollapsed: true }); + rerender(genMenu({ inlineCollapsed: true })); act(() => { jest.runAllTimers(); - wrapper.update(); }); // Open since controlled - expect(wrapper.find('Trigger').props().popupVisible).toBeTruthy(); + expect(container.querySelector('.rc-menu-submenu-popup')).toBeTruthy(); // Expand it - wrapper.setProps({ inlineCollapsed: false }); + rerender(genMenu({ inlineCollapsed: false })); expect( - wrapper.find('li.rc-menu-item-selected').getDOMNode().textContent, + container.querySelector('.rc-menu-item-selected').textContent, ).toBe('Option 2'); }); it('should hideMenu in initial state when collapsed', () => { - const wrapper = mount( + const genMenu = props => ( Option 1 Option 2 Option 4 - , + ); - expect(wrapper.find('Trigger').props().popupVisible).toBeFalsy(); + const { container, rerender } = render(genMenu()); - wrapper.setProps({ inlineCollapsed: false }); act(() => { jest.runAllTimers(); - wrapper.update(); }); + + expect(container.querySelector('.rc-menu-submenu-popup')).toBeTruthy(); + + rerender(genMenu({ inlineCollapsed: false })); + act(() => { + jest.runAllTimers(); + }); + expect( - wrapper.find('li.rc-menu-item-selected').getDOMNode().textContent, + container.querySelector('.rc-menu-item-selected').textContent, ).toBe('Option 1'); }); it('vertical also support inlineCollapsed', () => { - const wrapper = mount(); + const { container } = render(); - expect(wrapper.exists('.rc-menu-inline-collapsed')).toBeTruthy(); + expect(container.querySelector('.rc-menu-inline-collapsed')).toBeTruthy(); }); }); }); diff --git a/tests/Focus.spec.js b/tests/Focus.spec.js new file mode 100644 index 00000000..8590bc68 --- /dev/null +++ b/tests/Focus.spec.js @@ -0,0 +1,44 @@ +/* eslint-disable no-undef */ +import { act, fireEvent, render } from '@testing-library/react'; +import Menu, { MenuItem, SubMenu } from '../src'; + +describe('Focus', () => { + function runAllTimer() { + for (let i = 0; i < 10; i += 1) { + act(() => { + jest.runAllTimers(); + }); + } + } + + beforeEach(() => { + global.triggerProps = null; + global.popupTriggerProps = null; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('Get focus', () => { + const { container } = render( + + + 1 + + , + ); + + // Item focus + fireEvent.focus(container.querySelector('.rc-menu-item')); + expect(container.querySelector('.rc-menu-item')).toHaveClass( + 'rc-menu-item-active', + ); + + // Submenu focus + fireEvent.focus(container.querySelector('.rc-menu-submenu-title')); + expect(container.querySelector('.rc-menu-submenu-active')).toBeTruthy(); + }); +}); +/* eslint-enable */ diff --git a/tests/Keyboard.spec.tsx b/tests/Keyboard.spec.tsx index 07978ff9..73544b0f 100644 --- a/tests/Keyboard.spec.tsx +++ b/tests/Keyboard.spec.tsx @@ -1,16 +1,13 @@ /* 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 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 { spyElementPrototypes } from 'rc-util/lib/test/domHook'; -import { render } from 'enzyme'; -import { mount } from './util'; -import type { ReactWrapper } from './util'; -import KeyCode from 'rc-util/lib/KeyCode'; import Menu, { MenuItem, SubMenu } from '../src'; +import { isActive, last } from './util'; describe('Menu.Keyboard', () => { - let holder: HTMLDivElement; - beforeAll(() => { // Mock to force make menu item visible spyElementPrototypes(HTMLElement, { @@ -23,27 +20,33 @@ describe('Menu.Keyboard', () => { }); beforeEach(() => { - holder = document.createElement('div'); - document.body.appendChild(holder); jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); - holder.parentElement.removeChild(holder); }); - function keyDown(wrapper: ReactWrapper, keyCode: number) { - wrapper.find('ul.rc-menu-root').simulate('keyDown', { which: keyCode }); + function keyDown(container: HTMLElement, keyCode: number) { + fireEvent.keyDown(container.querySelector('ul.rc-menu-root'), { + which: keyCode, + keyCode, + charCode: keyCode, + }); + // SubMenu raf need slow than accessibility + for (let i = 0; i < 20; i += 1) { + act(() => { + jest.advanceTimersByTime(10); + }); + } act(() => { jest.runAllTimers(); - wrapper.update(); }); } it('no data-menu-id by init', () => { - const wrapper = render( + const { container } = render( Bamboo @@ -51,7 +54,7 @@ describe('Menu.Keyboard', () => { , ); - expect(wrapper).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); }); it('keydown works when children change', async () => { @@ -71,27 +74,27 @@ describe('Menu.Keyboard', () => { } } - const wrapper = mount(, { attachTo: holder }); + const { container } = render(); // First item - keyDown(wrapper, KeyCode.DOWN); - expect(wrapper.isActive(0)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + isActive(container, 0); // Next item - keyDown(wrapper, KeyCode.DOWN); - expect(wrapper.isActive(1)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + isActive(container, 1); // Very first item - keyDown(wrapper, KeyCode.HOME); - expect(wrapper.isActive(0)).toBeTruthy(); + keyDown(container, KeyCode.HOME); + isActive(container, 0); // Very last item - keyDown(wrapper, KeyCode.END); - expect(wrapper.isActive(2)).toBeTruthy(); + keyDown(container, KeyCode.END); + isActive(container, 2); }); it('Skip disabled item', () => { - const wrapper = mount( + const { container } = render( 1 @@ -99,43 +102,44 @@ describe('Menu.Keyboard', () => { 2 , - { attachTo: holder }, ); // Next item - keyDown(wrapper, KeyCode.DOWN); - keyDown(wrapper, KeyCode.DOWN); - expect(wrapper.isActive(3)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + keyDown(container, KeyCode.DOWN); + isActive(container, 3); // Back to first item - keyDown(wrapper, KeyCode.UP); - expect(wrapper.isActive(1)).toBeTruthy(); + keyDown(container, KeyCode.UP); + isActive(container, 1); // To the last available item - keyDown(wrapper, KeyCode.END); - expect(wrapper.isActive(3)).toBeTruthy(); + keyDown(container, KeyCode.END); + isActive(container, 3); // To the first available item - keyDown(wrapper, KeyCode.HOME); - expect(wrapper.isActive(1)).toBeTruthy(); + keyDown(container, KeyCode.HOME); + isActive(container, 1); }); it('Enter to open menu and active first item', () => { - const wrapper = mount( + const { container } = render( 1 , - { attachTo: holder }, ); // Active first sub menu - keyDown(wrapper, KeyCode.DOWN); + keyDown(container, KeyCode.DOWN); // Open it - keyDown(wrapper, KeyCode.ENTER); - expect(wrapper.find('PopupTrigger').prop('visible')).toBeTruthy(); + keyDown(container, KeyCode.ENTER); + act(() => { + jest.runAllTimers(); + }); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); }); describe('go to children & back of parent', () => { @@ -145,7 +149,7 @@ describe('Menu.Keyboard', () => { parentKey: number, ) { it(`direction ${direction}`, () => { - const wrapper = mount( + const { container, unmount } = render( @@ -153,49 +157,46 @@ describe('Menu.Keyboard', () => { , - { attachTo: holder }, ); // Active first - keyDown(wrapper, KeyCode.DOWN); + keyDown(container, KeyCode.DOWN); // Open and active sub - keyDown(wrapper, subKey); - expect( - wrapper.find('PopupTrigger').first().prop('visible'), - ).toBeTruthy(); - + keyDown(container, subKey); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); expect( - wrapper - .find('.rc-menu-submenu-active .rc-menu-submenu-title') - .last() - .text(), + last( + container.querySelectorAll( + '.rc-menu-submenu-active > .rc-menu-submenu-title', + ), + ).textContent, ).toEqual('Light'); // Open and active sub - keyDown(wrapper, subKey); + keyDown(container, subKey); expect( - wrapper.find('PopupTrigger').last().prop('visible'), - ).toBeTruthy(); - expect(wrapper.find('.rc-menu-item-active').last().text()).toEqual( - 'Little', - ); + container.querySelectorAll('.rc-menu-submenu-open'), + ).toHaveLength(2); + expect( + last(container.querySelectorAll('.rc-menu-item-active')).textContent, + ).toEqual('Little'); // Back to parent - keyDown(wrapper, parentKey); - expect(wrapper.find('PopupTrigger').last().prop('visible')).toBeFalsy(); - expect(wrapper.find('.rc-menu-item-active')).toHaveLength(0); + keyDown(container, parentKey); + expect( + container.querySelectorAll('.rc-menu-submenu-open'), + ).toHaveLength(1); + expect(container.querySelector('.rc-menu-item-active')).toBeFalsy(); // Back to parent - keyDown(wrapper, parentKey); - + keyDown(container, parentKey); + expect(container.querySelector('.rc-menu-submenu-open')).toBeFalsy(); expect( - wrapper.find('PopupTrigger').first().prop('visible'), - ).toBeFalsy(); - - expect(wrapper.find('li.rc-menu-submenu-active')).toHaveLength(1); + container.querySelectorAll('.rc-menu-submenu-active'), + ).toHaveLength(1); - wrapper.unmount(); + unmount(); }); } @@ -204,91 +205,81 @@ describe('Menu.Keyboard', () => { }); it('inline keyboard', () => { - const wrapper = mount( + const { container } = render( Light Little , - { attachTo: holder }, ); // Nothing happen when no control key - keyDown(wrapper, KeyCode.P); - expect(wrapper.exists('.rc-menu-item-active')).toBeFalsy(); + keyDown(container, KeyCode.P); + expect(container.querySelector('.rc-menu-item-active')).toBeFalsy(); // Active first - keyDown(wrapper, KeyCode.DOWN); - expect(wrapper.isActive(0)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + isActive(container, 0); // Active next - keyDown(wrapper, KeyCode.DOWN); + keyDown(container, KeyCode.DOWN); // Right will not open - keyDown(wrapper, KeyCode.RIGHT); - expect(wrapper.find('InlineSubMenuList').prop('open')).toBeFalsy(); + keyDown(container, KeyCode.RIGHT); + expect(container.querySelector('.rc-menu-submenu-open')).toBeFalsy(); // Trigger open - keyDown(wrapper, KeyCode.ENTER); - expect(wrapper.find('InlineSubMenuList').prop('open')).toBeTruthy(); - expect( - wrapper - .find('.rc-menu-submenu') - .last() - .hasClass('rc-menu-submenu-active'), - ).toBeTruthy(); - expect(wrapper.isActive(1)).toBeFalsy(); + keyDown(container, KeyCode.ENTER); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); + expect(last(container.querySelectorAll('.rc-menu-submenu'))).toHaveClass( + 'rc-menu-submenu-active', + ); + isActive(container, 1, false); // Active sub item - keyDown(wrapper, KeyCode.DOWN); - expect(wrapper.isActive(1)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + isActive(container, 1); }); it('Focus last one', () => { - const wrapper = mount( + const { container } = render( Light Bamboo , - { attachTo: holder }, ); - keyDown(wrapper, KeyCode.UP); - expect(wrapper.isActive(1)).toBeTruthy(); + keyDown(container, KeyCode.UP); + isActive(container, 1); }); it('Focus to link direct', () => { - const wrapper = mount( + const { container } = render( Light , - { attachTo: holder }, ); - const focusSpy = jest.spyOn( - (wrapper.find('a').instance() as any) as HTMLAnchorElement, - 'focus', - ); + const focusSpy = jest.spyOn(container.querySelector('a'), 'focus'); - keyDown(wrapper, KeyCode.DOWN); + keyDown(container, KeyCode.DOWN); expect(focusSpy).toHaveBeenCalled(); }); it('no dead loop', async () => { - const wrapper = mount( + const { container } = render( Little , - { attachTo: holder }, ); - keyDown(wrapper, KeyCode.DOWN); - keyDown(wrapper, KeyCode.LEFT); - keyDown(wrapper, KeyCode.RIGHT); - expect(wrapper.isActive(0)).toBeTruthy(); + keyDown(container, KeyCode.DOWN); + keyDown(container, KeyCode.LEFT); + keyDown(container, KeyCode.RIGHT); + isActive(container, 0); }); }); /* eslint-enable */ diff --git a/tests/Menu.spec.js b/tests/Menu.spec.js index eb45d2df..e973b0bb 100644 --- a/tests/Menu.spec.js +++ b/tests/Menu.spec.js @@ -1,12 +1,43 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mount } from 'enzyme'; +import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import { resetWarned } from 'rc-util/lib/warning'; -import Menu, { MenuItem, MenuItemGroup, SubMenu, Divider } from '../src'; +import { act } from 'react-dom/test-utils'; +import Menu, { Divider, MenuItem, MenuItemGroup, SubMenu } from '../src'; +import { isActive, last } from './util'; + +jest.mock('rc-trigger', () => { + const React = require('react'); + let Trigger = jest.requireActual('rc-trigger/lib/mock'); + Trigger = Trigger.default || Trigger; + + return React.forwardRef((props, ref) => { + global.triggerProps = props; + return React.createElement(Trigger, { ref, ...props }); + }); +}); + +jest.mock('rc-motion', () => { + const React = require('react'); + let Motion = jest.requireActual('rc-motion'); + Motion = Motion.default || Motion; + + return React.forwardRef((props, ref) => { + global.motionProps = props; + return React.createElement(Motion, { ref, ...props }); + }); +}); describe('Menu', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + describe('should render', () => { function createMenu(props, subKey) { return ( @@ -36,8 +67,7 @@ describe('Menu', () => { } it('popup with rtl has correct className', () => { - jest.useFakeTimers(); - const wrapper = mount( + const { container, unmount } = render( createMenu( { mode: 'vertical', direction: 'rtl', openKeys: ['sub'] }, 'sub', @@ -46,36 +76,32 @@ describe('Menu', () => { act(() => { jest.runAllTimers(); - wrapper.update(); }); expect( - wrapper.find('.rc-menu-submenu-popup').exists('.rc-menu-rtl'), + container.querySelector('.rc-menu-submenu-popup .rc-menu-rtl'), ).toBeTruthy(); - wrapper.unmount(); - - jest.useRealTimers(); - jest.clearAllTimers(); + unmount(); }); ['vertical', 'horizontal', 'inline'].forEach(mode => { it(`${mode} menu correctly`, () => { - const wrapper = mount(createMenu({ mode })); - expect(wrapper.render()).toMatchSnapshot(); + const { container } = render(createMenu({ mode })); + expect(container.children).toMatchSnapshot(); }); it(`${mode} menu with empty children without error`, () => { - expect(() => mount({[]})).not.toThrow(); + expect(() => render({[]})).not.toThrow(); }); it(`${mode} menu with undefined children without error`, () => { - expect(() => mount()).not.toThrow(); + expect(() => render()).not.toThrow(); }); it(`${mode} menu that has a submenu with undefined children without error`, () => { expect(() => - mount( + render( , @@ -84,15 +110,15 @@ describe('Menu', () => { }); it(`${mode} menu with rtl direction correctly`, () => { - const wrapper = mount(createMenu({ mode, direction: 'rtl' })); - expect(wrapper.render()).toMatchSnapshot(); + const { container } = render(createMenu({ mode, direction: 'rtl' })); + expect(container.children).toMatchSnapshot(); - expect(wrapper.find('ul').first().props().className).toContain('-rtl'); + expect(container.querySelector('ul').className).toContain('-rtl'); }); }); it('should support Fragment', () => { - const wrapper = mount( + const { container } = render( 6 @@ -106,7 +132,7 @@ describe('Menu', () => { , ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); }); }); @@ -128,41 +154,43 @@ describe('Menu', () => { } it('renders menu correctly', () => { - const wrapper = mount(createMenu()); - expect(wrapper.render()).toMatchSnapshot(); + const { container } = render(createMenu()); + expect(container.children).toMatchSnapshot(); }); }); it('set activeKey', () => { - const wrapper = mount( - + const genMenu = props => ( + 1 2 - , + ); - expect(wrapper.isActive(0)).toBeTruthy(); - expect(wrapper.isActive(1)).toBeFalsy(); - wrapper.setProps({ activeKey: '2' }); - expect(wrapper.isActive(0)).toBeFalsy(); - expect(wrapper.isActive(1)).toBeTruthy(); + const { container, rerender } = render(genMenu()); + isActive(container, 0); + isActive(container, 1, false); + + rerender(genMenu({ activeKey: '2' })); + isActive(container, 0, false); + isActive(container, 1); }); it('active first item', () => { - const wrapper = mount( + const { container } = render( 1 2 , ); - expect( - wrapper.find('.rc-menu-item').first().hasClass('rc-menu-item-active'), - ).toBeTruthy(); + expect(container.querySelector('.rc-menu-item')).toHaveClass( + 'rc-menu-item-active', + ); }); it('should render none menu item children', () => { expect(() => { - mount( + render( 1 2 @@ -179,33 +207,38 @@ describe('Menu', () => { }); it('select multiple items', () => { - const wrapper = mount( + const { container } = render( 1 2 , ); - wrapper.find('.rc-menu-item').first().simulate('click'); - wrapper.find('.rc-menu-item').last().simulate('click'); - expect(wrapper.find('li.rc-menu-item-selected')).toHaveLength(2); + fireEvent.click(container.querySelector('.rc-menu-item')); + fireEvent.click(last(container.querySelectorAll('.rc-menu-item'))); + + expect(container.querySelectorAll('.rc-menu-item-selected')).toHaveLength( + 2, + ); }); it('can be controlled by selectedKeys', () => { - const wrapper = mount( - + const genMenu = props => ( + 1 2 - , + + ); + const { container, rerender } = render(genMenu()); + expect(container.querySelector('li').className).toContain('-selected'); + rerender(genMenu({ selectedKeys: ['2'] })); + expect(last(container.querySelectorAll('li')).className).toContain( + '-selected', ); - expect(wrapper.find('li').first().props().className).toContain('-selected'); - wrapper.setProps({ selectedKeys: ['2'] }); - wrapper.update(); - expect(wrapper.find('li').last().props().className).toContain('-selected'); }); it('empty selectedKeys not to throw', () => { - mount( + render( foo , @@ -215,77 +248,83 @@ describe('Menu', () => { it('not selectable', () => { const onSelect = jest.fn(); - const wrapper = mount( - + const genMenu = props => ( + Bamboo - , + ); - wrapper.findItem(0).simulate('click'); + const { container, rerender } = render(genMenu()); + + fireEvent.click(container.querySelector('.rc-menu-item')); + expect(onSelect).toHaveBeenCalledWith( expect.objectContaining({ selectedKeys: ['bamboo'] }), ); onSelect.mockReset(); - wrapper.setProps({ selectable: false }); - wrapper.findItem(0).simulate('click'); + rerender(genMenu({ selectable: false })); + fireEvent.click(container.querySelector('.rc-menu-item')); expect(onSelect).not.toHaveBeenCalled(); }); it('select default item', () => { - const wrapper = mount( + const { container } = render( 1 2 , ); - expect(wrapper.find('li').first().props().className).toContain('-selected'); + expect(container.querySelector('li').className).toContain('-selected'); }); it('issue https://github.com/ant-design/ant-design/issues/29429', () => { // don't use selectedKeys as string // it is a compatible feature for https://github.com/ant-design/ant-design/issues/29429 - const wrapper = mount( + const { container } = render( 1 2 , ); - expect(wrapper.find('li').at(0).props().className).not.toContain( + expect(container.querySelector('li').className).not.toContain('-selected'); + expect(container.querySelectorAll('li')[1].className).toContain( '-selected', ); - expect(wrapper.find('li').at(1).props().className).toContain('-selected'); }); describe('openKeys', () => { it('can be controlled by openKeys', () => { - const wrapper = mount( - + const genMenu = props => ( + 1 2 - , + ); + const { container, rerender } = render(genMenu()); expect( - wrapper.find('InlineSubMenuList').first().props().open, - ).toBeTruthy(); - expect(wrapper.find('InlineSubMenuList').last().props().open).toBeFalsy(); + container.querySelectorAll('.rc-menu-submenu-vertical')[0], + ).toHaveClass('rc-menu-submenu-open'); + expect( + container.querySelectorAll('.rc-menu-submenu-vertical')[1], + ).not.toHaveClass('rc-menu-submenu-open'); - wrapper.setProps({ openKeys: ['g2'] }); + rerender(genMenu({ openKeys: ['g2'] })); expect( - wrapper.find('InlineSubMenuList').first().props().open, - ).toBeFalsy(); + container.querySelectorAll('.rc-menu-submenu-vertical')[0], + ).not.toHaveClass('rc-menu-submenu-open'); expect( - wrapper.find('InlineSubMenuList').last().props().open, - ).toBeTruthy(); + container.querySelectorAll('.rc-menu-submenu-vertical')[1], + ).toHaveClass('rc-menu-submenu-open'); }); it('openKeys should allow to be empty', () => { - const wrapper = mount( + const { container } = render( {}} onOpenChange={() => {}} @@ -302,25 +341,23 @@ describe('Menu', () => { , ); - expect(wrapper).toBeTruthy(); + expect(container.innerHTML).toBeTruthy(); }); it('null of openKeys', () => { - const wrapper = mount( + const { container } = render( Light , ); - expect(wrapper).toBeTruthy(); + expect(container.innerHTML).toBeTruthy(); }); }); it('open default submenu', () => { - jest.useFakeTimers(); - - const wrapper = mount( + const { container } = render( 1 @@ -333,36 +370,35 @@ describe('Menu', () => { act(() => { jest.runAllTimers(); - wrapper.update(); }); - expect(wrapper.find('PopupTrigger').first().props().visible).toBeTruthy(); - expect(wrapper.find('PopupTrigger').last().props().visible).toBeFalsy(); - - jest.useRealTimers(); + expect( + container.querySelectorAll('.rc-menu-submenu-vertical')[0], + ).toHaveClass('rc-menu-submenu-open'); + expect( + container.querySelectorAll('.rc-menu-submenu-vertical')[1], + ).not.toHaveClass('rc-menu-submenu-open'); }); it('fires select event', () => { const handleSelect = jest.fn(); - const wrapper = mount( + const { container } = render( 1 2 , ); - wrapper.find('MenuItem').first().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item')); expect(handleSelect.mock.calls[0][0].key).toBe('1'); }); it('fires click event', () => { - jest.useFakeTimers(); - resetWarned(); const errorSpy = jest.spyOn(console, 'error'); const handleClick = jest.fn(); - const wrapper = mount( + const { container } = render( 1 2 @@ -374,10 +410,9 @@ describe('Menu', () => { act(() => { jest.runAllTimers(); - wrapper.update(); }); - wrapper.find('.rc-menu-item').first().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item')); const info = handleClick.mock.calls[0][0]; expect(info.key).toBe('1'); expect(info.item).toBeTruthy(); @@ -387,61 +422,68 @@ describe('Menu', () => { ); handleClick.mockReset(); - wrapper.find('.rc-menu-item').last().simulate('click'); + fireEvent.click(last(container.querySelectorAll('.rc-menu-item'))); expect(handleClick.mock.calls[0][0].keyPath).toEqual(['3', 'parent']); errorSpy.mockRestore(); - - jest.useRealTimers(); }); it('fires deselect event', () => { const handleDeselect = jest.fn(); - const wrapper = mount( + const { container } = render( 1 2 , ); - wrapper.find('MenuItem').first().simulate('click').simulate('click'); + const item = container.querySelector('.rc-menu-item'); + fireEvent.click(item); + fireEvent.click(item); expect(handleDeselect.mock.calls[0][0].key).toBe('1'); }); it('active by mouse enter', () => { - const wrapper = mount( + const { container } = render( item disabled item2 , ); - wrapper.find('li').last().simulate('mouseEnter'); - expect(wrapper.isActive(2)).toBeTruthy(); + // 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 wrapper = mount( - + const genMenu = props => ( + 1 2 - , + ); + const { container, rerender } = render(genMenu()); // KeyDown will not change activeKey since control - wrapper.find('Overflow').simulate('keyDown', { which: KeyCode.DOWN }); - expect(wrapper.isActive(0)).toBeTruthy(); + fireEvent.keyDown(container.querySelector('.rc-menu-root'), { + which: KeyCode.DOWN, + keyCode: KeyCode.DOWN, + charCode: KeyCode.DOWN, + }); + isActive(container, 0); - wrapper.setProps({ activeKey: '2' }); - expect(wrapper.isActive(1)).toBeTruthy(); + rerender(genMenu({ activeKey: '2' })); + isActive(container, 1); }); it('defaultActiveFirst', () => { - const wrapper = mount( + const { container } = render( foo , ); - expect(wrapper.isActive(0)).toBeTruthy(); + isActive(container, 0); }); it('should accept builtinPlacements', () => { @@ -456,7 +498,7 @@ describe('Menu', () => { }, }; - const wrapper = mount( + const { container } = render( menuItem @@ -465,7 +507,7 @@ describe('Menu', () => { , ); - expect(wrapper.find('Trigger').prop('builtinPlacements').leftTop).toEqual( + expect(global.triggerProps.builtinPlacements.leftTop).toEqual( builtinPlacements.leftTop, ); }); @@ -478,81 +520,77 @@ describe('Menu', () => { }; it('defaultMotions should work correctly', () => { - const wrapper = mount( - + const genMenu = props => ( + - , + ); + const { container, rerender } = render(genMenu()); + // Inline - wrapper.setProps({ mode: 'inline' }); - expect(wrapper.find('CSSMotion').last().prop('motionName')).toEqual( - 'inlineMotion', - ); + rerender(genMenu({ mode: 'inline' })); + expect(global.motionProps.motionName).toEqual('inlineMotion'); // Horizontal - wrapper.setProps({ mode: 'horizontal' }); - expect( - wrapper.find('Trigger').last().prop('popupMotion').motionName, - ).toEqual('horizontalMotion'); + rerender(genMenu({ mode: 'horizontal' })); + expect(global.triggerProps.popupMotion.motionName).toEqual( + 'horizontalMotion', + ); // Default - wrapper.setProps({ mode: 'vertical' }); - expect( - wrapper.find('Trigger').last().prop('popupMotion').motionName, - ).toEqual('defaultMotion'); + rerender(genMenu({ mode: 'vertical' })); + expect(global.triggerProps.popupMotion.motionName).toEqual( + 'defaultMotion', + ); }); it('motion is first level', () => { - const wrapper = mount( + const genMenu = props => ( - , + ); + const { container, rerender } = render(genMenu()); // Inline - wrapper.setProps({ mode: 'inline' }); - expect(wrapper.find('CSSMotion').last().prop('motionName')).toEqual( - 'bambooLight', - ); + rerender(genMenu({ mode: 'inline' })); + expect(global.motionProps.motionName).toEqual('bambooLight'); // Horizontal - wrapper.setProps({ mode: 'horizontal' }); - expect( - wrapper.find('Trigger').last().prop('popupMotion').motionName, - ).toEqual('bambooLight'); + rerender(genMenu({ mode: 'horizontal' })); + expect(global.triggerProps.popupMotion.motionName).toEqual('bambooLight'); // Default - wrapper.setProps({ mode: 'vertical' }); - expect( - wrapper.find('Trigger').last().prop('popupMotion').motionName, - ).toEqual('bambooLight'); + rerender(genMenu({ mode: 'vertical' })); + expect(global.triggerProps.popupMotion.motionName).toEqual('bambooLight'); }); }); it('onMouseEnter should work', () => { const onMouseEnter = jest.fn(); - const wrapper = mount( + const { container } = render( Navigation One Navigation Two , ); - wrapper.find('ul.rc-menu-root').simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-root')); expect(onMouseEnter).toHaveBeenCalled(); }); it('Nest children active should bump to top', async () => { - const wrapper = mount( + const { container } = render( Light @@ -560,19 +598,19 @@ describe('Menu', () => { , ); - expect(wrapper.exists('.rc-menu-submenu-active')).toBeTruthy(); + expect(container.querySelector('.rc-menu-submenu-active')).toBeTruthy(); }); it('not warning on destroy', async () => { const errorSpy = jest.spyOn(console, 'error'); - const wrapper = mount( + const { unmount } = render( Bamboo , ); - wrapper.unmount(); + unmount(); await Promise.resolve(); @@ -584,11 +622,9 @@ describe('Menu', () => { describe('Click should close Menu', () => { function test(name, props) { it(name, async () => { - jest.useFakeTimers(); - const onOpenChange = jest.fn(); - const wrapper = mount( + const { container } = render( { // Open menu await act(async () => { jest.runAllTimers(); - wrapper.update(); }); - wrapper.update(); - - wrapper.find('.rc-menu-item').last().simulate('click'); + // wrapper.find('.rc-menu-item').last().simulate('click'); + fireEvent.click(last(container.querySelectorAll('.rc-menu-item'))); expect(onOpenChange).toHaveBeenCalledWith([]); - - jest.useRealTimers(); }); } @@ -621,11 +653,9 @@ describe('Menu', () => { test('inlineCollapsed', { mode: 'inline', inlineCollapsed: true }); it('not close inline', async () => { - jest.useFakeTimers(); - const onOpenChange = jest.fn(); - const wrapper = mount( + const { container } = render( Light @@ -636,32 +666,27 @@ describe('Menu', () => { // Open menu await act(async () => { jest.runAllTimers(); - wrapper.update(); }); - wrapper.update(); - - wrapper.find('.rc-menu-item').last().simulate('click'); + fireEvent.click(last(container.querySelectorAll('.rc-menu-item'))); expect(onOpenChange).not.toHaveBeenCalled(); - - jest.useRealTimers(); }); }); it('should support ref', () => { const menuRef = React.createRef(); - const wrapper = mount( + const { container } = render( Light , ); - expect(menuRef.current?.list).toBe(wrapper.find('ul').first().getDOMNode()); + expect(menuRef.current?.list).toBe(container.querySelector('ul')); }); it('should support focus through ref', () => { const menuRef = React.createRef(); - const wrapper = mount( + const { container } = render( Disabled child @@ -671,12 +696,12 @@ describe('Menu', () => { ); menuRef.current?.focus(); - expect(document.activeElement).toBe(wrapper.find('li').last().getDOMNode()); + expect(document.activeElement).toBe(last(container.querySelectorAll('li'))); }); it('should focus active item through ref', () => { const menuRef = React.createRef(); - const wrapper = mount( + const { container } = render( Light Cat @@ -684,7 +709,7 @@ describe('Menu', () => { ); menuRef.current?.focus(); - expect(document.activeElement).toBe(wrapper.find('li').last().getDOMNode()); + expect(document.activeElement).toBe(last(container.querySelectorAll('li'))); }); }); /* eslint-enable */ diff --git a/tests/MenuItem.spec.js b/tests/MenuItem.spec.js index 21518529..2fac3a15 100644 --- a/tests/MenuItem.spec.js +++ b/tests/MenuItem.spec.js @@ -1,6 +1,5 @@ /* eslint-disable no-undef */ -import React from 'react'; -import { mount } from 'enzyme'; +import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src'; @@ -15,20 +14,29 @@ describe('MenuItem', () => { return {subMenuIconText}; } + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + describe('custom icon', () => { it('should render custom arrow icon correctly.', () => { - const wrapper = mount( + const { container } = render( 1 , ); - const menuItemText = wrapper.find('.rc-menu-item').first().text(); + const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${menuItemIconText}`); }); it('should render custom arrow icon correctly (with children props).', () => { const targetText = 'target'; - const wrapper = mount( + const { container } = render( {targetText}}> 1 @@ -36,14 +44,14 @@ describe('MenuItem', () => { 2 , ); - const menuItemText = wrapper.find('.rc-menu-item').first().text(); + const menuItemText = container.querySelector('.rc-menu-item').textContent; expect(menuItemText).toEqual(`1${targetText}`); }); }); it('not fires select event when disabled', () => { const handleSelect = jest.fn(); - const wrapper = mount( + const { container } = render( Item content @@ -51,13 +59,13 @@ describe('MenuItem', () => { , ); - wrapper.find('.xx').simulate('click'); + fireEvent.click(container.querySelector('.xx')); expect(handleSelect).not.toBeCalled(); }); describe('menuItem events', () => { - function mountMenu(props, itemProps) { - return mount( + function renderMenu(props, itemProps) { + return render( , @@ -66,8 +74,11 @@ describe('MenuItem', () => { it('on enter key down should trigger onClick', () => { const onItemClick = jest.fn(); - const wrapper = mountMenu(null, { onClick: onItemClick }); - wrapper.findItem().simulate('keyDown', { which: KeyCode.ENTER }); + const { container } = renderMenu(null, { onClick: onItemClick }); + fireEvent.keyDown(container.querySelector('.rc-menu-item'), { + which: KeyCode.ENTER, + keyCode: KeyCode.ENTER, + }); expect(onItemClick).toHaveBeenCalledWith( expect.objectContaining({ domEvent: expect.anything() }), ); @@ -75,8 +86,10 @@ describe('MenuItem', () => { it('on mouse enter should trigger onMouseEnter', () => { const onItemMouseEnter = jest.fn(); - const wrapper = mountMenu(null, { onMouseEnter: onItemMouseEnter }); - wrapper.findItem().simulate('mouseEnter', { which: KeyCode.ENTER }); + const { container } = renderMenu(null, { + onMouseEnter: onItemMouseEnter, + }); + fireEvent.mouseEnter(container.querySelector('.rc-menu-item')); expect(onItemMouseEnter).toHaveBeenCalledWith( expect.objectContaining({ key: 'light', domEvent: expect.anything() }), ); @@ -84,8 +97,10 @@ describe('MenuItem', () => { it('on mouse leave should trigger onMouseLeave', () => { const onItemMouseLeave = jest.fn(); - const wrapper = mountMenu(null, { onMouseLeave: onItemMouseLeave }); - wrapper.findItem().simulate('mouseLeave', { which: KeyCode.ENTER }); + const { container } = renderMenu(null, { + onMouseLeave: onItemMouseLeave, + }); + fireEvent.mouseLeave(container.querySelector('.rc-menu-item')); expect(onItemMouseLeave).toHaveBeenCalledWith( expect.objectContaining({ key: 'light', domEvent: expect.anything() }), ); @@ -103,7 +118,7 @@ describe('MenuItem', () => { style: { fontSize: 20 }, }; - const wrapper = mount( + const { container } = render( 1 @@ -121,58 +136,58 @@ describe('MenuItem', () => { , ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); - wrapper.findItem().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item')); expect(onClick).toHaveBeenCalledTimes(1); - wrapper.find('.rc-menu-sub').simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-sub')); expect(onClick).toHaveBeenCalledTimes(1); - wrapper.find('.rc-menu-item-group').simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item-group')); expect(onClick).toHaveBeenCalledTimes(1); }); }); describe('overwrite default role', () => { it('should set role to none if null', () => { - const wrapper = mount( + const { container } = render( test , ); - expect(wrapper.find('li').render()).toMatchSnapshot(); + expect(container.querySelector('li')).toMatchSnapshot(); }); it('should set role to none if none', () => { - const wrapper = mount( + const { container } = render( test , ); - expect(wrapper.find('li').render()).toMatchSnapshot(); + expect(container.querySelector('li')).toMatchSnapshot(); }); it('should set role to listitem', () => { - const wrapper = mount( + const { container } = render( test , ); - expect(wrapper.find('li').render()).toMatchSnapshot(); + expect(container.querySelector('li')).toMatchSnapshot(); }); it('should set role to option', () => { - const wrapper = mount( + const { container } = render( test , ); - expect(wrapper.find('li').render()).toMatchSnapshot(); + expect(container.querySelector('li')).toMatchSnapshot(); }); }); }); diff --git a/tests/Options.spec.tsx b/tests/Options.spec.tsx index e1bdc3f0..adaa4cbf 100644 --- a/tests/Options.spec.tsx +++ b/tests/Options.spec.tsx @@ -1,11 +1,10 @@ /* eslint-disable no-undef */ -import React from 'react'; -import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; import Menu from '../src'; describe('Options', () => { it('should work', () => { - const wrapper = mount( + const { container } = render( { />, ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); }); }); /* eslint-enable */ diff --git a/tests/Private.spec.js b/tests/Private.spec.js index 1c405e9f..06bdac9b 100644 --- a/tests/Private.spec.js +++ b/tests/Private.spec.js @@ -1,12 +1,12 @@ /* eslint-disable no-undef */ -import React from 'react'; -import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; import classnames from 'classnames'; +import React from 'react'; import Menu, { MenuItem, SubMenu } from '../src'; describe('Private Props', () => { it('_internalRenderMenuItem', () => { - const wrapper = mount( + const { container } = render( React.cloneElement(node, { @@ -18,11 +18,11 @@ describe('Private Props', () => { , ); - expect(wrapper.exists('.inject-cls')).toBeTruthy(); + expect(container.querySelector('.inject-cls')).toBeTruthy(); }); it('_internalRenderSubMenuItem', () => { - const wrapper = mount( + const { container } = render( { , ); - expect(wrapper.exists('.inject-cls')).toBeTruthy(); + expect(container.querySelector('.inject-cls')).toBeTruthy(); }); }); /* eslint-enable */ diff --git a/tests/Responsive.spec.tsx b/tests/Responsive.spec.tsx index e06eadec..17eba953 100644 --- a/tests/Responsive.spec.tsx +++ b/tests/Responsive.spec.tsx @@ -1,15 +1,34 @@ /* eslint-disable no-undef, react/no-multi-comp, react/jsx-curly-brace-presence, max-classes-per-file */ -import React from 'react'; -import { act } from 'react-dom/test-utils'; +import { fireEvent, render } from '@testing-library/react'; import KeyCode from 'rc-util/lib/KeyCode'; -import { render } from 'enzyme'; -import ResizeObserver from 'rc-resize-observer'; -import { mount } from './util'; +import { act } from 'react-dom/test-utils'; import Menu, { MenuItem, SubMenu } from '../src'; import { OVERFLOW_KEY } from '../src/hooks/useKeyRecords'; +import { last } from './util'; + +jest.mock('rc-resize-observer', () => { + const React = require('react'); + let ResizeObserver = jest.requireActual('rc-resize-observer'); + ResizeObserver = ResizeObserver.default || ResizeObserver; + + let guid = 0; + + return React.forwardRef((props, ref) => { + const [id] = React.useState(() => { + guid += 1; + return guid; + }); + + global.resizeProps = global.resizeProps || new Map(); + global.resizeProps.set(id, props); + + return React.createElement(ResizeObserver, { ref, ...props }); + }); +}); describe('Menu.Responsive', () => { beforeEach(() => { + global.resizeProps = null; jest.useFakeTimers(); }); @@ -17,8 +36,12 @@ describe('Menu.Responsive', () => { jest.useRealTimers(); }); + function getResizeProps(): any[] { + return Array.from(global.resizeProps!.values()); + } + it('ssr render full', () => { - const wrapper = render( + const { container } = render( Light Bamboo @@ -26,69 +49,74 @@ describe('Menu.Responsive', () => { , ); - expect(wrapper).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); }); it('show rest', () => { const onOpenChange = jest.fn(); - const wrapper = mount( - + + const genMenu = (props?: any) => ( + Light Bamboo Little - , - { attachTo: document.body }, + ); + const { container, rerender } = render(genMenu()); + act(() => { jest.runAllTimers(); - wrapper.update(); }); // Set container width act(() => { - wrapper - .find(ResizeObserver) - .first() - .props() - .onResize({} as any, { clientWidth: 41 } as any); + getResizeProps()[0].onResize({} as any, { clientWidth: 41 } as any); jest.runAllTimers(); - wrapper.update(); }); // Resize every item - wrapper.find('Item').forEach(item => { - act(() => { - item - .find(ResizeObserver) - .props() - .onResize({ offsetWidth: 20 } as any, null); - jest.runAllTimers(); - wrapper.update(); + getResizeProps() + .slice(1) + .forEach(props => { + act(() => { + props.onResize({ offsetWidth: 20 } as any, null); + jest.runAllTimers(); + }); }); - }); // Should show the rest icon expect( - wrapper.find('.rc-menu-overflow-item-rest').last().prop('style').opacity, - ).not.toEqual(0); + last(container.querySelectorAll('.rc-menu-overflow-item-rest')), + ).not.toHaveStyle({ + opacity: '0', + }); // Should set active on rest expect( - wrapper - .find('.rc-menu-overflow-item-rest') - .last() - .hasClass('rc-menu-submenu-active'), - ).toBeTruthy(); + last(container.querySelectorAll('.rc-menu-overflow-item-rest')), + ).toHaveClass('rc-menu-submenu-active'); // Key down can open expect(onOpenChange).not.toHaveBeenCalled(); - wrapper.setProps({ activeKey: OVERFLOW_KEY }); - wrapper - .find('ul.rc-menu-root') - .simulate('keyDown', { which: KeyCode.DOWN }); + rerender(genMenu({ activeKey: OVERFLOW_KEY })); + act(() => { + jest.runAllTimers(); + }); + + fireEvent.keyDown(container.querySelector('ul.rc-menu-root'), { + which: KeyCode.DOWN, + keyCode: KeyCode.DOWN, + charCode: KeyCode.DOWN, + }); + expect(onOpenChange).toHaveBeenCalled(); }); }); diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 8a180158..1be4da35 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -1,12 +1,43 @@ /* eslint-disable no-undef */ -import React from 'react'; -import { mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import Menu, { MenuItem, SubMenu } from '../src'; +import { act, fireEvent, render } from '@testing-library/react'; import { resetWarned } from 'rc-util/lib/warning'; +import Menu, { MenuItem, SubMenu } from '../src'; +import { isActive, last } from './util'; + +jest.mock('rc-trigger', () => { + const React = require('react'); + let Trigger = jest.requireActual('rc-trigger/lib/mock'); + Trigger = Trigger.default || Trigger; + + return React.forwardRef((props, ref) => { + global.triggerProps = props; + return React.createElement(Trigger, { ref, ...props }); + }); +}); + +jest.mock('../src/SubMenu/PopupTrigger', () => { + const React = require('react'); + let PopupTrigger = jest.requireActual('../src/SubMenu/PopupTrigger'); + PopupTrigger = PopupTrigger.default || PopupTrigger; + + return React.forwardRef((props, ref) => { + global.popupTriggerProps = props; + return React.createElement(PopupTrigger, { ref, ...props }); + }); +}); describe('SubMenu', () => { + function runAllTimer() { + for (let i = 0; i < 10; i += 1) { + act(() => { + jest.runAllTimers(); + }); + } + } + beforeEach(() => { + global.triggerProps = null; + global.popupTriggerProps = null; jest.useFakeTimers(); }); @@ -35,20 +66,20 @@ describe('SubMenu', () => { } it("don't show submenu when disabled", () => { - const wrapper = mount( + const { container } = render( 1 , ); - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - expect(wrapper.find('PopupTrigger').prop('visible')).toBeFalsy(); + expect(global.popupTriggerProps.visible).toBeFalsy(); }); it('offsets the submenu popover', () => { - const wrapper = mount( + render( 1 @@ -56,12 +87,12 @@ describe('SubMenu', () => { , ); - const popupAlign = wrapper.find('Trigger').first().prop('popupAlign'); + const { popupAlign } = global.triggerProps; expect(popupAlign).toEqual({ offset: [0, 15] }); }); it('should render custom arrow icon correctly.', () => { - const wrapper = mount( + const { container } = render( { , ); - const wrapperWithExpandIconFunction = mount( + const wrapperWithExpandIconFunction = render( { , ); - const subMenuText = wrapper.find('.rc-menu-submenu-title').first().text(); - const subMenuTextWithExpandIconFunction = wrapperWithExpandIconFunction - .find('.rc-menu-submenu-title') - .first() - .text(); + const subMenuText = container.querySelector( + '.rc-menu-submenu-title', + ).textContent; + const subMenuTextWithExpandIconFunction = + wrapperWithExpandIconFunction.container.querySelector( + '.rc-menu-submenu-title', + ).textContent; expect(subMenuText).toEqual('submenuSubMenuIconNode'); expect(subMenuTextWithExpandIconFunction).toEqual('submenuSubMenuIconNode'); }); it('should Not render custom arrow icon in horizontal mode.', () => { - const wrapper = mount( + const { container } = render( { , ); - const childText = wrapper.find('.rc-menu-submenu-title').hostNodes().text(); + const childText = container.querySelector( + '.rc-menu-submenu-title', + ).textContent; expect(childText).toEqual('submenu'); }); describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { it('toggles when mouse enter and leave', () => { - const wrapper = mount(createMenu()); + const { container } = render( + + + 1 + + , + ); // Enter - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy(); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); + runAllTimer(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); // Leave - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseLeave'); + fireEvent.mouseLeave(container.querySelector('.rc-menu-submenu-title')); act(() => { jest.runAllTimers(); - wrapper.update(); }); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeFalsy(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeFalsy(); }); }); describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are false', () => { it('toggles when mouse click', () => { - const wrapper = mount( + const { container } = render( createMenu({ triggerSubMenuAction: 'click', }), ); - wrapper.find('.rc-menu-submenu-title').first().simulate('click'); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy(); + fireEvent.click(container.querySelector('.rc-menu-submenu-title')); + runAllTimer(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); - wrapper.find('.rc-menu-submenu-title').first().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-submenu-title')); act(() => { jest.runAllTimers(); - wrapper.update(); }); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeFalsy(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeFalsy(); }); }); it('fires openChange event', () => { const handleOpenChange = jest.fn(); - const wrapper = mount( + const { container } = render( 1 @@ -175,19 +208,15 @@ describe('SubMenu', () => { ); // First - wrapper.find('div.rc-menu-submenu-title').at(0).simulate('mouseEnter'); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); + runAllTimer(); expect(handleOpenChange).toHaveBeenCalledWith(['tmp_key-1']); // Second - wrapper.find('div.rc-menu-submenu-title').at(1).simulate('mouseEnter'); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + fireEvent.mouseEnter( + container.querySelectorAll('.rc-menu-submenu-title')[1], + ); + runAllTimer(); expect(handleOpenChange).toHaveBeenCalledWith([ 'tmp_key-1', 'tmp_key-tmp_key-1-1', @@ -202,7 +231,7 @@ describe('SubMenu', () => { .spyOn(console, 'error') .mockImplementation(() => {}); - mount( + render( 1 , @@ -222,7 +251,7 @@ describe('SubMenu', () => { .spyOn(console, 'error') .mockImplementation(() => {}); - mount( + render( , @@ -242,7 +271,7 @@ describe('SubMenu', () => { .spyOn(console, 'error') .mockImplementation(() => {}); - mount( + render( , @@ -256,37 +285,31 @@ describe('SubMenu', () => { describe('mouse events', () => { it('mouse enter event on a submenu should not activate first item', () => { - const wrapper = mount(createMenu({ openKeys: ['s1'] })); - const title = wrapper.find('div.rc-menu-submenu-title').first(); - title.simulate('mouseEnter'); + const { container } = render(createMenu({ openKeys: ['s1'] })); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy(); - expect(wrapper.isActive(0)).toBeFalsy(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); + isActive(container, 0, false); }); it('click to open a submenu should not activate first item', () => { - const wrapper = mount(createMenu({ triggerSubMenuAction: 'click' })); - const subMenuTitle = wrapper.find('div.rc-menu-submenu-title').first(); - subMenuTitle.simulate('click'); + const { container } = render( + createMenu({ triggerSubMenuAction: 'click' }), + ); + fireEvent.click(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy(); - expect(wrapper.isActive(0)).toBeFalsy(); + expect(container.querySelector('.rc-menu-submenu-open')).toBeTruthy(); + isActive(container, 0, false); }); it('mouse enter/mouse leave on a subMenu item should trigger hooks', () => { const onMouseEnter = jest.fn(); const onMouseLeave = jest.fn(); - const wrapper = mount( + const { container } = render( { , ); - const subMenu = wrapper.find('.rc-menu-submenu').first(); - subMenu.simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); expect(onMouseEnter).toHaveBeenCalledTimes(1); - subMenu.simulate('mouseLeave'); + fireEvent.mouseLeave(container.querySelector('.rc-menu-submenu-title')); expect(onMouseLeave).toHaveBeenCalledTimes(1); }); }); it('fires select event', () => { const handleSelect = jest.fn(); - const wrapper = mount(createMenu({ onSelect: handleSelect })); - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); + const { container } = render(createMenu({ onSelect: handleSelect })); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - wrapper.findItem().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item')); expect(handleSelect.mock.calls[0][0].key).toBe('s1-1'); }); it('fires select event with className', () => { - const wrapper = mount(createMenu()); - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); + const { container } = render(createMenu()); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - wrapper.find('MenuItem').first().simulate('click'); - expect( - wrapper.find('.rc-menu-submenu').first().is('.rc-menu-submenu-selected'), - ).toBe(true); + fireEvent.click(container.querySelector('.rc-menu-item')); + expect(container.querySelector('.rc-menu-submenu')).toHaveClass( + 'rc-menu-submenu-selected', + ); }); it('fires deselect event for multiple menu', () => { const handleDeselect = jest.fn(); - const wrapper = mount( + const { container } = render( createMenu({ multiple: true, onDeselect: handleDeselect, }), ); - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - wrapper.findItem().simulate('click'); - wrapper.findItem().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-item')); + fireEvent.click(container.querySelector('.rc-menu-item')); expect(handleDeselect.mock.calls[0][0].key).toBe('s1-1'); }); @@ -365,14 +378,14 @@ describe('SubMenu', () => { ); - const wrapper = mount(); - expect(wrapper.find('.rc-menu').first().prop('style')).toEqual({ + const { container } = render(); + expect(container.querySelector('.rc-menu')).toHaveStyle({ backgroundColor: 'black', }); }); it('not pass style into sub menu item', () => { - const wrapper = mount( + const { container } = render( 1 @@ -380,7 +393,7 @@ describe('SubMenu', () => { , ); - expect(wrapper.find('li.rc-menu-item').at(0).props().style).toEqual({ + expect(container.querySelector('.rc-menu-item')).toHaveStyle({ color: 'red', }); }); @@ -388,7 +401,7 @@ describe('SubMenu', () => { it('inline click for open', () => { const onOpenChange = jest.fn(); - const wrapper = mount( + const { container } = render( Little @@ -400,18 +413,16 @@ describe('SubMenu', () => { ); // Disabled - wrapper.find('div.rc-menu-submenu-title').first().simulate('click'); + fireEvent.click(container.querySelector('.rc-menu-submenu-title')); expect(onOpenChange).not.toHaveBeenCalled(); // Disabled - wrapper.find('div.rc-menu-submenu-title').last().simulate('click'); + fireEvent.click(last(container.querySelectorAll('.rc-menu-submenu-title'))); expect(onOpenChange).toHaveBeenCalledWith(['light']); }); it('popup className should correct', () => { - jest.useFakeTimers(); - - const wrapper = mount( + const { container } = render( @@ -419,29 +430,19 @@ describe('SubMenu', () => { , ); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - expect( - wrapper - .find('li.rc-menu-submenu') - .first() - .hasClass('rc-menu-submenu-horizontal'), - ).toBeTruthy(); - expect( - wrapper - .find('li.rc-menu-submenu') - .last() - .hasClass('rc-menu-submenu-vertical'), - ).toBeTruthy(); + expect(container.querySelector('.rc-menu-submenu')).toHaveClass( + 'rc-menu-submenu-horizontal', + ); - jest.useRealTimers(); + expect(last(container.querySelectorAll('.rc-menu-submenu'))).toHaveClass( + 'rc-menu-submenu-vertical', + ); }); it('should support rootClassName', () => { - const wrapper = mount( + const { container } = render( @@ -456,31 +457,27 @@ describe('SubMenu', () => { , ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); - expect( - wrapper.find('ul.rc-menu-root').at(0).hasClass('custom-className'), - ).toBe(true); - expect(wrapper.find('.rc-menu-submenu-popup').length).toBe(0); + expect(container.querySelector('.rc-menu-root')).toHaveClass( + 'custom-className', + ); + expect(container.querySelectorAll('.rc-menu-submenu-popup')).toHaveLength( + 0, + ); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); // Enter - wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); + fireEvent.mouseEnter(container.querySelector('.rc-menu-submenu-title')); - act(() => { - jest.runAllTimers(); - wrapper.update(); - }); + runAllTimer(); - expect( - wrapper.find('.rc-menu-submenu-popup').at(0).hasClass('custom-className'), - ).toBe(true); + expect(container.querySelector('.rc-menu-submenu-popup')).toHaveClass( + 'custom-className', + ); - expect(wrapper.render()).toMatchSnapshot(); + expect(container.children).toMatchSnapshot(); }); }); /* eslint-enable */ diff --git a/tests/__snapshots__/Keyboard.spec.tsx.snap b/tests/__snapshots__/Keyboard.spec.tsx.snap index 4eff8d3e..00ab93c2 100644 --- a/tests/__snapshots__/Keyboard.spec.tsx.snap +++ b/tests/__snapshots__/Keyboard.spec.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Menu.Keyboard no data-menu-id by init 1`] = ` -Array [ +HTMLCollection [