diff --git a/__tests__/components/RoutedTabs.test.jsx b/__tests__/components/RoutedTabs.test.jsx new file mode 100644 index 0000000000..05bc5aeee7 --- /dev/null +++ b/__tests__/components/RoutedTabs.test.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; +import { I18nProvider } from '@lingui/react'; +import RoutedTabs from '../../src/components/Tabs/RoutedTabs'; +import { DonateIcon } from '@patternfly/react-icons'; + +let wrapper; + +afterEach(() => { + jest.clearAllMocks(); +}); +const tabs = [ + { name: 'Details', link: 'organizations/19/details', id: 0 }, + { name: 'Access', link: 'organizations/19/access', id: 1 }, + { name: 'Teams', link: 'organizations/19/teams', id: 2 } +]; + +const history = createMemoryHistory({ + history: { + location: { + pathname: '/organizations/19/details' + } + } +}); + +describe('', () => { + test('RoutedTabs renders successfully', () => { + wrapper = mount( + + + + + + ).find('RoutedTabs'); + }); + + test('the correct tab is rendered', async () => { + const currentTab = 'organizations/1/details'; + wrapper = mount( + + + + + + ).find('RoutedTabs'); + wrapper.find('button').at(2).simulate('click'); + wrapper.update(); + expect(history.location.pathname).toEqual('/organizations/19/access'); + }); + + test('Given a URL the correct tab is displayed', async (done) => { + const currentTab = createMemoryHistory({ + initialEntries: ['/organizations/19/teams'], + }); + wrapper = mount( + + + + + + ).find('RoutedTabs'); + setImmediate(() => { + wrapper.find('Tabs').prop('onSelect')({}, 2); + const selectedTab = wrapper.find('li').get(2).props.className; + expect(selectedTab).toBe('pf-c-tabs__item pf-m-current'); + done(); + }); + }); +}); diff --git a/__tests__/pages/Organizations/screens/Organization/Organization.test.jsx b/__tests__/pages/Organizations/screens/Organization/Organization.test.jsx index d7746c5838..a3b59a9e0c 100644 --- a/__tests__/pages/Organizations/screens/Organization/Organization.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/Organization.test.jsx @@ -3,56 +3,19 @@ import { mount } from 'enzyme'; import { MemoryRouter } from 'react-router-dom'; import { I18nProvider } from '@lingui/react'; -import Organization, { _Organization } from '../../../../../src/pages/Organizations/screens/Organization/Organization'; +import Organization from '../../../../../src/pages/Organizations/screens/Organization/Organization'; describe('', () => { test('initially renders succesfully', () => { - const spy = jest.spyOn(_Organization.prototype, 'checkLocation'); mount( - <_Organization + ); - expect(spy).toHaveBeenCalled(); - }); - - test('handleTabSelect renders the correct tab', async () => { - const currentTab = 'organizations/19/access'; - const spy = jest.spyOn(_Organization.prototype, 'handleTabSelect'); - const wrapper = mount( - - - - - - ).find('Organization'); - wrapper.find('button').at(2).simulate('click'); - setImmediate(async () => { - wrapper.setState({ activeTabKey: 1 }); - }); - wrapper.update(); - expect(spy).toBeCalled(); - expect(wrapper.state('activeTabKey')).toBe(1); - }); - - test('checkLocation renders proper state when new tab is selected', async () => { - const currentTab = 'organizations/19/access'; - const wrapper = mount( - - - - - - ).find('Organization'); - setImmediate(async () => { - wrapper.setState({ activeTabKey: 1 }); - }); - wrapper.find('button').at(3).simulate('click'); - expect(wrapper.state('activeTabKey')).toBe(2); }); }); diff --git a/src/components/Tabs/RoutedTabs.jsx b/src/components/Tabs/RoutedTabs.jsx new file mode 100644 index 0000000000..820eace467 --- /dev/null +++ b/src/components/Tabs/RoutedTabs.jsx @@ -0,0 +1,55 @@ +import React from 'react'; + +import { + withRouter +} from 'react-router-dom'; + +import { + Tab, + Tabs +} from '@patternfly/react-core'; + +class RoutedTabs extends React.Component { + constructor (props) { + super(props); + + this.handleTabSelect = this.handleTabSelect.bind(this); + } + + getActiveTabId () { + const { history, tabsArray } = this.props; + const matchTab = tabsArray.find(selectedTab => selectedTab.link === history.location.pathname); + return matchTab ? matchTab.id : 0; + } + + handleTabSelect (event, eventKey) { + const { history, tabsArray } = this.props; + + const tab = tabsArray.find(tabElement => tabElement.id === eventKey); + history.push(tab.link); + } + + render () { + const { tabsArray } = this.props; + return ( + + {tabsArray.map(tabElement => ( + + ))} + + ); + } +} + +export { RoutedTabs as _RoutedTabs }; +export default withRouter(RoutedTabs); diff --git a/src/components/Tabs/Tab.jsx b/src/components/Tabs/Tab.jsx deleted file mode 100644 index 6dd9409391..0000000000 --- a/src/components/Tabs/Tab.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { NavLink } from 'react-router-dom'; -import './tabs.scss'; - -const Tab = ({ children, link, replace }) => ( -
  • - - {children} - -
  • -); - -Tab.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node - ]).isRequired, - link: PropTypes.string, - replace: PropTypes.bool, -}; - -Tab.defaultProps = { - link: null, - replace: false, -}; - -export default Tab; diff --git a/src/components/Tabs/Tabs.jsx b/src/components/Tabs/Tabs.jsx deleted file mode 100644 index abea04f8b8..0000000000 --- a/src/components/Tabs/Tabs.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; -import { Button, Tooltip } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; -import './tabs.scss'; - -const Tabs = ({ children, labelText, closeButton }) => ( -
    -
      - {children} -
    - {closeButton - && ( - - - - - - ) - } -
    -); - -Tabs.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node - ]).isRequired, - labelText: PropTypes.string, - closeButton: PropTypes.shape({ - text: PropTypes.string, - link: PropTypes.string, - }), -}; - -Tabs.defaultProps = { - labelText: null, - closeButton: null, -}; - -export default Tabs; diff --git a/src/components/Tabs/tabs.scss b/src/components/Tabs/tabs.scss deleted file mode 100644 index 651be6acba..0000000000 --- a/src/components/Tabs/tabs.scss +++ /dev/null @@ -1,71 +0,0 @@ -.pf-c-card__header { - --pf-c-card__header--PaddingBottom: 0; - --pf-c-card__header--PaddingX: 0; - --pf-c-card__header--PaddingRight: 0; - --pf-c-card__header--PaddingLeft: 0; - --pf-c-card__header--PaddingTop: 0; -} - -.pf-c-tabs { - --pf-global--link--Color: #484848; - --pf-global--link--Color--hover: #484848; - --pf-global--link--TextDecoration--hover: none; - - align-items: center; - flex-direction: row; - justify-content: space-between; - - &:before { - border-bottom: 1px solid var(--pf-c-tabs__item--BorderColor); - border-top: 1px solid var(--pf-c-tabs__item--BorderColor); - bottom: 0; - content: " "; - left: 0; - position: absolute; - right: 0; - top: 0; - } - - .pf-c-tabs__button { - --pf-c-tabs__button--PaddingLeft: 20px; - --pf-c-tabs__button--PaddingRight: 20px; - display: block; - font-weight: 700; - - &:after { - content: ''; - bottom: 0; - left: 0; - position: absolute; - right: 0; - top: 0; - } - } - - .pf-c-tabs__item:first-child .pf-c-tabs__button:before { - border-left: 0; - } - - .pf-c-tabs__item:not(.pf-m-current):hover - .pf-c-tabs__button::after { - border-top: none; - } - - .pf-c-tabs__item:hover - .pf-c-tabs__button:not(.pf-m-current)::after { - border-bottom: 3px solid var(--pf-global--Color--dark-200); - border-top: none; - } - - .pf-c-tabs__button.pf-m-current { - color: var(--pf-c-tabs__item--m-current--Color); - } - - .pf-c-tabs__button.pf-m-current::after { - content: ''; - border-bottom: 3px solid var(--pf-c-tabs__item--m-current--Color); - border-top: none; - margin-left: 1px; - } -} - diff --git a/src/pages/Organizations/screens/Organization/Organization.jsx b/src/pages/Organizations/screens/Organization/Organization.jsx index b3a92c77cb..1df548b796 100644 --- a/src/pages/Organizations/screens/Organization/Organization.jsx +++ b/src/pages/Organizations/screens/Organization/Organization.jsx @@ -12,8 +12,6 @@ import { Card, CardHeader, PageSection, - Tab, - Tabs } from '@patternfly/react-core'; import { TimesIcon @@ -25,6 +23,7 @@ import OrganizationDetail from './OrganizationDetail'; import OrganizationEdit from './OrganizationEdit'; import OrganizationNotifications from './OrganizationNotifications'; import OrganizationTeams from './OrganizationTeams'; +import RoutedTabs from '../../../../components/Tabs/RoutedTabs'; class Organization extends Component { constructor (props) { @@ -34,17 +33,9 @@ class Organization extends Component { organization: null, error: false, loading: true, - tabElements: [ - { name: i18nMark('Details'), link: `${props.match.url}/details`, id: 0 }, - { name: i18nMark('Access'), link: `${props.match.url}/access`, id: 1 }, - { name: i18nMark('Teams'), link: `${props.match.url}/teams`, id: 2 }, - { name: i18nMark('Notifications'), link: `${props.match.url}/notifications`, id: 3 }, - ], }; this.fetchOrganization = this.fetchOrganization.bind(this); - this.handleTabSelect = this.handleTabSelect.bind(this); - this.checkLocation = this.checkLocation.bind(this); } componentDidMount () { @@ -75,28 +66,9 @@ class Organization extends Component { this.setState({ error: true }); } finally { this.setState({ loading: false }); - this.checkLocation(); } } - checkLocation () { - const { location } = this.props; - const { tabElements } = this.state; - const activeTab = tabElements.filter(tabElement => tabElement.link === location.pathname); - this.setState({ activeTabKey: activeTab[0].id }); - } - - handleTabSelect (event, eventKey) { - const { history } = this.props; - const { tabElements } = this.state; - - const tab = tabElements.find(tabElement => tabElement.id === eventKey); - history.push(tab.link); - const activeTab = tabElements.filter(selectedTab => selectedTab.link === tab.link) - .map(selectedTab => selectedTab.id); - this.setState({ activeTabKey: activeTab[0] }); - } - render () { const { location, @@ -104,36 +76,25 @@ class Organization extends Component { history } = this.props; const { - activeTabKey, organization, error, loading, - tabElements } = this.state; let cardHeader = ( {({ i18n }) => ( - <> - + { - this.handleTabSelect(event, eventKey); - }} - > - {tabElements.map(tabElement => ( - - ))} - + tabsArray={[ + { name: i18nMark('Details'), link: `${match.url}/details`, id: 0 }, + { name: i18nMark('Access'), link: `${match.url}/access`, id: 1 }, + { name: i18nMark('Teams'), link: `${match.url}/teams`, id: 2 }, + { name: i18nMark('Notifications'), link: `${match.url}/notifications`, id: 3 }, + ]} + /> - + )}