refactoring and updating tests

This commit is contained in:
Alex Corey
2019-04-16 15:03:22 -04:00
parent 2daf202e52
commit 76a7a76e81
7 changed files with 147 additions and 246 deletions

View File

@@ -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('<RoutedTabs />', () => {
test('RoutedTabs renders successfully', () => {
wrapper = mount(
<I18nProvider>
<Router history={history}>
<RoutedTabs
tabsArray={tabs}
/>
</Router>
</I18nProvider>
).find('RoutedTabs');
});
test('the correct tab is rendered', async () => {
const currentTab = 'organizations/1/details';
wrapper = mount(
<I18nProvider>
<Router history={history}>
<RoutedTabs
tabsArray={tabs}
location={currentTab}
/>
</Router>
</I18nProvider>
).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(
<I18nProvider>
<Router history={currentTab}>
<RoutedTabs
tabsArray={tabs}
/>
</Router>
</I18nProvider>
).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();
});
});
});

View File

@@ -3,56 +3,19 @@ import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { I18nProvider } from '@lingui/react'; 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('<OrganizationView />', () => { describe('<OrganizationView />', () => {
test('initially renders succesfully', () => { test('initially renders succesfully', () => {
const spy = jest.spyOn(_Organization.prototype, 'checkLocation');
mount( mount(
<I18nProvider> <I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}> <MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<_Organization <Organization
match={{ path: '/organizations/:id', url: '/organizations/1' }} match={{ path: '/organizations/:id', url: '/organizations/1' }}
location={{ search: '', pathname: '/organizations/1' }} location={{ search: '', pathname: '/organizations/1' }}
/> />
</MemoryRouter> </MemoryRouter>
</I18nProvider> </I18nProvider>
); );
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(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<Organization location={currentTab} />
</MemoryRouter>
</I18nProvider>
).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(
<I18nProvider>
<MemoryRouter initialEntries={['/organizations/1']} initialIndex={0}>
<Organization location={currentTab} />
</MemoryRouter>
</I18nProvider>
).find('Organization');
setImmediate(async () => {
wrapper.setState({ activeTabKey: 1 });
});
wrapper.find('button').at(3).simulate('click');
expect(wrapper.state('activeTabKey')).toBe(2);
}); });
}); });

View File

@@ -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 (
<Tabs
activeKey={this.getActiveTabId()}
onSelect={this.handleTabSelect}
>
{tabsArray.map(tabElement => (
<Tab
className={`${tabElement.name}`}
aria-label={`${tabElement.name}`}
eventKey={tabElement.id}
key={tabElement.id}
link={tabElement.link}
title={tabElement.name}
/>
))}
</Tabs>
);
}
}
export { RoutedTabs as _RoutedTabs };
export default withRouter(RoutedTabs);

View File

@@ -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 }) => (
<li className="pf-c-tabs__item">
<NavLink
to={link}
replace={replace}
className="pf-c-tabs__button"
activeClassName="pf-m-current"
>
{children}
</NavLink>
</li>
);
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;

View File

@@ -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 }) => (
<div
aria-label={labelText}
className="pf-c-tabs"
>
<ul className="pf-c-tabs__list">
{children}
</ul>
{closeButton
&& (
<Tooltip
content={closeButton.text}
position="top"
>
<Link to={closeButton.link}>
<Button
variant="plain"
aria-label={closeButton.text}
>
<TimesIcon />
</Button>
</Link>
</Tooltip>
)
}
</div>
);
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;

View File

@@ -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;
}
}

View File

@@ -12,8 +12,6 @@ import {
Card, Card,
CardHeader, CardHeader,
PageSection, PageSection,
Tab,
Tabs
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { import {
TimesIcon TimesIcon
@@ -25,6 +23,7 @@ import OrganizationDetail from './OrganizationDetail';
import OrganizationEdit from './OrganizationEdit'; import OrganizationEdit from './OrganizationEdit';
import OrganizationNotifications from './OrganizationNotifications'; import OrganizationNotifications from './OrganizationNotifications';
import OrganizationTeams from './OrganizationTeams'; import OrganizationTeams from './OrganizationTeams';
import RoutedTabs from '../../../../components/Tabs/RoutedTabs';
class Organization extends Component { class Organization extends Component {
constructor (props) { constructor (props) {
@@ -34,17 +33,9 @@ class Organization extends Component {
organization: null, organization: null,
error: false, error: false,
loading: true, 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.fetchOrganization = this.fetchOrganization.bind(this);
this.handleTabSelect = this.handleTabSelect.bind(this);
this.checkLocation = this.checkLocation.bind(this);
} }
componentDidMount () { componentDidMount () {
@@ -75,28 +66,9 @@ class Organization extends Component {
this.setState({ error: true }); this.setState({ error: true });
} finally { } finally {
this.setState({ loading: false }); 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 () { render () {
const { const {
location, location,
@@ -104,36 +76,25 @@ class Organization extends Component {
history history
} = this.props; } = this.props;
const { const {
activeTabKey,
organization, organization,
error, error,
loading, loading,
tabElements
} = this.state; } = this.state;
let cardHeader = ( let cardHeader = (
<CardHeader> <CardHeader>
<I18n> <I18n>
{({ i18n }) => ( {({ i18n }) => (
<> <React.Fragment>
<Tabs <RoutedTabs
labeltext={i18n._(t`Organization detail tabs`)} labeltext={i18n._(t`Organization detail tabs`)}
activeKey={activeTabKey} tabsArray={[
onSelect={(event, eventKey) => { { name: i18nMark('Details'), link: `${match.url}/details`, id: 0 },
this.handleTabSelect(event, eventKey); { 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 },
{tabElements.map(tabElement => ( ]}
<Tab />
className={`${tabElement.name}`}
aria-label={`${tabElement.name}`}
eventKey={tabElement.id}
key={tabElement.id}
link={tabElement.link}
title={tabElement.name}
/>
))}
</Tabs>
<Link <Link
aria-label="Close" aria-label="Close"
title="Close" title="Close"
@@ -141,7 +102,7 @@ class Organization extends Component {
> >
<TimesIcon className="OrgsTab-closeButton" /> <TimesIcon className="OrgsTab-closeButton" />
</Link> </Link>
</> </React.Fragment>
)} )}
</I18n> </I18n>
</CardHeader> </CardHeader>