diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx index ccd71b2204..5147773c86 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx @@ -9,7 +9,11 @@ import useRequest, { } from '../../../util/useRequest'; import { getQSConfig, parseQueryString } from '../../../util/qs'; import { InventoriesAPI, InventorySourcesAPI } from '../../../api'; -import PaginatedDataList, { +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; +import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; @@ -148,7 +152,7 @@ function InventorySourceList() { ); return ( <> - )} - renderItem={inventorySource => { - let label; - sourceChoices.forEach(([scMatch, scLabel]) => { - if (inventorySource.source === scMatch) { - label = scLabel; - } - }); + headerRow={ + + {t`Name`} + {t`Status`} + {t`Type`} + {t`Actions`} + + } + renderRow={(inventorySource, index) => { + const label = sourceChoices.find( + ([scMatch]) => inventorySource.source === scMatch + ); return ( handleSelect(inventorySource)} - label={label} + label={label[1]} detailUrl={`${listUrl}${inventorySource.id}`} isSelected={selected.some(row => row.id === inventorySource.id)} + rowIndex={index} /> ); }} diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.test.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.test.jsx index be37e47f7e..138fac4207 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.test.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.test.jsx @@ -7,10 +7,7 @@ import { InventorySourcesAPI, WorkflowJobTemplateNodesAPI, } from '../../../api'; -import { - mountWithContexts, - waitForElement, -} from '../../../../testUtils/enzymeHelpers'; +import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; import InventorySourceList from './InventorySourceList'; @@ -108,18 +105,15 @@ describe('', () => { } ); }); + wrapper.update(); }); + afterEach(() => { jest.clearAllMocks(); global.console.debug = debug; }); - test('should mount properly', async () => { - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); - }); - test('api calls should be made on mount', async () => { - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); expect(InventoriesAPI.readSources).toHaveBeenCalledWith('1', { not__source: '', order_by: 'name', @@ -136,23 +130,16 @@ describe('', () => { }); test('source data should render properly', async () => { - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); + expect(wrapper.find('InventorySourceListItem')).toHaveLength(2); expect( wrapper - .find("DataListItem[aria-labelledby='check-action-1']") - .find('PFDataListCell[aria-label="name"]') - .text() - ).toBe('Source Foo'); - expect( - wrapper - .find("DataListItem[aria-labelledby='check-action-1']") - .find('PFDataListCell[aria-label="type"]') - .text() - ).toBe('EC2'); + .find('InventorySourceListItem') + .first() + .prop('source') + ).toEqual(sources.data.results[0]); }); test('add button is not disabled and delete button is disabled', async () => { - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); const addButton = wrapper.find('ToolbarAddButton').find('Link'); const deleteButton = wrapper.find('ToolbarDeleteButton').find('Button'); expect(addButton.prop('aria-disabled')).toBe(false); @@ -162,14 +149,23 @@ describe('', () => { test('delete button becomes enabled and properly calls api to delete', async () => { const deleteButton = wrapper.find('ToolbarDeleteButton').find('Button'); - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); expect(deleteButton.prop('isDisabled')).toBe(true); await act(async () => - wrapper.find('DataListCheck#select-source-1').prop('onChange')({ id: 1 }) + wrapper + .find('.pf-c-table__check') + .first() + .find('input') + .prop('onChange')({ id: 1 }) ); wrapper.update(); - expect(wrapper.find('input#select-source-1').prop('checked')).toBe(true); + expect( + wrapper + .find('.pf-c-table__check') + .first() + .find('input') + .prop('checked') + ).toBe(true); await act(async () => wrapper.find('Button[aria-label="Delete"]').prop('onClick')() @@ -199,10 +195,12 @@ describe('', () => { }) ); - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); - await act(async () => - wrapper.find('DataListCheck#select-source-1').prop('onChange')({ id: 1 }) + wrapper + .find('.pf-c-table__check') + .first() + .find('input') + .prop('onChange')({ id: 1 }) ); wrapper.update(); @@ -249,8 +247,7 @@ describe('', () => { await act(async () => { wrapper = mountWithContexts(); }); - - await waitForElement(wrapper, 'ContentError', el => el.length > 0); + wrapper.update(); expect(wrapper.find('ContentError').length).toBe(1); }); @@ -272,8 +269,7 @@ describe('', () => { await act(async () => { wrapper = mountWithContexts(); }); - - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); + wrapper.update(); expect(wrapper.find('ContentError').length).toBe(1); }); @@ -291,7 +287,6 @@ describe('', () => { }, }) ); - await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0); await act(async () => wrapper.find('Button[aria-label="Sync all"]').prop('onClick')() ); @@ -301,11 +296,6 @@ describe('', () => { }); test('should render sync all button and make api call to start sync for all', async () => { - await waitForElement( - wrapper, - 'InventorySourceListItem', - el => el.length > 0 - ); const syncAllButton = wrapper.find('Button[aria-label="Sync all"]'); expect(syncAllButton.length).toBe(1); await act(async () => syncAllButton.prop('onClick')()); @@ -359,11 +349,7 @@ describe(' RBAC testing', () => { } ); }); - await waitForElement( - newWrapper, - 'InventorySourceList', - el => el.length > 0 - ); + newWrapper.update(); expect(newWrapper.find('ToolbarAddButton').length).toBe(0); jest.clearAllMocks(); }); @@ -398,11 +384,7 @@ describe(' RBAC testing', () => { } ); }); - await waitForElement( - newWrapper, - 'InventorySourceList', - el => el.length > 0 - ); + newWrapper.update(); expect(newWrapper.find('Button[aria-label="Sync All"]').length).toBe(0); jest.clearAllMocks(); }); diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.jsx index 2ac1f21fc3..3ca06649a7 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.jsx @@ -1,23 +1,15 @@ import React from 'react'; - import { Link } from 'react-router-dom'; import { t } from '@lingui/macro'; -import { - Button, - DataListItem, - DataListItemRow, - DataListCheck, - DataListItemCells, - DataListCell, - DataListAction, - Tooltip, -} from '@patternfly/react-core'; +import { Button, Tooltip } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; import { ExclamationTriangleIcon as PFExclamationTriangleIcon, PencilAltIcon, } from '@patternfly/react-icons'; import styled from 'styled-components'; +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; import StatusIcon from '../../../components/StatusIcon'; import InventorySourceSyncButton from '../shared/InventorySourceSyncButton'; @@ -30,9 +22,9 @@ function InventorySourceListItem({ source, isSelected, onSelect, - detailUrl, label, + rowIndex, }) { const generateLastJobTooltip = job => { return ( @@ -58,80 +50,67 @@ function InventorySourceListItem({ return ( <> - - - - - {source.summary_fields.last_job && ( - - - - - - )} - , - - - - {source.name} - - - {missingExecutionEnvironment && ( - - - - - - )} - , - - {label} - , - ]} - /> - - {source.summary_fields.user_capabilities.start && ( - - )} - {source.summary_fields.user_capabilities.edit && ( - + + + + {source.name} + + {missingExecutionEnvironment && ( + + - - - )} - - - + + + + )} + + + {source.summary_fields.last_job && ( + + + + + + )} + + {label} + + + + + + + + + + + > ); } diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.test.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.test.jsx index 6c0a9146a9..398f6febfb 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.test.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceListItem.test.jsx @@ -26,15 +26,20 @@ describe('', () => { wrapper.unmount(); jest.clearAllMocks(); }); + test('should mount properly', () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect(wrapper.find('InventorySourceListItem').length).toBe(1); }); @@ -42,32 +47,35 @@ describe('', () => { test('all buttons and text fields should render properly', () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect(wrapper.find('StatusIcon').length).toBe(1); expect( wrapper .find('Link') - .at(0) + .at(1) .prop('to') ).toBe('/jobs/inventory/664'); - expect(wrapper.find('DataListCheck').length).toBe(1); - expect(); + expect(wrapper.find('.pf-c-table__check').length).toBe(1); expect( wrapper - .find('DataListCell') + .find('Td') .at(1) .text() ).toBe('Foo'); expect( wrapper - .find('DataListCell') - .at(2) + .find('Td') + .at(3) .text() ).toBe('Source Bar'); expect(wrapper.find('InventorySourceSyncButton').length).toBe(1); @@ -77,32 +85,46 @@ describe('', () => { test('item should be checked', () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); - expect(wrapper.find('DataListCheck').length).toBe(1); - expect(wrapper.find('DataListCheck').prop('checked')).toBe(true); + wrapper.update(); + expect(wrapper.find('.pf-c-table__check').length).toBe(1); + expect( + wrapper + .find('Td') + .first() + .prop('select').isSelected + ).toEqual(true); }); test('should not render status icon', () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect(wrapper.find('StatusIcon').length).toBe(0); }); @@ -110,14 +132,20 @@ describe('', () => { test('should not render sync buttons', async () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect(wrapper.find('InventorySourceSyncButton').length).toBe(0); expect(wrapper.find('Button[aria-label="Edit Source"]').length).toBe(1); @@ -126,15 +154,21 @@ describe('', () => { test('should not render edit buttons', async () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect(wrapper.find('Button[aria-label="Edit Source"]').length).toBe(0); expect(wrapper.find('InventorySourceSyncButton').length).toBe(1); @@ -143,16 +177,20 @@ describe('', () => { test('should render warning about missing execution environment', () => { const onSelect = jest.fn(); wrapper = mountWithContexts( - + + + + + ); expect( wrapper.find('.missing-execution-environment').prop('content')