1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 08:21:15 +03:00

Merge remote-tracking branch 'origin' into access-list-remove-role-functionality

This commit is contained in:
Kia Lam 2019-03-13 15:43:30 -04:00
commit 21156d1409
No known key found for this signature in database
GPG Key ID: 294F9BE53C241D03
11 changed files with 188 additions and 207 deletions

View File

@ -119,6 +119,63 @@ describe('<Pagination />', () => {
pageSizeDropdownItems.at(1).simulate('click');
});
test('itemCount displays correctly', () => {
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={7}
page={1}
pageCount={2}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
const itemCount = pagination.find('.awx-pagination__item-count');
expect(itemCount.text()).toEqual('Items 1 5 of 7');
});
test('itemCount matching pageSize displays correctly', () => {
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={5}
page={1}
pageCount={1}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
const itemCount = pagination.find('.awx-pagination__item-count');
expect(itemCount.text()).toEqual('Items 1 5 of 5');
});
test('itemCount less than pageSize displays correctly', () => {
const onSetPage = jest.fn();
pagination = mount(
<I18nProvider>
<Pagination
count={3}
page={1}
pageCount={1}
page_size={5}
pageSizeOptions={[5, 10, 25, 50]}
onSetPage={onSetPage}
/>
</I18nProvider>
);
const itemCount = pagination.find('.awx-pagination__item-count');
expect(itemCount.text()).toEqual('Items 1 3 of 3');
});
test('submit a new page by typing in input works', () => {
const textInputSelector = '.awx-pagination__page-input.pf-c-form-control';
const submitFormSelector = '.awx-pagination__page-input-form';

View File

@ -1,69 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import Tooltip from '../../src/components/Tooltip';
describe('<Tooltip />', () => {
let elem;
let content;
let mouseOverHandler;
let mouseOutHandler;
const child = (<span>foo</span>);
const message = 'hi';
test('initially renders without crashing', () => {
elem = mount(
<Tooltip message={message}>
{child}
</Tooltip>
);
expect(elem.length).toBe(1);
});
test('shows/hides with mouse over and leave', () => {
elem = mount(
<Tooltip message={message}>
{child}
</Tooltip>
);
mouseOverHandler = elem.find('.mouseOverHandler');
mouseOutHandler = elem.find('.mouseOutHandler');
expect(elem.state().isDisplayed).toBe(false);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(0);
mouseOverHandler.props().onMouseOver();
expect(elem.state().isDisplayed).toBe(true);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(1);
mouseOutHandler.props().onMouseLeave();
expect(elem.state().isDisplayed).toBe(false);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(0);
});
test('shows/hides with focus and blur', () => {
elem = mount(
<Tooltip message={message}>
{child}
</Tooltip>
);
mouseOverHandler = elem.find('.mouseOverHandler');
mouseOutHandler = elem.find('.mouseOutHandler');
expect(elem.state().isDisplayed).toBe(false);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(0);
mouseOverHandler.props().onFocus();
expect(elem.state().isDisplayed).toBe(true);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(1);
mouseOutHandler.props().onBlur();
expect(elem.state().isDisplayed).toBe(false);
elem.update();
content = elem.find('.pf-c-tooltip__content');
expect(content.length).toBe(0);
});
});

View File

@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router-dom';
import { MemoryRouter, Router } from 'react-router-dom';
import { I18nProvider } from '@lingui/react';
import { ConfigContext } from '../../../../src/context';
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
@ -18,6 +18,7 @@ describe('<OrganizationAdd />', () => {
</MemoryRouter>
);
});
test('calls "onFieldChange" when input values change', () => {
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onFieldChange');
const wrapper = mount(
@ -35,6 +36,7 @@ describe('<OrganizationAdd />', () => {
wrapper.find('input#add-org-form-description').simulate('change', { target: { value: 'bar' } });
expect(spy).toHaveBeenCalledTimes(2);
});
test('calls "onSubmit" when Save button is clicked', () => {
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit');
const wrapper = mount(
@ -51,6 +53,7 @@ describe('<OrganizationAdd />', () => {
wrapper.find('button[aria-label="Save"]').prop('onClick')();
expect(spy).toBeCalled();
});
test('calls "onCancel" when Cancel button is clicked', () => {
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel');
const wrapper = mount(
@ -68,6 +71,25 @@ describe('<OrganizationAdd />', () => {
expect(spy).toBeCalled();
});
test('calls "onCancel" when close button (x) is clicked', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/organizations/add']} initialIndex={0}>
<I18nProvider>
<OrganizationAdd
match={{ path: '/organizations/add', url: '/organizations/add' }}
location={{ search: '', pathname: '/organizations/add' }}
/>
</I18nProvider>
</MemoryRouter>
);
const history = wrapper.find(Router).prop('history');
expect(history.length).toBe(1);
expect(history.location.pathname).toEqual('/organizations/add');
wrapper.find('button[aria-label="Close"]').prop('onClick')();
expect(history.length).toBe(2);
expect(history.location.pathname).toEqual('/organizations');
});
test('Successful form submission triggers redirect', (done) => {
const onSuccess = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSuccess');
const mockedResp = { data: { id: 1, related: { instance_groups: '/bar' } } };

View File

@ -206,6 +206,21 @@
--pf-c-card__body--PaddingTop: 24px;
}
//
// pf tooltip overrides
//
.pf-c-tooltip__content {
--pf-c-tooltip__content--PaddingTop: 0.71rem;
--pf-c-tooltip__content--PaddingRight: 0.71rem;
--pf-c-tooltip__content--PaddingBottom: 0.71rem;
--pf-c-tooltip__content--PaddingLeft: 0.71rem;
}
// higher specificity needed to override PF styles added dynamically to page
.pf-c-tooltip .pf-c-tooltip__content {
text-align: left;
}
//
// pf empty state overrides
//
@ -219,6 +234,13 @@
// note that these should be given a consistent prefix
// and bem style, as well as moved into component-based scss files
//
.awx-lookup {
min-height: 36px;
.pf-c-form-control {
--pf-c-form-control--Height: auto;
}
}
.awx-c-list {
border-bottom: 1px solid #d7d7d7;
@ -236,22 +258,20 @@
position: relative;
}
.awx-c-alert {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.pf-c-alert {
position: absolute;
width: 100%;
z-index: 20;
& button {
button {
margin-right: 20px;
}
}
.pf-c-alert__icon > svg {
fill: white;
}
.pf-c-alert__icon {
--pf-c-alert__icon--Color: white;
}
.at-u-textRight {
text-align: right;
}

View File

@ -15,6 +15,7 @@ import {
Toolbar,
ToolbarGroup,
ToolbarItem,
Tooltip,
} from '@patternfly/react-core';
import {
BarsIcon,
@ -30,7 +31,6 @@ import {
Link
} from 'react-router-dom';
import Tooltip from '../Tooltip';
import VerticalSeparator from '../VerticalSeparator';
const flexGrowStyling = {
@ -291,7 +291,7 @@ class DataListToolbar extends React.Component {
<LevelItem>
{ showDelete && (
<Tooltip
message={i18n._(t`Delete`)}
content={i18n._(t`Delete`)}
position="top"
>
<Button

View File

@ -34,6 +34,7 @@ class NotificationListItem extends React.Component {
to={{
pathname: detailUrl
}}
style={{ marginRight: '1.5em' }}
>
<b>{name}</b>
</Link>
@ -82,4 +83,3 @@ NotificationListItem.defaultProps = {
};
export default NotificationListItem;

View File

@ -116,7 +116,12 @@ class Pagination extends Component {
const isOnFirst = page === 1;
const isOnLast = page === pageCount;
const itemCount = isOnLast ? count % page_size : page_size;
let itemCount;
if (!isOnLast || count === page_size) {
itemCount = page_size;
} else {
itemCount = count % page_size;
}
const itemMin = ((page - 1) * page_size) + 1;
const itemMax = itemMin + itemCount - 1;
@ -154,7 +159,7 @@ class Pagination extends Component {
)}
<div className="awx-pagination__counts">
<div className="awx-pagination__item-count">
<Trans>{`Items ${itemMin} - ${itemMax} of ${count}`}</Trans>
<Trans>{`Items ${itemMin} ${itemMax} of ${count}`}</Trans>
</div>
{pageCount !== 1 && (
<div className="awx-pagination__page-count">

View File

@ -1,9 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Button } from '@patternfly/react-core';
import { Button, Tooltip } from '@patternfly/react-core';
import { TimesIcon } from '@patternfly/react-icons';
import Tooltip from '../Tooltip';
import './tabs.scss';
const Tabs = ({ children, labelText, closeButton }) => (
@ -17,7 +16,7 @@ const Tabs = ({ children, labelText, closeButton }) => (
{closeButton
&& (
<Tooltip
message={closeButton.text}
content={closeButton.text}
position="top"
>
<Link to={closeButton.link}>

View File

@ -1,96 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const toolTipContent = {
padding: '10px',
minWidth: '100px',
};
class Tooltip extends React.Component {
transforms = {
top: {
bottom: '100%',
left: '50%',
transform: 'translate(-50%, -25%)'
},
bottom: {
top: '100%',
left: '50%',
transform: 'translate(-50%, 25%)'
},
left: {
top: '50%',
right: '100%',
transform: 'translate(-25%, -50%)'
},
right: {
bottom: '100%',
left: '50%',
transform: 'translate(25%, 50%)'
},
};
constructor (props) {
super(props);
this.state = {
isDisplayed: false
};
}
render () {
const {
children,
message,
position,
} = this.props;
const {
isDisplayed
} = this.state;
if (message === '') {
return null;
}
return (
<span
style={{ position: 'relative' }}
className="mouseOutHandler"
onMouseLeave={() => this.setState({ isDisplayed: false })}
onBlur={() => this.setState({ isDisplayed: false })}
>
{ isDisplayed
&& (
<div
style={{ position: 'absolute', zIndex: '10', ...this.transforms[position] }}
className={`pf-c-tooltip pf-m-${position}`}
>
<div className="pf-c-tooltip__arrow" />
<div className="pf-c-tooltip__content" style={toolTipContent}>
{ message }
</div>
</div>
)
}
<span
className="mouseOverHandler"
onMouseOver={() => this.setState({ isDisplayed: true })}
onFocus={() => this.setState({ isDisplayed: true })}
>
{ children }
</span>
</span>
);
}
}
Tooltip.propTypes = {
children: PropTypes.element.isRequired,
message: PropTypes.string.isRequired,
position: PropTypes.string,
};
Tooltip.defaultProps = {
position: 'top',
};
export default Tooltip;

View File

@ -1,3 +0,0 @@
import Tooltip from './Tooltip';
export default Tooltip;

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { I18n, i18nMark } from '@lingui/react';
@ -10,8 +10,12 @@ import {
TextInput,
Gallery,
Card,
CardHeader,
CardBody,
Button,
Tooltip,
} from '@patternfly/react-core';
import { QuestionCircleIcon, TimesIcon } from '@patternfly/react-icons';
import { ConfigContext } from '../../../context';
import Lookup from '../../../components/Lookup';
@ -108,11 +112,25 @@ class OrganizationAdd extends React.Component {
return (
<PageSection>
<Card>
<CardBody>
<Form autoComplete="off">
<I18n>
{({ i18n }) => (
<I18n>
{({ i18n }) => (
<Card>
<CardHeader className="at-u-textRight">
<Tooltip
content={i18n._(t`Close`)}
position="top"
>
<Button
variant="plain"
aria-label={i18n._(t`Close`)}
onClick={this.onCancel}
>
<TimesIcon />
</Button>
</Tooltip>
</CardHeader>
<CardBody>
<Form autoComplete="off">
<Gallery gutter="md">
<FormGroup
label={i18n._(t`Name`)}
@ -135,7 +153,21 @@ class OrganizationAdd extends React.Component {
onChange={this.onFieldChange}
/>
</FormGroup>
<FormGroup label={i18n._(t`Instance Groups`)} fieldId="add-org-form-instance-groups">
<FormGroup
label={(
<Fragment>
{i18n._(t`Instance Groups`)}
{' '}
<Tooltip
position="right"
content={i18n._(t`Select the Instance Groups for this Organization to run on.`)}
>
<QuestionCircleIcon />
</Tooltip>
</Fragment>
)}
fieldId="add-org-form-instance-groups"
>
<Lookup
lookupHeader={i18n._(t`Instance Groups`)}
name="instanceGroups"
@ -149,7 +181,21 @@ class OrganizationAdd extends React.Component {
<ConfigContext.Consumer>
{({ custom_virtualenvs }) => (
custom_virtualenvs && custom_virtualenvs.length > 1 && (
<FormGroup label={i18n._(t`Ansible Environment`)} fieldId="add-org-custom-virtualenv">
<FormGroup
label={(
<Fragment>
{i18n._(t`Ansible Environment`)}
{' '}
<Tooltip
position="right"
content={i18n._(t`Select the custom Python virtual environment for this organization to run on.`)}
>
<QuestionCircleIcon />
</Tooltip>
</Fragment>
)}
fieldId="add-org-custom-virtualenv"
>
<AnsibleSelect
label={i18n._(t`Ansible Environment`)}
name="custom_virtualenv"
@ -163,17 +209,17 @@ class OrganizationAdd extends React.Component {
)}
</ConfigContext.Consumer>
</Gallery>
)}
</I18n>
<FormActionGroup
onSubmit={this.onSubmit}
submitDisabled={!enabled}
onCancel={this.onCancel}
/>
{error ? <div>error</div> : ''}
</Form>
</CardBody>
</Card>
<FormActionGroup
onSubmit={this.onSubmit}
submitDisabled={!enabled}
onCancel={this.onCancel}
/>
{error ? <div>error</div> : ''}
</Form>
</CardBody>
</Card>
)}
</I18n>
</PageSection>
);
}