adds tests

This commit is contained in:
Alex Corey 2021-02-19 19:04:12 -05:00
parent 7e7bb5261b
commit c2e224bb86
26 changed files with 528 additions and 250 deletions

View File

@ -70,56 +70,55 @@ function DeleteButton({
>
{children || i18n._(t`Delete`)}
</Button>
{!deleteMessageError && (
<AlertModal
isOpen={isOpen}
title={modalTitle}
variant="danger"
onClose={() => toggleModal(false)}
actions={[
<Button
ouiaId="delete-modal-confirm"
key="delete"
variant="danger"
aria-label={i18n._(t`Confirm Delete`)}
isDisabled={isDisabled}
onClick={onConfirm}
>
{i18n._(t`Delete`)}
</Button>,
<Button
ouiaId="delete-modal-cancel"
key="cancel"
variant="link"
aria-label={i18n._(t`Cancel`)}
onClick={() => toggleModal(false)}
>
{i18n._(t`Cancel`)}
</Button>,
]}
>
{i18n._(t`Are you sure you want to delete:`)}
<br />
<strong>{name}</strong>
{Object.values(deleteDetails).length > 0 && (
<WarningMessage
variant="warning"
isInline
title={
<div>
<div aria-label={deleteMessage}>{deleteMessage}</div>
<br />
{Object.entries(deleteDetails).map(([key, value]) => (
<DetailsWrapper aria-label={`${key}: ${value}`} key={key}>
<span>{key}</span> <Badge>{value}</Badge>
</DetailsWrapper>
))}
</div>
}
/>
)}
</AlertModal>
)}
<AlertModal
isOpen={isOpen}
title={modalTitle}
variant="danger"
onClose={() => toggleModal(false)}
actions={[
<Button
ouiaId="delete-modal-confirm"
key="delete"
variant="danger"
aria-label={i18n._(t`Confirm Delete`)}
isDisabled={isDisabled}
onClick={onConfirm}
>
{i18n._(t`Delete`)}
</Button>,
<Button
ouiaId="delete-modal-cancel"
key="cancel"
variant="link"
aria-label={i18n._(t`Cancel`)}
onClick={() => toggleModal(false)}
>
{i18n._(t`Cancel`)}
</Button>,
]}
>
{i18n._(t`Are you sure you want to delete:`)}
<br />
<strong>{name}</strong>
{Object.values(deleteDetails).length > 0 && (
<WarningMessage
variant="warning"
isInline
title={
<div>
<div aria-label={deleteMessage}>{deleteMessage}</div>
<br />
{Object.entries(deleteDetails).map(([key, value]) => (
<DetailsWrapper aria-label={`${key}: ${value}`} key={key}>
<span>{key}</span> <Badge>{value}</Badge>
</DetailsWrapper>
))}
</div>
}
/>
)}
</AlertModal>
</>
);
}

View File

@ -38,7 +38,6 @@ describe('<DeleteButton />', () => {
);
});
// expect(wrapper.find('Modal')).toHaveLength(0);
await act(async () => {
wrapper.find('button').prop('onClick')();
});

View File

@ -112,7 +112,11 @@ function ToolbarDeleteButton({
};
const toggleModal = async isOpen => {
if (itemsToDelete.length === 1 && deleteDetailsRequests?.length > 0) {
if (
isOpen &&
itemsToDelete.length === 1 &&
deleteDetailsRequests?.length > 0
) {
const { results, error } = await getRelatedResourceDeleteCounts(
deleteDetailsRequests
);
@ -195,7 +199,7 @@ function ToolbarDeleteButton({
variant="secondary"
aria-label={i18n._(t`Delete`)}
onClick={() => toggleModal(true)}
isAriaDisabled={isDisabled}
isDisabled={isDisabled}
>
{i18n._(t`Delete`)}
</Button>
@ -203,7 +207,7 @@ function ToolbarDeleteButton({
</Tooltip>
)}
{isModalOpen && !deleteMessageError && (
{isModalOpen && (
<AlertModal
variant="danger"
title={modalTitle}

View File

@ -30,7 +30,7 @@ describe('<ToolbarDeleteButton />', () => {
beforeEach(() => {
deleteDetailsRequests = [
{
label: 'job',
label: 'Workflow Job Template Node',
request: CredentialsAPI.read.mockResolvedValue({ data: { count: 1 } }),
},
];
@ -62,10 +62,55 @@ describe('<ToolbarDeleteButton />', () => {
wrapper.find('button').prop('onClick')();
});
await waitForElement(wrapper, 'Modal', el => el.length > 0);
expect(CredentialsAPI.read).toBeCalled();
expect(wrapper.find('Modal')).toHaveLength(1);
expect(
wrapper.find('span[aria-label="Workflow Job Template Node: 1"]')
).toHaveLength(1);
expect(wrapper.find('div[aria-label="Delete this?"]')).toHaveLength(1);
});
test('should open delete error modal', async () => {
const request = [
{
label: 'Workflow Job Template Node',
request: CredentialsAPI.read.mockRejectedValue(
new Error({
response: {
config: {
method: 'get',
url: '/api/v2/credentals',
},
data: 'An error occurred',
status: 403,
},
})
),
},
];
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<ToolbarDeleteButton
onDelete={() => {}}
itemsToDelete={[itemA]}
deleteDetailsRequests={request}
deleteMessage="Delete this?"
warningMessage="Are you sure to want to delete this"
/>
);
});
expect(wrapper.find('Modal')).toHaveLength(0);
await act(async () => wrapper.find('button').simulate('click'));
await waitForElement(wrapper, 'Modal', el => el.length > 0);
expect(CredentialsAPI.read).toBeCalled();
wrapper.update();
expect(wrapper.find('AlertModal[title="Error!"]')).toHaveLength(1);
});
test('should invoke onDelete prop', () => {
const onDelete = jest.fn();
const wrapper = mountWithContexts(

View File

@ -74,7 +74,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
>
<Button
aria-label="Delete"
isAriaDisabled={true}
isDisabled={true}
onClick={[Function]}
variant="secondary"
>
@ -92,20 +92,19 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
>
<Button
aria-label="Delete"
isAriaDisabled={true}
isDisabled={true}
onClick={[Function]}
variant="secondary"
>
<button
aria-disabled={true}
aria-label="Delete"
className="pf-c-button pf-m-secondary pf-m-aria-disabled"
className="pf-c-button pf-m-secondary pf-m-disabled"
data-ouia-component-id="OUIA-Generated-Button-secondary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
disabled={true}
onClick={[Function]}
onKeyPress={[Function]}
role={null}
tabIndex={null}
type="button"

View File

@ -119,7 +119,7 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
title="Remove Team Access"
titleIconVariant={null}
titleLabel=""
variant="small"
variant="medium"
>
<Portal
containerInfo={
@ -135,8 +135,8 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-label="Alert modal"
aria-labelledby="pf-modal-part-0 alert-modal-header-label pf-modal-part-1"
aria-modal="true"
class="pf-c-modal-box pf-m-sm"
data-ouia-component-id="OUIA-Generated-Modal-small-1"
class="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe="true"
id="pf-modal-part-0"
@ -277,13 +277,13 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
isOpen={true}
labelId="pf-modal-part-1"
onClose={[Function]}
ouiaId="OUIA-Generated-Modal-small-1"
ouiaId="OUIA-Generated-Modal-medium-1"
ouiaSafe={true}
showClose={true}
title="Remove Team Access"
titleIconVariant={null}
titleLabel=""
variant="small"
variant="medium"
>
<Backdrop>
<div
@ -308,20 +308,20 @@ exports[`<DeleteRoleConfirmationModal /> should render initially 1`] = `
aria-label="Alert modal"
aria-labelledby="pf-modal-part-0 alert-modal-header-label pf-modal-part-1"
className=""
data-ouia-component-id="OUIA-Generated-Modal-small-1"
data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true}
id="pf-modal-part-0"
style={Object {}}
variant="small"
variant="medium"
>
<div
aria-describedby="pf-modal-part-2"
aria-label="Alert modal"
aria-labelledby="pf-modal-part-0 alert-modal-header-label pf-modal-part-1"
aria-modal="true"
className="pf-c-modal-box pf-m-sm"
data-ouia-component-id="OUIA-Generated-Modal-small-1"
className="pf-c-modal-box pf-m-md"
data-ouia-component-id="OUIA-Generated-Modal-medium-1"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true}
id="pf-modal-part-0"

View File

@ -18,7 +18,7 @@ import { getQSConfig, parseQueryString } from '../../util/qs';
import useWsTemplates from '../../util/useWsTemplates';
import AddDropDownButton from '../AddDropDownButton';
import TemplateListItem from './TemplateListItem';
import { deleteRequests } from '../../util/getDeleteDetails';
import { relatedResourceDeleteRequests } from '../../util/getRelatedResourceDeleteDetails';
function TemplateList({ defaultParams, i18n }) {
// The type value in const qsConfig below does not have a space between job_template and
@ -169,7 +169,10 @@ function TemplateList({ defaultParams, i18n }) {
<AddDropDownButton key="add" dropdownItems={addDropDownButton} />
);
const deleteDetailsRequests = deleteRequests.template(selected[0], i18n);
const deleteDetailsRequests = relatedResourceDeleteRequests.template(
selected[0],
i18n
);
return (
<Fragment>

View File

@ -65,6 +65,12 @@ describe('<CredentialDetail />', () => {
expect(wrapper.find('CredentialDetail').length).toBe(1);
});
test('should have proper number of delete detail requests', () => {
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(4);
});
test('should render details', () => {
expectDetailToMatch(wrapper, 'Name', mockCredential.name);
expectDetailToMatch(wrapper, 'Description', mockCredential.description);

View File

@ -40,6 +40,12 @@ describe('<CredentialList />', () => {
expect(wrapper.find('CredentialList').length).toBe(1);
});
test('should have proper number of delete detail requests', () => {
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(4);
});
test('should fetch credentials from api and render the in the list', () => {
expect(CredentialsAPI.read).toHaveBeenCalled();
expect(wrapper.find('CredentialListItem').length).toBe(5);

View File

@ -92,6 +92,12 @@ describe('<CredentialTypeDetails/>', () => {
);
});
test('should have proper number of delete detail requests', () => {
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('expected api call is made for delete', async () => {
const history = createMemoryHistory({
initialEntries: ['/credential_types/42/details'],

View File

@ -6,10 +6,11 @@ import {
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import { CredentialTypesAPI } from '../../../api';
import { CredentialTypesAPI, CredentialsAPI } from '../../../api';
import CredentialTypeList from './CredentialTypeList';
jest.mock('../../../api/models/CredentialTypes');
jest.mock('../../../api/models/Credentials');
const credentialTypes = {
data: {
@ -49,6 +50,12 @@ describe('<CredentialTypeList', () => {
await waitForElement(wrapper, 'CredentialTypeList', el => el.length > 0);
});
test('should have proper number of delete detail requests', () => {
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('should have data fetched and render 2 rows', async () => {
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
@ -65,6 +72,7 @@ describe('<CredentialTypeList', () => {
test('should delete item successfully', async () => {
CredentialTypesAPI.read.mockResolvedValue(credentialTypes);
CredentialTypesAPI.readOptions.mockResolvedValue(options);
CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
await act(async () => {
wrapper = mountWithContexts(<CredentialTypeList />);

View File

@ -105,6 +105,18 @@ describe('<InventoryDetail />', () => {
expect(dates.at(1).prop('date')).toEqual(mockInventory.modified);
});
test('should have proper number of delete detail requests', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<InventoryDetail inventory={mockInventory} />
);
});
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(2);
});
test('should load instance groups', async () => {
InventoriesAPI.readInstanceGroups.mockResolvedValue({
data: {

View File

@ -1,11 +1,17 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { InventoriesAPI } from '../../../api';
import {
InventoriesAPI,
JobTemplatesAPI,
WorkflowJobTemplatesAPI,
} from '../../../api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import InventoryList from './InventoryList';
jest.mock('../../../api');
jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/JobTemplates');
jest.mock('../../../api/models/WorkflowJobTemplates');
const mockInventories = [
{
@ -136,6 +142,8 @@ describe('<InventoryList />', () => {
},
},
});
JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {};
});
@ -155,6 +163,16 @@ describe('<InventoryList />', () => {
expect(wrapper.find('InventoryListItem')).toHaveLength(3);
});
test('should have proper number of delete detail requests', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(<InventoryList />);
});
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(2);
});
test('should select inventory when checked', async () => {
let wrapper;
await act(async () => {

View File

@ -7,9 +7,20 @@ import {
} from '../../../../testUtils/enzymeHelpers';
import InventorySourceDetail from './InventorySourceDetail';
import mockInvSource from '../shared/data.inventory_source.json';
import { InventorySourcesAPI } from '../../../api';
import {
InventorySourcesAPI,
InventoriesAPI,
WorkflowJobTemplateNodesAPI,
} from '../../../api';
jest.mock('../../../api/models/InventorySources');
jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/WorkflowJobTemplateNodes');
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 1 }],
});
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
@ -101,6 +112,17 @@ describe('InventorySourceDetail', () => {
expect(wrapper.find('InventorySourceSyncButton')).toHaveLength(1);
});
test('should have proper number of delete detail requests', async () => {
await act(async () => {
wrapper = mountWithContexts(
<InventorySourceDetail inventorySource={mockInvSource} />
);
});
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('should hide expected action buttons for users without permissions', async () => {
const userCapabilities = {
edit: false,

View File

@ -2,7 +2,11 @@ import React from 'react';
import { Route } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils';
import { InventoriesAPI, InventorySourcesAPI } from '../../../api';
import {
InventoriesAPI,
InventorySourcesAPI,
WorkflowJobTemplateNodesAPI,
} from '../../../api';
import {
mountWithContexts,
waitForElement,
@ -13,6 +17,7 @@ import InventorySourceList from './InventorySourceList';
jest.mock('../../../api/models/InventorySources');
jest.mock('../../../api/models/Inventories');
jest.mock('../../../api/models/InventoryUpdates');
jest.mock('../../../api/models/WorkflowJobTemplateNodes');
const sources = {
data: {
@ -60,6 +65,10 @@ describe('<InventorySourceList />', () => {
beforeEach(async () => {
debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {};
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 1 }],
});
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventoriesAPI.readSources.mockResolvedValue(sources);
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
@ -119,6 +128,12 @@ describe('<InventorySourceList />', () => {
expect(InventorySourcesAPI.readOptions).toHaveBeenCalled();
});
test('should have proper number of delete detail requests', async () => {
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('source data should render properly', async () => {
await waitForElement(wrapper, 'InventorySourceList', el => el.length > 0);
expect(

View File

@ -116,7 +116,7 @@ describe('<JobDetail />', () => {
wrapper.update();
const modal = wrapper.find('Modal');
expect(modal.length).toBe(1);
modal.find('button[aria-label="Delete"]').simulate('click');
modal.find('button[aria-label="Confirm Delete"]').simulate('click');
expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
});
@ -138,7 +138,7 @@ describe('<JobDetail />', () => {
const modal = wrapper.find('Modal');
expect(modal.length).toBe(1);
await act(async () => {
modal.find('button[aria-label="Delete"]').simulate('click');
modal.find('button[aria-label="Confirm Delete"]').simulate('click');
});
wrapper.update();

View File

@ -188,9 +188,19 @@ describe('<JobOutput />', () => {
wrapper = mountWithContexts(<JobOutput job={mockJob} />);
});
await waitForElement(wrapper, 'JobEvent', el => el.length > 0);
await act(async () => {
wrapper.find('DeleteButton').invoke('onConfirm')();
});
await act(async () =>
wrapper.find('button[aria-label="Delete"]').simulate('click')
);
await waitForElement(
wrapper,
'Modal',
el => el.props().isOpen === true && el.props().title === 'Delete Job'
);
await act(async () =>
wrapper
.find('Modal button[aria-label="Confirm Delete"]')
.simulate('click')
);
expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
});

View File

@ -1,7 +1,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { OrganizationsAPI } from '../../../api';
import { OrganizationsAPI, CredentialsAPI } from '../../../api';
import {
mountWithContexts,
waitForElement,
@ -44,6 +44,8 @@ describe('<OrganizationDetail />', () => {
};
beforeEach(() => {
CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
OrganizationsAPI.readInstanceGroups.mockResolvedValue(mockInstanceGroups);
});
@ -64,6 +66,20 @@ describe('<OrganizationDetail />', () => {
expect(OrganizationsAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
});
test('should have proper number of delete detail requests', async () => {
let component;
await act(async () => {
component = mountWithContexts(
<OrganizationDetail organization={mockOrganization} />
);
});
await waitForElement(component, 'ContentLoading', el => el.length === 0);
expect(
component.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('should render the expected instance group', async () => {
let component;
await act(async () => {

View File

@ -1,7 +1,7 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { OrganizationsAPI } from '../../../api';
import { OrganizationsAPI, CredentialsAPI } from '../../../api';
import {
mountWithContexts,
waitForElement,
@ -70,6 +70,7 @@ const mockOrganizations = {
describe('<OrganizationsList />', () => {
let wrapper;
beforeEach(() => {
CredentialsAPI.read.mockResolvedValue({ data: { count: 0 } });
OrganizationsAPI.read.mockResolvedValue(mockOrganizations);
OrganizationsAPI.readOptions.mockResolvedValue({
data: {
@ -90,6 +91,20 @@ describe('<OrganizationsList />', () => {
});
});
test('should have proper number of delete detail requests', async () => {
await act(async () => {
wrapper = mountWithContexts(<OrganizationsList />);
});
await waitForElement(
wrapper,
'OrganizationsList',
el => el.find('ContentLoading').length === 0
);
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('Items are rendered after loading', async () => {
await act(async () => {
wrapper = mountWithContexts(<OrganizationsList />);

View File

@ -5,7 +5,12 @@ import {
mountWithContexts,
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import { ProjectsAPI } from '../../../api';
import {
ProjectsAPI,
JobTemplatesAPI,
WorkflowJobTemplatesAPI,
InventorySourcesAPI,
} from '../../../api';
import ProjectDetail from './ProjectDetail';
jest.mock('../../../api');
@ -147,6 +152,27 @@ describe('<ProjectDetail />', () => {
expect(wrapper.find('Detail[label="Options"]').length).toBe(0);
});
test('should have proper number of delete detail requests', () => {
JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.read.mockResolvedValue({ data: { count: 0 } });
const mockOptions = {
scm_type: '',
scm_clean: false,
scm_delete_on_update: false,
scm_update_on_launch: false,
allow_override: false,
created: '',
modified: '',
};
const wrapper = mountWithContexts(
<ProjectDetail project={{ ...mockProject, ...mockOptions }} />
);
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(3);
});
test('should render with missing summary fields', async () => {
const wrapper = mountWithContexts(
<ProjectDetail project={{ ...mockProject, summary_fields: {} }} />

View File

@ -1,7 +1,15 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { ProjectsAPI } from '../../../api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import {
ProjectsAPI,
JobTemplatesAPI,
WorkflowJobTemplatesAPI,
InventorySourcesAPI,
} from '../../../api';
import {
mountWithContexts,
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import ProjectList from './ProjectList';
jest.mock('../../../api');
@ -83,6 +91,9 @@ const mockProjects = [
describe('<ProjectList />', () => {
beforeEach(() => {
JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
WorkflowJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.read.mockResolvedValue({ data: { count: 0 } });
ProjectsAPI.read.mockResolvedValue({
data: {
count: mockProjects.length,
@ -138,6 +149,17 @@ describe('<ProjectList />', () => {
).toEqual(true);
});
test('should have proper number of delete detail requests', async () => {
let wrapper;
await act(async () => {
wrapper = mountWithContexts(<ProjectList />);
});
wrapper.update();
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(3);
});
test('should select all', async () => {
let wrapper;
await act(async () => {
@ -177,10 +199,11 @@ describe('<ProjectList />', () => {
.at(2)
.invoke('onSelect')();
});
wrapper.update();
expect(wrapper.find('ToolbarDeleteButton button').prop('disabled')).toEqual(
true
waitForElement(
wrapper,
'ToolbarDeleteButton button',
el => el.prop('disabled') === true
);
});

View File

@ -5,7 +5,7 @@ import {
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import JobTemplateDetail from './JobTemplateDetail';
import { JobTemplatesAPI } from '../../../api';
import { JobTemplatesAPI, WorkflowJobTemplateNodesAPI } from '../../../api';
import mockTemplate from '../shared/data.job_template.json';
jest.mock('../../../api');
@ -25,6 +25,7 @@ describe('<JobTemplateDetail />', () => {
beforeEach(async () => {
JobTemplatesAPI.readInstanceGroups.mockResolvedValue(mockInstanceGroups);
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
await act(async () => {
wrapper = mountWithContexts(
<JobTemplateDetail template={mockTemplate} />
@ -56,6 +57,23 @@ describe('<JobTemplateDetail />', () => {
);
});
test('should have proper number of delete detail requests', async () => {
await act(async () => {
wrapper = mountWithContexts(
<JobTemplateDetail
template={{
...mockTemplate,
become_enabled: true,
summary_fields: { user_capabilities: { delete: true } },
}}
/>
);
});
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('should request instance groups from api', async () => {
expect(JobTemplatesAPI.readInstanceGroups).toHaveBeenCalledTimes(1);
});

View File

@ -5,6 +5,9 @@ import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail';
import { WorkflowJobTemplateNodesAPI } from '../../../api';
jest.mock('../../../api');
describe('<WorkflowJobTemplateDetail/>', () => {
let wrapper;
@ -50,6 +53,7 @@ describe('<WorkflowJobTemplateDetail/>', () => {
};
beforeEach(async () => {
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
history = createMemoryHistory({
initialEntries: ['/templates/workflow_job_template/1/details'],
});
@ -86,6 +90,7 @@ describe('<WorkflowJobTemplateDetail/>', () => {
afterEach(() => {
wrapper.unmount();
jest.clearAllMocks();
});
test('renders successfully', () => {
@ -163,6 +168,12 @@ describe('<WorkflowJobTemplateDetail/>', () => {
).toBe('Demo EE');
});
test('should have proper number of delete detail requests', async () => {
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
});
test('link out resource have the correct url', () => {
const inventory = wrapper.find('Detail[label="Inventory"]').find('Link');
const organization = wrapper

View File

@ -1,128 +0,0 @@
import { t } from '@lingui/macro';
import {
CredentialsAPI,
InventoriesAPI,
InventorySourcesAPI,
JobTemplatesAPI,
ProjectsAPI,
WorkflowJobTemplateNodesAPI,
WorkflowJobTemplatesAPI,
} from '../api';
export default async function getDeleteDetails(requests) {
const results = {};
let error = null;
let hasCount = false;
try {
await Promise.all(
requests.map(async ({ request, label }) => {
const {
data: { count },
} = await request();
if (count > 0) {
results[label] = count;
hasCount = true;
}
})
);
} catch (err) {
error = err;
}
return {
results: hasCount && results,
error,
};
}
export const deleteRequests = {
credential: (selected, i18n) => [
{
request: async () =>
JobTemplatesAPI.read({
credentials: selected.id,
}),
label: i18n._(t`Job Templates`),
},
{
request: () => ProjectsAPI.read({ credentials: selected.id }),
label: i18n._(t`Projects`),
},
{
request: () =>
InventoriesAPI.read({
insights_credential: selected.id,
}),
label: i18n._(t`Inventories`),
},
{
request: () =>
InventorySourcesAPI.read({
credentials__id: selected.id,
}),
label: i18n._(t`Inventory Sources`),
},
],
template: (selected, i18n) => [
{
request: async () =>
WorkflowJobTemplateNodesAPI.read({
unified_job_template: selected.id,
}),
label: [i18n._(t`Workflow Job Template Node`)],
},
],
credentialType: (selected, i18n) => [
{
request: async () =>
CredentialsAPI.read({
credential_type__id: selected.id,
}),
label: i18n._(t`Credentials`),
},
],
inventory: (selected, i18n) => [
{
request: async () =>
JobTemplatesAPI.read({
inventory: selected.id,
}),
label: i18n._(t`Job Templates`),
},
{
request: () => WorkflowJobTemplatesAPI.read({ inventory: selected.id }),
label: i18n._(t`Workflow Job Template`),
},
],
inventorySource: (inventoryId, i18n) => [
{
request: async () => {
try {
const { data } = await InventoriesAPI.updateSources(inventoryId);
return WorkflowJobTemplateNodesAPI.read({
unified_job_template: data[0].inventory_source,
});
} catch (err) {
throw new Error(err);
}
},
label: i18n._(t`Workflow Job Template Node`),
},
],
organization: (selected, i18n) => [
{
request: async () =>
CredentialsAPI.read({
organization: selected.id,
}),
label: i18n._(t`Credential`),
},
],
};

View File

@ -0,0 +1,144 @@
import {
getRelatedResourceDeleteCounts,
relatedResourceDeleteRequests,
} from './getRelatedResourceDeleteDetails';
import {
InventoriesAPI,
InventorySourcesAPI,
JobTemplatesAPI,
ProjectsAPI,
WorkflowJobTemplatesAPI,
WorkflowJobTemplateNodesAPI,
CredentialsAPI,
} from '../api';
jest.mock('../api/models/Credentials');
jest.mock('../api/models/Inventories');
jest.mock('../api/models/InventorySources');
jest.mock('../api/models/JobTemplates');
jest.mock('../api/models/Projects');
jest.mock('../api/models/WorkflowJobTemplates');
jest.mock('../api/models/WorkflowJobTemplateNodes');
const i18n = {
_: key => {
if (key.values) {
Object.entries(key.values).forEach(([k, v]) => {
key.id = key.id.replace(new RegExp(`\\{${k}\\}`), v);
});
}
return key.id;
},
};
describe('delete details', () => {
afterEach(() => {
jest.clearAllMocks();
});
test('should call api for credentials list', () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.credential({ id: 1 }, i18n)
);
expect(InventoriesAPI.read).toBeCalledWith({
insights_credential: 1,
});
expect(InventorySourcesAPI.read).toBeCalledWith({
credentials__id: 1,
});
expect(JobTemplatesAPI.read).toBeCalledWith({ credentials: 1 });
expect(ProjectsAPI.read).toBeCalledWith({ credentials: 1 });
});
test('should call api for projects list', () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.project({ id: 1 }, i18n)
);
expect(WorkflowJobTemplatesAPI.read).toBeCalledWith({
credentials: 1,
});
expect(InventorySourcesAPI.read).toBeCalledWith({
credentials__id: 1,
});
expect(JobTemplatesAPI.read).toBeCalledWith({ credentials: 1 });
});
test('should call api for templates list', () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.template({ id: 1 }, i18n)
);
expect(WorkflowJobTemplateNodesAPI.read).toBeCalledWith({
unified_job_template: 1,
});
});
test('should call api for credential type list', () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.credentialType({ id: 1 }, i18n)
);
expect(CredentialsAPI.read).toBeCalledWith({
credential_type__id: 1,
});
});
test('should call api for inventory list', () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.inventory({ id: 1 }, i18n)
);
expect(JobTemplatesAPI.read).toBeCalledWith({ inventory: 1 });
expect(WorkflowJobTemplatesAPI.read).toBeCalledWith({
inventory: 1,
});
});
test('should call api for inventory source list', async () => {
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 2 }],
});
await getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.inventorySource(1, i18n)
);
expect(InventoriesAPI.updateSources).toBeCalledWith(1);
expect(WorkflowJobTemplateNodesAPI.read).toBeCalledWith({
unified_job_template: 2,
});
});
test('should call api for organization list', async () => {
getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.organization({ id: 1 }, i18n)
);
expect(CredentialsAPI.read).toBeCalledWith({ organization: 1 });
});
test('should call return error for inventory source list', async () => {
InventoriesAPI.updateSources.mockRejectedValue({
response: {
config: {
method: 'post',
url: '/api/v2/inventories/1/ad_hoc_commands',
},
data: 'An error occurred',
status: 403,
},
});
const { error } = await getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.inventorySource(1, i18n)
);
expect(InventoriesAPI.updateSources).toBeCalledWith(1);
expect(error).toBeDefined();
});
test('should return proper results', async () => {
JobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
ProjectsAPI.read.mockResolvedValue({ data: { count: 2 } });
InventoriesAPI.read.mockResolvedValue({ data: { count: 3 } });
InventorySourcesAPI.read.mockResolvedValue({ data: { count: 0 } });
const { results } = await getRelatedResourceDeleteCounts(
relatedResourceDeleteRequests.credential({ id: 1 }, i18n)
);
expect(results).toEqual({ Projects: 2, Inventories: 3 });
});
});

View File

@ -21,6 +21,7 @@ export async function getRelatedResourceDeleteCounts(requests) {
const {
data: { count },
} = await request();
if (count > 0) {
results[label] = count;
hasCount = true;
@ -40,7 +41,7 @@ export async function getRelatedResourceDeleteCounts(requests) {
export const relatedResourceDeleteRequests = {
credential: (selected, i18n) => [
{
request: async () =>
request: () =>
JobTemplatesAPI.read({
credentials: selected.id,
}),
@ -66,37 +67,6 @@ export const relatedResourceDeleteRequests = {
},
],
project: (selected, i18n) => [
{
request: async () =>
JobTemplatesAPI.read({
credentials: selected.id,
}),
label: i18n._(t`Job Templates`),
},
{
request: () => WorkflowJobTemplatesAPI.read({ credentials: selected.id }),
label: i18n._(t`Workflow Job Templates`),
},
{
request: () =>
InventorySourcesAPI.read({
credentials__id: selected.id,
}),
label: i18n._(t`Inventory Sources`),
},
],
template: (selected, i18n) => [
{
request: async () =>
WorkflowJobTemplateNodesAPI.read({
unified_job_template: selected.id,
}),
label: [i18n._(t`Workflow Job Template Node`)],
},
],
credentialType: (selected, i18n) => [
{
request: async () =>
@ -137,6 +107,37 @@ export const relatedResourceDeleteRequests = {
},
],
project: (selected, i18n) => [
{
request: () =>
JobTemplatesAPI.read({
credentials: selected.id,
}),
label: i18n._(t`Job Templates`),
},
{
request: () => WorkflowJobTemplatesAPI.read({ credentials: selected.id }),
label: i18n._(t`Workflow Job Templates`),
},
{
request: () =>
InventorySourcesAPI.read({
credentials__id: selected.id,
}),
label: i18n._(t`Inventory Sources`),
},
],
template: (selected, i18n) => [
{
request: async () =>
WorkflowJobTemplateNodesAPI.read({
unified_job_template: selected.id,
}),
label: [i18n._(t`Workflow Job Template Node`)],
},
],
organization: (selected, i18n) => [
{
request: async () =>