diff --git a/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx b/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx new file mode 100644 index 0000000000..b7ec4e2cd3 --- /dev/null +++ b/superset-frontend/spec/javascripts/components/SubMenu_spec.jsx @@ -0,0 +1,88 @@ +/** + * 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, MenuItem } from 'react-bootstrap'; +import SubMenu from 'src/components/Menu/SubMenu'; + +const defaultProps = { + name: 'Title', + children: [ + { + 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(MenuItem)).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(MenuItem)).toHaveLength(1); + }); +}); diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx index 15995ed028..bc965ef8ce 100644 --- a/superset-frontend/src/components/Menu/SubMenu.tsx +++ b/superset-frontend/src/components/Menu/SubMenu.tsx @@ -17,6 +17,7 @@ * under the License. */ import React from 'react'; +import { Link, useHistory } from 'react-router-dom'; import { styled } from '@superset-ui/core'; import { Nav, Navbar, MenuItem } from 'react-bootstrap'; import Button, { OnClickHandler } from 'src/components/Button'; @@ -31,16 +32,29 @@ const StyledHeader = styled.header` } .navbar-nav { li { - a { + a, + div { font-size: ${({ theme }) => theme.typography.sizes.s}px; - padding: ${({ theme }) => theme.gridUnit * 2}px; + padding: ${({ theme }) => theme.gridUnit * 2}px 0; margin: ${({ theme }) => theme.gridUnit * 2}px; color: ${({ theme }) => theme.colors.secondary.dark1}; + + a { + margin: 0; + padding: ${({ theme }) => theme.gridUnit * 4}px; + } + } + + &.no-router a { + padding: ${({ theme }) => theme.gridUnit * 2}px + ${({ theme }) => theme.gridUnit * 4}px; } } li.active > a, - li > a:hover { + li.active > div, + li > a:hover, + li > div:hover { background-color: ${({ theme }) => theme.colors.secondary.light4}; border-bottom: none; border-radius: 4px; @@ -52,6 +66,7 @@ type MenuChild = { label: string; name: string; url: string; + usesRouter?: boolean; }; export interface SubMenuProps { @@ -66,9 +81,23 @@ export interface SubMenuProps { name: string; children?: MenuChild[]; activeChild?: MenuChild['name']; + /* If usesRouter is true, a react-router component will be used instead of href. + * ONLY set usesRouter to true if SubMenu is wrapped in a react-router ; + * otherwise, a 'You should not use outside a ' error will be thrown */ + usesRouter?: boolean; } const SubMenu: React.FunctionComponent = props => { + let hasHistory = true; + + // If no parent component exists, useHistory throws an error + try { + useHistory(); + } catch (err) { + // If error is thrown, we know not to use in render + hasHistory = false; + } + return ( @@ -77,15 +106,31 @@ const SubMenu: React.FunctionComponent = props => {