diff --git a/superset-frontend/spec/javascripts/components/Menu_spec.jsx b/superset-frontend/spec/javascripts/components/Menu_spec.jsx deleted file mode 100644 index 3b28388d11..0000000000 --- a/superset-frontend/spec/javascripts/components/Menu_spec.jsx +++ /dev/null @@ -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(); - }; - - 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(); - - 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(); - - 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(); - - expect(props.isFrontendRoute).toHaveBeenCalled(); - expect(wrapper2.find(Link).exists()).toBe(false); - }); -}); diff --git a/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx b/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx deleted file mode 100644 index 46259328f8..0000000000 --- a/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx +++ /dev/null @@ -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(); - }; - - 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(); - }); -}); diff --git a/superset-frontend/src/components/Menu/LanguagePicker.test.tsx b/superset-frontend/src/components/Menu/LanguagePicker.test.tsx new file mode 100644 index 0000000000..ad494d620c --- /dev/null +++ b/superset-frontend/src/components/Menu/LanguagePicker.test.tsx @@ -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(); + expect(container).toBeInTheDocument(); +}); + +test('should render the button', () => { + render(); + const button = screen.getByRole('button'); + expect(button).toHaveAttribute('href', '#'); +}); + +test('should render the menuitem', () => { + render(); + const menuitem = screen.getByRole('menuitem'); + expect(menuitem).toHaveTextContent('Italian'); +}); diff --git a/superset-frontend/src/components/Menu/LanguagePicker.tsx b/superset-frontend/src/components/Menu/LanguagePicker.tsx index f125c0aee6..63f6e6bae1 100644 --- a/superset-frontend/src/components/Menu/LanguagePicker.tsx +++ b/superset-frontend/src/components/Menu/LanguagePicker.tsx @@ -51,6 +51,7 @@ export default function LanguagePicker({ } + data-test="language-picker" > { diff --git a/superset-frontend/src/components/Menu/Menu.test.tsx b/superset-frontend/src/components/Menu/Menu.test.tsx new file mode 100644 index 0000000000..b3a2273eb1 --- /dev/null +++ b/superset-frontend/src/components/Menu/Menu.test.tsx @@ -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(); + expect(container).toBeInTheDocument(); +}); + +test('should render the navigation', () => { + render(); + expect(screen.getByRole('navigation')).toBeInTheDocument(); +}); + +test('should render the brand', () => { + const { + data: { + brand: { alt, icon }, + }, + } = mockedProps; + render(); + 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.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(); + 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(); + const settings = screen.getByText('Settings'); + expect(settings).toHaveAttribute('href', '#'); +}); + +test('should render the Settings menu item', () => { + render(); + expect(screen.getByText('Security')).toBeInTheDocument(); +}); + +test('should render the Settings dropdown child menu items', () => { + const { + data: { settings }, + } = mockedProps; + render(); + 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(); + expect(screen.getByTestId('new-dropdown')).toBeInTheDocument(); +}); + +test('should NOT render the plus menu (+) when user is anonymous', () => { + render(); + 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(); + 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(); + expect(screen.queryByText('User')).not.toBeInTheDocument(); +}); + +test('should render the Profile link when available', () => { + const { + data: { + navbar_right: { user_profile_url }, + }, + } = mockedProps; + + render(); + 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(); + 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(); + 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(); + 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(); + const login = screen.getByText('Login'); + expect(login).toHaveAttribute('href', user_login_url); +}); + +test('should render the Language Picker', () => { + render(); + expect(screen.getByTestId('language-picker')).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/components/Menu/NewMenu.test.tsx b/superset-frontend/src/components/Menu/NewMenu.test.tsx new file mode 100644 index 0000000000..b325370cec --- /dev/null +++ b/superset-frontend/src/components/Menu/NewMenu.test.tsx @@ -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(); + expect(container).toBeInTheDocument(); +}); + +test('should render the dropdown items', () => { + render(); + dropdownItems.forEach(item => { + expect(screen.getByText(item.label)).toHaveAttribute('href', item.url); + expect(screen.getByTestId(`menu-item-${item.label}`)).toBeInTheDocument(); + }); +}); diff --git a/superset-frontend/src/components/Menu/NewMenu.tsx b/superset-frontend/src/components/Menu/NewMenu.tsx index b2aa9b2ad7..8ff46d2953 100644 --- a/superset-frontend/src/components/Menu/NewMenu.tsx +++ b/superset-frontend/src/components/Menu/NewMenu.tsx @@ -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', diff --git a/superset-frontend/src/components/Menu/SubMenu.test.tsx b/superset-frontend/src/components/Menu/SubMenu.test.tsx new file mode 100644 index 0000000000..a7c2a557f3 --- /dev/null +++ b/superset-frontend/src/components/Menu/SubMenu.test.tsx @@ -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(); + expect(container).toBeInTheDocument(); +}); + +test('should render the navigation', () => { + render(); + expect(screen.getByRole('navigation')).toBeInTheDocument(); +}); + +test('should render the brand', () => { + render(); + expect(screen.getByText('Title')).toBeInTheDocument(); +}); + +test('should render the right number of tabs', () => { + render(); + expect(screen.getAllByRole('tab')).toHaveLength(3); +}); + +test('should render all the tabs links', () => { + const { tabs } = mockedProps; + render(); + 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(); + const testButton = screen.getByText(buttons[0].name); + expect(screen.getAllByRole('button')).toHaveLength(2); + userEvent.click(testButton); + expect(mockFunc).toHaveBeenCalled(); +}); diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx index 0efd3cdf5f..d109aa7f4c 100644 --- a/superset-frontend/src/components/Menu/SubMenu.tsx +++ b/superset-frontend/src/components/Menu/SubMenu.tsx @@ -141,6 +141,7 @@ const SubMenu: React.FunctionComponent = props => { return (
  • @@ -158,6 +159,7 @@ const SubMenu: React.FunctionComponent = props => { className={cx('no-router', { active: tab.name === props.activeChild, })} + role="tab" > {tab.label}