test: Menu component tests (#13302)

* Add tests for the Menu component

* Clean up and add  NewMenu - SubMenu tests

* Remove obsolete SubMenu test

* Add LanguagePicker tests
This commit is contained in:
Geido 2021-02-26 01:51:26 +02:00 committed by GitHub
parent 45cca3a945
commit f3f37b0229
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 487 additions and 321 deletions

View File

@ -1,212 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { styledMount as mount } from 'spec/helpers/theming';
import { shallow } from 'enzyme';
import { Nav } from 'react-bootstrap';
import { Menu as DropdownMenu } from 'src/common/components';
import NavDropdown from 'src/components/NavDropdown';
import { Link } from 'react-router-dom';
import { Menu } from 'src/components/Menu/Menu';
import MenuObject from 'src/components/Menu/MenuObject';
const defaultProps = {
data: {
menu: [
{
name: 'Home',
icon: '',
label: 'Home',
url: '/superset/welcome',
},
{
name: 'Sources',
icon: 'fa-table',
label: 'Sources',
childs: [
{
name: 'Datasets',
icon: 'fa-table',
label: 'Datasets',
url: '/tablemodelview/list/',
},
'-',
{
name: 'Databases',
icon: 'fa-database',
label: 'Databases',
url: '/databaseview/list/',
},
],
},
{
name: 'Charts',
icon: 'fa-bar-chart',
label: 'Charts',
url: '/chart/list/',
},
{
name: 'Dashboards',
icon: 'fa-dashboard',
label: 'Dashboards',
url: '/dashboard/list/',
},
],
brand: {
path: '/superset/profile/admin/',
icon: '/static/assets/images/superset-logo-horiz.png',
alt: 'Superset',
width: '126',
},
navbar_right: {
bug_report_url: null,
documentation_url: null,
languages: {
en: {
flag: 'us',
name: 'English',
url: '/lang/en',
},
it: {
flag: 'it',
name: 'Italian',
url: '/lang/it',
},
},
show_language_picker: true,
user_is_anonymous: false,
user_info_url: '/users/userinfo/',
user_logout_url: '/logout/',
user_login_url: '/login/',
locale: 'en',
},
settings: [
{
name: 'Security',
icon: 'fa-cogs',
label: 'Security',
childs: [
{
name: 'List Users',
icon: 'fa-user',
label: 'List Users',
url: '/users/list/',
},
],
},
],
},
};
describe('Menu', () => {
let wrapper;
const getWrapper = (overrideProps = {}) => {
const props = {
...defaultProps,
...overrideProps,
};
return shallow(<Menu {...props} />);
};
beforeEach(() => {
wrapper = getWrapper();
});
it('renders the brand', () => {
expect(wrapper.find('.navbar-brand')).toExist();
});
it('renders 2 navs', () => {
expect(wrapper.find(Nav)).toHaveLength(2);
});
it('renders 4 elements in main Menu Nav for every user', () => {
expect(wrapper.find(MenuObject)).toHaveLength(4);
});
it('renders a logged out view', () => {
const loggedOutWrapper = getWrapper({
data: {
...defaultProps.data,
navbar_right: {
...defaultProps.data.navbar_right,
user_is_anonymous: true,
},
},
});
expect(loggedOutWrapper.find('i.fa-sign-in')).toHaveLength(1);
expect(loggedOutWrapper.find('i.fa-user')).toHaveLength(0);
});
it('renders version number and SHA', () => {
const overrideProps = {
data: {
...defaultProps.data,
navbar_right: {
...defaultProps.data.navbar_right,
version_string: 'A1',
version_sha: 'X',
},
},
};
const props = {
...defaultProps,
...overrideProps,
};
const versionedWrapper = mount(<Menu {...props} />);
expect(versionedWrapper.find('.version-info span')).toHaveLength(2);
});
it('renders a NavDropdown (settings)', () => {
expect(wrapper.find(NavDropdown)).toHaveLength(1);
});
it('renders MenuItems in NavDropdown (settings)', () => {
expect(wrapper.find(NavDropdown).find(DropdownMenu.Item)).toHaveLength(3);
});
it('renders a react-router Link if isFrontendRoute', () => {
const props = {
...defaultProps,
isFrontendRoute: jest.fn(() => true),
};
const wrapper2 = mount(<Menu {...props} />);
expect(props.isFrontendRoute).toHaveBeenCalled();
expect(wrapper2.find(Link)).toExist();
});
it('does not render a react-router Link if not isFrontendRoute', () => {
const props = {
...defaultProps,
isFrontendRoute: jest.fn(() => false),
};
const wrapper2 = mount(<Menu {...props} />);
expect(props.isFrontendRoute).toHaveBeenCalled();
expect(wrapper2.find(Link).exists()).toBe(false);
});
});

View File

@ -1,108 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { Link } from 'react-router-dom';
import { shallow } from 'enzyme';
import { Navbar } from 'react-bootstrap';
import SubMenu from 'src/components/Menu/SubMenu';
const defaultProps = {
name: 'Title',
tabs: [
{
name: 'Page1',
label: 'Page1',
url: '/page1',
usesRouter: true,
},
{
name: 'Page2',
label: 'Page2',
url: '/page2',
usesRouter: true,
},
{
name: 'Page3',
label: 'Page3',
url: '/page3',
usesRouter: false,
},
],
};
describe('SubMenu', () => {
let wrapper;
const getWrapper = (overrideProps = {}) => {
const props = {
...defaultProps,
...overrideProps,
};
return shallow(<SubMenu {...props} />);
};
beforeEach(() => {
wrapper = getWrapper();
});
it('renders a Navbar', () => {
expect(wrapper.find(Navbar)).toExist();
});
it('renders 3 MenuItems (when usesRouter === false)', () => {
expect(wrapper.find('li')).toHaveLength(3);
});
it('renders the menu title', () => {
expect(wrapper.find(Navbar.Brand)).toExist();
expect(wrapper.find(Navbar.Brand).children().text()).toEqual('Title');
});
it('renders Link components when usesRouter === true', () => {
const overrideProps = {
usesRouter: true,
};
const routerWrapper = getWrapper(overrideProps);
expect(routerWrapper.find(Link)).toExist();
expect(routerWrapper.find(Link)).toHaveLength(2);
expect(routerWrapper.find('li.no-router')).toHaveLength(1);
});
it('renders buttons in the right nav of the submenu', () => {
const mockFunc = jest.fn();
const buttons = [
{
name: 'test_button',
onClick: mockFunc,
buttonStyle: 'primary',
},
{
name: 'danger_button',
buttonStyle: 'danger',
},
];
const overrideProps = { buttons };
const newWrapper = getWrapper(overrideProps);
expect(newWrapper.find('.navbar-right').children()).toHaveLength(2);
newWrapper.find('[buttonStyle="primary"]').simulate('click');
expect(mockFunc).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import LanguagePicker from './LanguagePicker';
const mockedProps = {
locale: 'en',
languages: {
en: {
flag: 'us',
name: 'English',
url: '/lang/en',
},
it: {
flag: 'it',
name: 'Italian',
url: '/lang/it',
},
},
};
test('should render', () => {
const { container } = render(<LanguagePicker {...mockedProps} />);
expect(container).toBeInTheDocument();
});
test('should render the button', () => {
render(<LanguagePicker {...mockedProps} />);
const button = screen.getByRole('button');
expect(button).toHaveAttribute('href', '#');
});
test('should render the menuitem', () => {
render(<LanguagePicker {...mockedProps} />);
const menuitem = screen.getByRole('menuitem');
expect(menuitem).toHaveTextContent('Italian');
});

View File

@ -51,6 +51,7 @@ export default function LanguagePicker({
<i className={`flag ${languages[locale].flag}`} />
</span>
}
data-test="language-picker"
>
<Menu
onSelect={({ key }) => {

View File

@ -0,0 +1,295 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import { Menu } from './Menu';
const mockedProps = {
data: {
menu: [
{
name: 'Home',
icon: '',
label: 'Home',
url: '/superset/welcome',
index: 1,
},
{
name: 'Sources',
icon: 'fa-table',
label: 'Sources',
index: 2,
childs: [
{
name: 'Datasets',
icon: 'fa-table',
label: 'Datasets',
url: '/tablemodelview/list/',
index: 1,
},
'-',
{
name: 'Databases',
icon: 'fa-database',
label: 'Databases',
url: '/databaseview/list/',
index: 2,
},
],
},
{
name: 'Charts',
icon: 'fa-bar-chart',
label: 'Charts',
url: '/chart/list/',
index: 3,
},
{
name: 'Dashboards',
icon: 'fa-dashboard',
label: 'Dashboards',
url: '/dashboard/list/',
index: 4,
},
],
brand: {
path: '/superset/profile/admin/',
icon: '/static/assets/images/superset-logo-horiz.png',
alt: 'Superset',
width: '126',
},
navbar_right: {
bug_report_url: '/report/',
documentation_url: '/docs/',
languages: {
en: {
flag: 'us',
name: 'English',
url: '/lang/en',
},
it: {
flag: 'it',
name: 'Italian',
url: '/lang/it',
},
},
show_language_picker: true,
user_is_anonymous: true,
user_info_url: '/users/userinfo/',
user_logout_url: '/logout/',
user_login_url: '/login/',
user_profile_url: '/profile/',
locale: 'en',
version_string: '1.0.0',
version_sha: 'randomSHA',
},
settings: [
{
name: 'Security',
icon: 'fa-cogs',
label: 'Security',
index: 1,
childs: [
{
name: 'List Users',
icon: 'fa-user',
label: 'List Users',
url: '/users/list/',
index: 1,
},
],
},
],
},
};
const notanonProps = {
...mockedProps,
data: {
...mockedProps.data,
navbar_right: {
...mockedProps.data.navbar_right,
user_is_anonymous: false,
},
},
};
test('should render', () => {
const { container } = render(<Menu {...mockedProps} />);
expect(container).toBeInTheDocument();
});
test('should render the navigation', () => {
render(<Menu {...mockedProps} />);
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
test('should render the brand', () => {
const {
data: {
brand: { alt, icon },
},
} = mockedProps;
render(<Menu {...mockedProps} />);
const image = screen.getByAltText(alt);
expect(image).toHaveAttribute('src', icon);
});
test('should render all the top navbar menu items', () => {
const {
data: { menu },
} = mockedProps;
render(<Menu {...mockedProps} />);
menu.forEach(item => {
const menuItem = screen.getByText(item.label);
expect(menuItem).toHaveAttribute('href', item.url);
});
});
test('should render the top navbar child menu items', () => {
const {
data: { menu },
} = mockedProps;
render(<Menu {...mockedProps} />);
const datasets = screen.getByText('Datasets');
const databases = screen.getByText('Databases');
const dataset = menu[1].childs![0] as { url: string };
const database = menu[1].childs![2] as { url: string };
expect(datasets).toHaveAttribute('href', dataset.url);
expect(databases).toHaveAttribute('href', database.url);
});
test('should render the Settings', () => {
render(<Menu {...mockedProps} />);
const settings = screen.getByText('Settings');
expect(settings).toHaveAttribute('href', '#');
});
test('should render the Settings menu item', () => {
render(<Menu {...mockedProps} />);
expect(screen.getByText('Security')).toBeInTheDocument();
});
test('should render the Settings dropdown child menu items', () => {
const {
data: { settings },
} = mockedProps;
render(<Menu {...mockedProps} />);
const listUsers = screen.getByText('List Users');
expect(listUsers).toHaveAttribute('href', settings[0].childs[0].url);
});
test('should render the plus menu (+) when user is not anonymous', () => {
render(<Menu {...notanonProps} />);
expect(screen.getByTestId('new-dropdown')).toBeInTheDocument();
});
test('should NOT render the plus menu (+) when user is anonymous', () => {
render(<Menu {...mockedProps} />);
expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument();
});
test('should render the user actions when user is not anonymous', () => {
const {
data: {
navbar_right: { user_info_url, user_logout_url },
},
} = mockedProps;
render(<Menu {...notanonProps} />);
expect(screen.getByText('User')).toBeInTheDocument();
const info = screen.getByText('Info');
const logout = screen.getByText('Logout');
expect(info).toHaveAttribute('href', user_info_url);
expect(logout).toHaveAttribute('href', user_logout_url);
});
test('should NOT render the user actions when user is anonymous', () => {
render(<Menu {...mockedProps} />);
expect(screen.queryByText('User')).not.toBeInTheDocument();
});
test('should render the Profile link when available', () => {
const {
data: {
navbar_right: { user_profile_url },
},
} = mockedProps;
render(<Menu {...notanonProps} />);
const profile = screen.getByText('Profile');
expect(profile).toHaveAttribute('href', user_profile_url);
});
test('should render the About section and version_string or sha when available', () => {
const {
data: {
navbar_right: { version_sha, version_string },
},
} = mockedProps;
render(<Menu {...mockedProps} />);
expect(screen.getByText('About')).toBeInTheDocument();
expect(screen.getByText(`Version: ${version_string}`)).toBeInTheDocument();
expect(screen.getByText(`SHA: ${version_sha}`)).toBeInTheDocument();
});
test('should render the Documentation link when available', () => {
const {
data: {
navbar_right: { documentation_url },
},
} = mockedProps;
render(<Menu {...mockedProps} />);
const doc = screen.getByTitle('Documentation');
expect(doc).toHaveAttribute('href', documentation_url);
});
test('should render the Bug Report link when available', () => {
const {
data: {
navbar_right: { bug_report_url },
},
} = mockedProps;
render(<Menu {...mockedProps} />);
const bugReport = screen.getByTitle('Report a Bug');
expect(bugReport).toHaveAttribute('href', bug_report_url);
});
test('should render the Login link when user is anonymous', () => {
const {
data: {
navbar_right: { user_login_url },
},
} = mockedProps;
render(<Menu {...mockedProps} />);
const login = screen.getByText('Login');
expect(login).toHaveAttribute('href', user_login_url);
});
test('should render the Language Picker', () => {
render(<Menu {...mockedProps} />);
expect(screen.getByTestId('language-picker')).toBeInTheDocument();
});

View File

@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import NewMenu, { dropdownItems } from './NewMenu';
test('should render', () => {
const { container } = render(<NewMenu />);
expect(container).toBeInTheDocument();
});
test('should render the dropdown items', () => {
render(<NewMenu />);
dropdownItems.forEach(item => {
expect(screen.getByText(item.label)).toHaveAttribute('href', item.url);
expect(screen.getByTestId(`menu-item-${item.label}`)).toBeInTheDocument();
});
});

View File

@ -21,7 +21,7 @@ import { t, styled } from '@superset-ui/core';
import { Menu } from 'src/common/components';
import NavDropdown from 'src/components/NavDropdown';
const dropdownItems = [
export const dropdownItems = [
{
label: t('SQL query'),
url: '/superset/sqllab',

View File

@ -0,0 +1,100 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import userEvent from '@testing-library/user-event';
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import SubMenu, { ButtonProps } from './SubMenu';
const mockedProps = {
name: 'Title',
tabs: [
{
name: 'Page1',
label: 'Page1',
url: '/page1',
usesRouter: true,
},
{
name: 'Page2',
label: 'Page2',
url: '/page2',
usesRouter: true,
},
{
name: 'Page3',
label: 'Page3',
url: '/page3',
usesRouter: false,
},
],
};
test('should render', () => {
const { container } = render(<SubMenu {...mockedProps} />);
expect(container).toBeInTheDocument();
});
test('should render the navigation', () => {
render(<SubMenu {...mockedProps} />);
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
test('should render the brand', () => {
render(<SubMenu {...mockedProps} />);
expect(screen.getByText('Title')).toBeInTheDocument();
});
test('should render the right number of tabs', () => {
render(<SubMenu {...mockedProps} />);
expect(screen.getAllByRole('tab')).toHaveLength(3);
});
test('should render all the tabs links', () => {
const { tabs } = mockedProps;
render(<SubMenu {...mockedProps} />);
tabs.forEach(tab => {
const tabItem = screen.getByText(tab.label);
expect(tabItem).toHaveAttribute('href', tab.url);
});
});
test('should render the buttons', () => {
const mockFunc = jest.fn();
const buttons = [
{
name: 'test_button',
onClick: mockFunc,
buttonStyle: 'primary' as ButtonProps['buttonStyle'],
},
{
name: 'danger_button',
onClick: mockFunc,
buttonStyle: 'danger' as ButtonProps['buttonStyle'],
},
];
const buttonsProps = {
...mockedProps,
buttons,
};
render(<SubMenu {...buttonsProps} />);
const testButton = screen.getByText(buttons[0].name);
expect(screen.getAllByRole('button')).toHaveLength(2);
userEvent.click(testButton);
expect(mockFunc).toHaveBeenCalled();
});

View File

@ -141,6 +141,7 @@ const SubMenu: React.FunctionComponent<SubMenuProps> = props => {
return (
<React.Fragment key={tab.label}>
<li
role="tab"
data-test={tab['data-test']}
className={tab.name === props.activeChild ? 'active' : ''}
>
@ -158,6 +159,7 @@ const SubMenu: React.FunctionComponent<SubMenuProps> = props => {
className={cx('no-router', {
active: tab.name === props.activeChild,
})}
role="tab"
>
<a href={tab.url} onClick={tab.onClick}>
{tab.label}