diff --git a/components/menu/__tests__/demo.test.js b/components/menu/__tests__/demo.test.js
new file mode 100644
index 000000000..ad45d6588
--- /dev/null
+++ b/components/menu/__tests__/demo.test.js
@@ -0,0 +1,3 @@
+import demoTest from '../../../tests/shared/demoTest';
+
+demoTest('menu');
diff --git a/components/menu/__tests__/index.test.js b/components/menu/__tests__/index.test.js
new file mode 100644
index 000000000..ed370bc10
--- /dev/null
+++ b/components/menu/__tests__/index.test.js
@@ -0,0 +1,542 @@
+import { mount } from '@vue/test-utils';
+import { asyncExpect } from '@/tests/utils';
+import Menu from '..';
+import { InboxOutlined, PieChartOutlined } from '@ant-design/icons-vue';
+import mountTest from '../../../tests/shared/mountTest';
+
+const { SubMenu } = Menu;
+function $$(className) {
+ return document.body.querySelectorAll(className);
+}
+describe('Menu', () => {
+ mountTest({
+ render() {
+ return (
+
+
+
+ );
+ },
+ });
+ beforeEach(() => {
+ document.body.innerHTML = '';
+ // jest.useFakeTimers()
+ });
+
+ afterEach(() => {
+ // jest.useRealTimers()
+ });
+ it('If has select nested submenu item ,the menu items on the grandfather level should be highlight', async () => {
+ mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-submenu-selected').length).toBe(1);
+ });
+ });
+ it('should accept defaultOpenKeys in mode horizontal', async () => {
+ mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ });
+ });
+
+ it('should accept defaultOpenKeys in mode inline', async () => {
+ mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ });
+ });
+
+ it('should accept defaultOpenKeys in mode vertical', async () => {
+ mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ });
+ });
+
+ it('horizontal', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ openKeys: {
+ type: Array,
+ default() {
+ return ['1'];
+ },
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ });
+ wrapper.setProps({ openKeys: [] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).toBe('none');
+ }, 500);
+
+ wrapper.setProps({ openKeys: ['1'] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ }, 0);
+ });
+
+ it('inline', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ openKeys: {
+ type: Array,
+ default() {
+ return ['1'];
+ },
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].style.display).not.toBe('none');
+ });
+ wrapper.setProps({ openKeys: [] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].style.display).toBe('none');
+ }, 0);
+ wrapper.setProps({ openKeys: ['1'] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].style.display).not.toBe('none');
+ }, 0);
+ });
+
+ it('vertical', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ openKeys: {
+ type: Array,
+ default() {
+ return ['1'];
+ },
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ });
+ wrapper.setProps({ openKeys: [] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).toBe('none');
+ }, 500);
+ wrapper.setProps({ openKeys: ['1'] });
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ }, 0);
+ });
+
+ // https://github.com/ant-design/ant-design/pulls/4677
+ // https://github.com/ant-design/ant-design/issues/4692
+ // TypeError: Cannot read property 'indexOf' of undefined
+ it('pr #4677 and issue #4692', () => {
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ wrapper.vm.$forceUpdate();
+ // just expect no error emit
+ });
+
+ it('should always follow openKeys when mode is switched', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ mode: {
+ type: String,
+ default: 'inline',
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('ul.ant-menu-sub')[0].style.display).not.toBe('none');
+ });
+ wrapper.setProps({ mode: 'vertical' });
+ await asyncExpect(() => {
+ expect($$('ul.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ }, 0);
+ wrapper.setProps({ mode: 'inline' });
+ await asyncExpect(() => {
+ expect($$('ul.ant-menu-sub')[0].style.display).not.toBe('none');
+ }, 0);
+ });
+
+ it('should always follow openKeys when inlineCollapsed is switched', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ inlineCollapsed: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect(wrapper.findAll('ul.ant-menu-sub')[0].classes()).toContain('ant-menu-inline');
+ expect($$('ul.ant-menu-sub')[0].style.display).not.toBe('none');
+ }, 0);
+ wrapper.setProps({ inlineCollapsed: true });
+ await asyncExpect(() => {
+ // 动画完成后的回调
+ wrapper.vm.$refs.menu.switchModeFromInline = false;
+ wrapper.vm.$forceUpdate();
+ });
+ // await asyncExpect(() => {
+ // wrapper.trigger('transitionend', { propertyName: 'width' });
+ // });
+ // await asyncExpect(() => {
+ // expect(wrapper.findAll('ul.ant-menu-root')[0].classes()).toContain('ant-menu-vertical');
+ // expect(wrapper.findAll('ul.ant-menu-sub').length).toBe(0);
+ // }, 500);
+ wrapper.setProps({ inlineCollapsed: false });
+ await asyncExpect(() => {
+ expect(wrapper.findAll('ul.ant-menu-sub')[0].classes()).toContain('ant-menu-inline');
+ expect($$('ul.ant-menu-sub')[0].style.display).not.toBe('none');
+ }, 0);
+ });
+
+ it('inlineCollapsed should works well when specify a not existed default openKeys', async () => {
+ const wrapper = mount(
+ {
+ props: {
+ inlineCollapsed: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect(wrapper.findAll('.ant-menu-sub').length).toBe(0);
+ });
+ wrapper.setProps({ inlineCollapsed: true });
+ await asyncExpect(() => {
+ // 动画完成后的回调
+ wrapper.vm.$refs.menu.switchModeFromInline = false;
+ wrapper.vm.$forceUpdate();
+ });
+ // await asyncExpect(() => {
+ // wrapper.trigger('transitionend', { propertyName: 'width' });
+ // });
+ // await asyncExpect(() => {
+ // $$('.ant-menu-submenu-title')[0].dispatchEvent(new MouseEvent('mouseenter'));
+ // });
+ // await asyncExpect(() => {
+ // expect(wrapper.findAll('.ant-menu-submenu')[0].classes()).toContain(
+ // 'ant-menu-submenu-vertical',
+ // );
+ // expect(wrapper.findAll('.ant-menu-submenu')[0].classes()).toContain('ant-menu-submenu-open');
+ // expect($$('ul.ant-menu-sub')[0].className).toContain('ant-menu-vertical');
+ // expect($$('ul.ant-menu-sub')[0].style.display).not.toBe('none');
+ // }, 500);
+ });
+
+ describe('open submenu when click submenu title', () => {
+ beforeEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ const toggleMenu = (wrapper, index, event) => {
+ wrapper.findAll('.ant-menu-submenu-title')[index].trigger(event);
+ };
+
+ it('inline', async () => {
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(0);
+ toggleMenu(wrapper, 0, 'click');
+ }, 0);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(1);
+ expect($$('.ant-menu-sub')[0].style.display).not.toBe('none');
+ toggleMenu(wrapper, 0, 'click');
+ }, 500);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].style.display).toBe('none');
+ }, 500);
+ });
+
+ it('vertical', async () => {
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(0);
+ toggleMenu(wrapper, 0, 'mouseenter');
+ }, 0);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(1);
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ toggleMenu(wrapper, 0, 'mouseleave');
+ }, 500);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).toBe('none');
+ }, 500);
+ });
+
+ it('horizontal', async () => {
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { attachTo: 'body', sync: false },
+ );
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(0);
+ toggleMenu(wrapper, 1, 'mouseenter');
+ }, 100);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub').length).toBe(1);
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).not.toBe('none');
+ toggleMenu(wrapper, 1, 'mouseleave');
+ }, 500);
+ await asyncExpect(() => {
+ expect($$('.ant-menu-sub')[0].parentElement.style.display).toBe('none');
+ }, 500);
+ });
+ });
+
+ it('inline title', async () => {
+ const wrapper = mount(
+ {
+ render() {
+ return (
+
+ );
+ },
+ },
+ { sync: false, attachTo: 'body' },
+ );
+
+ wrapper.find('.ant-menu-item').trigger('mouseenter');
+ await asyncExpect(() => {
+ const text = $$('.ant-tooltip-inner')[0].textContent;
+ expect(text).toBe('bamboo lucky');
+ }, 500);
+ });
+});