flush out tests for PaginatedTable and related components

This commit is contained in:
Keith Grant 2020-12-10 16:10:10 -08:00
parent 204af9ec91
commit a9d7fea86f
11 changed files with 218 additions and 54 deletions

View File

@ -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('');
});
});

View File

@ -15,6 +15,7 @@ const ActionsGrid = styled.div`
`;
}}
`;
ActionsGrid.displayName = 'ActionsGrid';
export default function ActionsTd({ children, ...props }) {
const numActions = children.length || 1;

View File

@ -12,7 +12,7 @@ const Th = styled(PFTh)`
--pf-c-table--cell--Overflow: initial;
`;
export default function HeaderRow({ qsConfig, defaultSortKey, children }) {
export default function HeaderRow({ qsConfig, children }) {
const location = useLocation();
const history = useHistory();
@ -33,10 +33,11 @@ export default function HeaderRow({ qsConfig, defaultSortKey, children }) {
const sortKey = params.order_by?.replace('-', '');
const sortBy = {
index: sortKey || defaultSortKey,
index: sortKey || qsConfig.defaultParams?.order_by,
direction: params.order_by?.startsWith('-') ? 'desc' : 'asc',
};
// empty first Th aligns with checkboxes in table rows
return (
<Thead>
<Tr>

View 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);
});
});

View File

@ -17,7 +17,6 @@ import {
parseQueryString,
replaceParams,
} from '../../util/qs';
import PaginatedTableRow from './PaginatedTableRow';
import { QSConfig, SearchColumns } from '../../types';
function PaginatedTable({
@ -160,7 +159,7 @@ PaginatedTable.propTypes = {
itemCount: PropTypes.number.isRequired,
pluralizedItemName: PropTypes.string,
qsConfig: QSConfig.isRequired,
renderRow: PropTypes.func,
renderRow: PropTypes.func.isRequired,
toolbarSearchColumns: SearchColumns,
toolbarSearchableKeys: PropTypes.arrayOf(PropTypes.string),
toolbarRelatedSearchableKeys: PropTypes.arrayOf(PropTypes.string),
@ -178,7 +177,6 @@ PaginatedTable.defaultProps = {
toolbarRelatedSearchableKeys: [],
pluralizedItemName: 'Items',
showPageSizeOptions: true,
renderRow: item => <PaginatedTableRow key={item.id} item={item} />,
renderToolbar: props => <DataListToolbar {...props} />,
};

View File

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

View File

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

View File

@ -1,5 +1,4 @@
export { default } from './PaginatedTable';
export { default as PaginatedTableRow } from './PaginatedTableRow';
export { default as ActionsTd } from './ActionsTd';
export { default as HeaderRow, HeaderCell } from './HeaderRow';
export { default as ActionItem } from './ActionItem';

View File

@ -190,7 +190,7 @@ function InventoryList({ i18n }) {
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell>{i18n._(t`Status`)}</HeaderCell>
<HeaderCell>{i18n._(t`Type`)}</HeaderCell>

View File

@ -150,7 +150,7 @@ function OrganizationsList({ i18n }) {
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow defaultSortKey="name" qsConfig={QS_CONFIG}>
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell>{i18n._(t`Members`)}</HeaderCell>
<HeaderCell>{i18n._(t`Teams`)}</HeaderCell>

View File

@ -28,15 +28,20 @@ function OrganizationListItem({
onSelect,
disable: false,
}}
dataLabel={i18n._(t`Selected`)}
/>
<Td id={labelId}>
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
<Link to={`${detailUrl}`}>
<b>{organization.name}</b>
</Link>
</Td>
<Td>{organization.summary_fields.related_field_counts.users}</Td>
<Td>{organization.summary_fields.related_field_counts.teams}</Td>
<ActionsTd>
<Td dataLabel={i18n._(t`Members`)}>
{organization.summary_fields.related_field_counts.users}
</Td>
<Td dataLabel={i18n._(t`Teams`)}>
{organization.summary_fields.related_field_counts.teams}
</Td>
<ActionsTd dataLabel={i18n._(t`Actions`)}>
<ActionItem
visible={organization.summary_fields.user_capabilities.edit}
tooltip={i18n._(t`Edit Organization`)}