mirror of
https://github.com/ansible/awx.git
synced 2026-04-14 14:39:26 -02:30
flush out tests for PaginatedTable and related components
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import ActionItem from './ActionItem';
|
||||||
|
|
||||||
|
describe('<ActionItem />', () => {
|
||||||
|
test('should render child with tooltip', async () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ActionItem columns={1} tooltip="a tooltip" visible>
|
||||||
|
foo
|
||||||
|
</ActionItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tooltip = wrapper.find('Tooltip');
|
||||||
|
expect(tooltip.prop('content')).toEqual('a tooltip');
|
||||||
|
expect(tooltip.prop('children')).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render null if not visible', async () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<ActionItem columns={1} tooltip="foo">
|
||||||
|
<div>foo</div>
|
||||||
|
</ActionItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('Tooltip')).toHaveLength(0);
|
||||||
|
expect(wrapper.find('div')).toHaveLength(0);
|
||||||
|
expect(wrapper.text()).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,6 +15,7 @@ const ActionsGrid = styled.div`
|
|||||||
`;
|
`;
|
||||||
}}
|
}}
|
||||||
`;
|
`;
|
||||||
|
ActionsGrid.displayName = 'ActionsGrid';
|
||||||
|
|
||||||
export default function ActionsTd({ children, ...props }) {
|
export default function ActionsTd({ children, ...props }) {
|
||||||
const numActions = children.length || 1;
|
const numActions = children.length || 1;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const Th = styled(PFTh)`
|
|||||||
--pf-c-table--cell--Overflow: initial;
|
--pf-c-table--cell--Overflow: initial;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function HeaderRow({ qsConfig, defaultSortKey, children }) {
|
export default function HeaderRow({ qsConfig, children }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -33,10 +33,11 @@ export default function HeaderRow({ qsConfig, defaultSortKey, children }) {
|
|||||||
|
|
||||||
const sortKey = params.order_by?.replace('-', '');
|
const sortKey = params.order_by?.replace('-', '');
|
||||||
const sortBy = {
|
const sortBy = {
|
||||||
index: sortKey || defaultSortKey,
|
index: sortKey || qsConfig.defaultParams?.order_by,
|
||||||
direction: params.order_by?.startsWith('-') ? 'desc' : 'asc',
|
direction: params.order_by?.startsWith('-') ? 'desc' : 'asc',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// empty first Th aligns with checkboxes in table rows
|
||||||
return (
|
return (
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
|
|||||||
65
awx/ui_next/src/components/PaginatedTable/HeaderRow.test.jsx
Normal file
65
awx/ui_next/src/components/PaginatedTable/HeaderRow.test.jsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||||
|
import HeaderRow, { HeaderCell } from './HeaderRow';
|
||||||
|
|
||||||
|
describe('<HeaderRow />', () => {
|
||||||
|
const qsConfig = {
|
||||||
|
defaultParams: {
|
||||||
|
order_by: 'one',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should render cells', async () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<table>
|
||||||
|
<HeaderRow qsConfig={qsConfig}>
|
||||||
|
<HeaderCell sortKey="one">One</HeaderCell>
|
||||||
|
<HeaderCell>Two</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
|
||||||
|
const cells = wrapper.find('Th');
|
||||||
|
expect(cells).toHaveLength(3);
|
||||||
|
expect(cells.at(1).text()).toEqual('One');
|
||||||
|
expect(cells.at(2).text()).toEqual('Two');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should provide sort controls', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/list'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<table>
|
||||||
|
<HeaderRow qsConfig={qsConfig}>
|
||||||
|
<HeaderCell sortKey="one">One</HeaderCell>
|
||||||
|
<HeaderCell>Two</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</table>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const cell = wrapper.find('Th').at(1);
|
||||||
|
cell.prop('sort').onSort({}, '', 'desc');
|
||||||
|
expect(history.location.search).toEqual('?order_by=-one');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not sort cells without a sortKey', async () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/list'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<table>
|
||||||
|
<HeaderRow qsConfig={qsConfig}>
|
||||||
|
<HeaderCell sortKey="one">One</HeaderCell>
|
||||||
|
<HeaderCell>Two</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
</table>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const cell = wrapper.find('Th').at(2);
|
||||||
|
expect(cell.prop('sort')).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
parseQueryString,
|
parseQueryString,
|
||||||
replaceParams,
|
replaceParams,
|
||||||
} from '../../util/qs';
|
} from '../../util/qs';
|
||||||
import PaginatedTableRow from './PaginatedTableRow';
|
|
||||||
import { QSConfig, SearchColumns } from '../../types';
|
import { QSConfig, SearchColumns } from '../../types';
|
||||||
|
|
||||||
function PaginatedTable({
|
function PaginatedTable({
|
||||||
@@ -160,7 +159,7 @@ PaginatedTable.propTypes = {
|
|||||||
itemCount: PropTypes.number.isRequired,
|
itemCount: PropTypes.number.isRequired,
|
||||||
pluralizedItemName: PropTypes.string,
|
pluralizedItemName: PropTypes.string,
|
||||||
qsConfig: QSConfig.isRequired,
|
qsConfig: QSConfig.isRequired,
|
||||||
renderRow: PropTypes.func,
|
renderRow: PropTypes.func.isRequired,
|
||||||
toolbarSearchColumns: SearchColumns,
|
toolbarSearchColumns: SearchColumns,
|
||||||
toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
@@ -178,7 +177,6 @@ PaginatedTable.defaultProps = {
|
|||||||
toolbarRelatedSearchableKeys: [],
|
toolbarRelatedSearchableKeys: [],
|
||||||
pluralizedItemName: 'Items',
|
pluralizedItemName: 'Items',
|
||||||
showPageSizeOptions: true,
|
showPageSizeOptions: true,
|
||||||
renderRow: item => <PaginatedTableRow key={item.id} item={item} />,
|
|
||||||
renderToolbar: props => <DataListToolbar {...props} />,
|
renderToolbar: props => <DataListToolbar {...props} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||||
|
import PaginatedTable from './PaginatedTable';
|
||||||
|
|
||||||
|
const mockData = [
|
||||||
|
{ id: 1, name: 'one', url: '/org/team/1' },
|
||||||
|
{ id: 2, name: 'two', url: '/org/team/2' },
|
||||||
|
{ id: 3, name: 'three', url: '/org/team/3' },
|
||||||
|
{ id: 4, name: 'four', url: '/org/team/4' },
|
||||||
|
{ id: 5, name: 'five', url: '/org/team/5' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const qsConfig = {
|
||||||
|
namespace: 'item',
|
||||||
|
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||||
|
integerFields: ['page', 'page_size'],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<PaginatedTable />', () => {
|
||||||
|
test('should render item rows', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/organizations/1/teams'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<PaginatedTable
|
||||||
|
items={mockData}
|
||||||
|
itemCount={7}
|
||||||
|
queryParams={{
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
}}
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
renderRow={item => (
|
||||||
|
<tr key={item.id}>
|
||||||
|
<td>{item.name}</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const rows = wrapper.find('tr');
|
||||||
|
expect(rows).toHaveLength(5);
|
||||||
|
expect(rows.at(0).text()).toEqual('one');
|
||||||
|
expect(rows.at(1).text()).toEqual('two');
|
||||||
|
expect(rows.at(2).text()).toEqual('three');
|
||||||
|
expect(rows.at(3).text()).toEqual('four');
|
||||||
|
expect(rows.at(4).text()).toEqual('five');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should navigate page when changes', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/organizations/1/teams'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<PaginatedTable
|
||||||
|
items={mockData}
|
||||||
|
itemCount={7}
|
||||||
|
queryParams={{
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
}}
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
renderRow={() => null}
|
||||||
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const pagination = wrapper.find('Pagination').at(1);
|
||||||
|
pagination.prop('onSetPage')(null, 2);
|
||||||
|
expect(history.location.search).toEqual('?item.page=2');
|
||||||
|
wrapper.update();
|
||||||
|
pagination.prop('onSetPage')(null, 1);
|
||||||
|
// since page = 1 is the default, that should be strip out of the search
|
||||||
|
expect(history.location.search).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should navigate to page when page size changes', () => {
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/organizations/1/teams'],
|
||||||
|
});
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<PaginatedTable
|
||||||
|
items={mockData}
|
||||||
|
itemCount={7}
|
||||||
|
queryParams={{
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
order_by: 'name',
|
||||||
|
}}
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
renderRow={() => null}
|
||||||
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const pagination = wrapper.find('Pagination').at(1);
|
||||||
|
pagination.prop('onPerPageSelect')(null, 25, 2);
|
||||||
|
expect(history.location.search).toEqual('?item.page=2&item.page_size=25');
|
||||||
|
wrapper.update();
|
||||||
|
// since page_size = 5 is the default, that should be strip out of the search
|
||||||
|
pagination.prop('onPerPageSelect')(null, 5, 2);
|
||||||
|
expect(history.location.search).toEqual('?item.page=2');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
DataListItem,
|
|
||||||
DataListItemRow,
|
|
||||||
DataListItemCells,
|
|
||||||
TextContent,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import DataListCell from '../DataListCell';
|
|
||||||
|
|
||||||
const DetailWrapper = styled(TextContent)`
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns:
|
|
||||||
minmax(70px, max-content)
|
|
||||||
repeat(auto-fit, minmax(60px, max-content));
|
|
||||||
grid-gap: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function PaginatedDataListItem({ item }) {
|
|
||||||
return (
|
|
||||||
<DataListItem
|
|
||||||
aria-labelledby={`items-list-item-${item.id}`}
|
|
||||||
key={item.id}
|
|
||||||
id={`${item.id}`}
|
|
||||||
>
|
|
||||||
<DataListItemRow>
|
|
||||||
<DataListItemCells
|
|
||||||
dataListCells={[
|
|
||||||
<DataListCell key="name">
|
|
||||||
<DetailWrapper>
|
|
||||||
<Link to={{ pathname: item.url }}>
|
|
||||||
<b id={`items-list-item-${item.id}`}>{item.name}</b>
|
|
||||||
</Link>
|
|
||||||
</DetailWrapper>
|
|
||||||
</DataListCell>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</DataListItemRow>
|
|
||||||
</DataListItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export { default } from './PaginatedTable';
|
export { default } from './PaginatedTable';
|
||||||
export { default as PaginatedTableRow } from './PaginatedTableRow';
|
|
||||||
export { default as ActionsTd } from './ActionsTd';
|
export { default as ActionsTd } from './ActionsTd';
|
||||||
export { default as HeaderRow, HeaderCell } from './HeaderRow';
|
export { default as HeaderRow, HeaderCell } from './HeaderRow';
|
||||||
export { default as ActionItem } from './ActionItem';
|
export { default as ActionItem } from './ActionItem';
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ function InventoryList({ i18n }) {
|
|||||||
toolbarSearchableKeys={searchableKeys}
|
toolbarSearchableKeys={searchableKeys}
|
||||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||||
headerRow={
|
headerRow={
|
||||||
<HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
|
<HeaderRow qsConfig={QS_CONFIG}>
|
||||||
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
|
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
|
||||||
<HeaderCell>{i18n._(t`Status`)}</HeaderCell>
|
<HeaderCell>{i18n._(t`Status`)}</HeaderCell>
|
||||||
<HeaderCell>{i18n._(t`Type`)}</HeaderCell>
|
<HeaderCell>{i18n._(t`Type`)}</HeaderCell>
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ function OrganizationsList({ i18n }) {
|
|||||||
toolbarSearchableKeys={searchableKeys}
|
toolbarSearchableKeys={searchableKeys}
|
||||||
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||||
headerRow={
|
headerRow={
|
||||||
<HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
|
<HeaderRow qsConfig={QS_CONFIG}>
|
||||||
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
|
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
|
||||||
<HeaderCell>{i18n._(t`Members`)}</HeaderCell>
|
<HeaderCell>{i18n._(t`Members`)}</HeaderCell>
|
||||||
<HeaderCell>{i18n._(t`Teams`)}</HeaderCell>
|
<HeaderCell>{i18n._(t`Teams`)}</HeaderCell>
|
||||||
|
|||||||
@@ -28,15 +28,20 @@ function OrganizationListItem({
|
|||||||
onSelect,
|
onSelect,
|
||||||
disable: false,
|
disable: false,
|
||||||
}}
|
}}
|
||||||
|
dataLabel={i18n._(t`Selected`)}
|
||||||
/>
|
/>
|
||||||
<Td id={labelId}>
|
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
||||||
<Link to={`${detailUrl}`}>
|
<Link to={`${detailUrl}`}>
|
||||||
<b>{organization.name}</b>
|
<b>{organization.name}</b>
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{organization.summary_fields.related_field_counts.users}</Td>
|
<Td dataLabel={i18n._(t`Members`)}>
|
||||||
<Td>{organization.summary_fields.related_field_counts.teams}</Td>
|
{organization.summary_fields.related_field_counts.users}
|
||||||
<ActionsTd>
|
</Td>
|
||||||
|
<Td dataLabel={i18n._(t`Teams`)}>
|
||||||
|
{organization.summary_fields.related_field_counts.teams}
|
||||||
|
</Td>
|
||||||
|
<ActionsTd dataLabel={i18n._(t`Actions`)}>
|
||||||
<ActionItem
|
<ActionItem
|
||||||
visible={organization.summary_fields.user_capabilities.edit}
|
visible={organization.summary_fields.user_capabilities.edit}
|
||||||
tooltip={i18n._(t`Edit Organization`)}
|
tooltip={i18n._(t`Edit Organization`)}
|
||||||
|
|||||||
Reference in New Issue
Block a user