mirror of
https://github.com/ansible/awx.git
synced 2026-04-14 14:39:26 -02:30
Adds cancel button to jobs list toolbar
This commit is contained in:
@@ -8,15 +8,20 @@ import AlertModal from '../AlertModal';
|
|||||||
import DatalistToolbar from '../DataListToolbar';
|
import DatalistToolbar from '../DataListToolbar';
|
||||||
import ErrorDetail from '../ErrorDetail';
|
import ErrorDetail from '../ErrorDetail';
|
||||||
import PaginatedDataList, { ToolbarDeleteButton } from '../PaginatedDataList';
|
import PaginatedDataList, { ToolbarDeleteButton } from '../PaginatedDataList';
|
||||||
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
import useRequest, {
|
||||||
|
useDeleteItems,
|
||||||
|
useDismissableError,
|
||||||
|
} from '../../util/useRequest';
|
||||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||||
import JobListItem from './JobListItem';
|
import JobListItem from './JobListItem';
|
||||||
|
import JobListCancelButton from './JobListCancelButton';
|
||||||
import useWsJobs from './useWsJobs';
|
import useWsJobs from './useWsJobs';
|
||||||
import {
|
import {
|
||||||
AdHocCommandsAPI,
|
AdHocCommandsAPI,
|
||||||
InventoryUpdatesAPI,
|
InventoryUpdatesAPI,
|
||||||
JobsAPI,
|
JobsAPI,
|
||||||
ProjectUpdatesAPI,
|
ProjectUpdatesAPI,
|
||||||
|
RelatedAPI,
|
||||||
SystemJobsAPI,
|
SystemJobsAPI,
|
||||||
UnifiedJobsAPI,
|
UnifiedJobsAPI,
|
||||||
WorkflowJobsAPI,
|
WorkflowJobsAPI,
|
||||||
@@ -88,6 +93,30 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
|||||||
const jobs = useWsJobs(results, fetchJobsById, QS_CONFIG);
|
const jobs = useWsJobs(results, fetchJobsById, QS_CONFIG);
|
||||||
|
|
||||||
const isAllSelected = selected.length === jobs.length && selected.length > 0;
|
const isAllSelected = selected.length === jobs.length && selected.length > 0;
|
||||||
|
|
||||||
|
const {
|
||||||
|
error: cancelJobsError,
|
||||||
|
isLoading: isCancelLoading,
|
||||||
|
request: cancelJobs,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
return Promise.all(
|
||||||
|
selected.map(job => {
|
||||||
|
if (['new', 'pending', 'waiting', 'running'].includes(job.status)) {
|
||||||
|
return RelatedAPI.post(job.related.cancel);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [selected]),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
error: cancelError,
|
||||||
|
dismissError: dismissCancelError,
|
||||||
|
} = useDismissableError(cancelJobsError);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: isDeleteLoading,
|
isLoading: isDeleteLoading,
|
||||||
deleteItems: deleteJobs,
|
deleteItems: deleteJobs,
|
||||||
@@ -123,6 +152,11 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleJobCancel = async () => {
|
||||||
|
await cancelJobs();
|
||||||
|
setSelected([]);
|
||||||
|
};
|
||||||
|
|
||||||
const handleJobDelete = async () => {
|
const handleJobDelete = async () => {
|
||||||
await deleteJobs();
|
await deleteJobs();
|
||||||
setSelected([]);
|
setSelected([]);
|
||||||
@@ -145,7 +179,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
contentError={contentError}
|
contentError={contentError}
|
||||||
hasContentLoading={isLoading || isDeleteLoading}
|
hasContentLoading={isLoading || isDeleteLoading || isCancelLoading}
|
||||||
items={jobs}
|
items={jobs}
|
||||||
itemCount={count}
|
itemCount={count}
|
||||||
pluralizedItemName={i18n._(t`Jobs`)}
|
pluralizedItemName={i18n._(t`Jobs`)}
|
||||||
@@ -242,6 +276,10 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName="Jobs"
|
pluralizedItemName="Jobs"
|
||||||
/>,
|
/>,
|
||||||
|
<JobListCancelButton
|
||||||
|
onCancel={handleJobCancel}
|
||||||
|
jobsToCancel={selected}
|
||||||
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -256,15 +294,28 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<AlertModal
|
{deletionError && (
|
||||||
isOpen={deletionError}
|
<AlertModal
|
||||||
variant="error"
|
isOpen
|
||||||
title={i18n._(t`Error!`)}
|
variant="error"
|
||||||
onClose={clearDeletionError}
|
title={i18n._(t`Error!`)}
|
||||||
>
|
onClose={clearDeletionError}
|
||||||
{i18n._(t`Failed to delete one or more jobs.`)}
|
>
|
||||||
<ErrorDetail error={deletionError} />
|
{i18n._(t`Failed to delete one or more jobs.`)}
|
||||||
</AlertModal>
|
<ErrorDetail error={deletionError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
|
{cancelError && (
|
||||||
|
<AlertModal
|
||||||
|
isOpen
|
||||||
|
variant="error"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={dismissCancelError}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to cancel one or more jobs.`)}
|
||||||
|
<ErrorDetail error={cancelError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
InventoryUpdatesAPI,
|
InventoryUpdatesAPI,
|
||||||
JobsAPI,
|
JobsAPI,
|
||||||
ProjectUpdatesAPI,
|
ProjectUpdatesAPI,
|
||||||
|
RelatedAPI,
|
||||||
SystemJobsAPI,
|
SystemJobsAPI,
|
||||||
UnifiedJobsAPI,
|
UnifiedJobsAPI,
|
||||||
WorkflowJobsAPI,
|
WorkflowJobsAPI,
|
||||||
@@ -23,6 +24,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/project_updates/1',
|
url: '/api/v2/project_updates/1',
|
||||||
name: 'job 1',
|
name: 'job 1',
|
||||||
type: 'project_update',
|
type: 'project_update',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/project_updates/1/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -35,6 +40,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/jobs/2',
|
url: '/api/v2/jobs/2',
|
||||||
name: 'job 2',
|
name: 'job 2',
|
||||||
type: 'job',
|
type: 'job',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/jobs/2/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -47,6 +56,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/inventory_updates/3',
|
url: '/api/v2/inventory_updates/3',
|
||||||
name: 'job 3',
|
name: 'job 3',
|
||||||
type: 'inventory_update',
|
type: 'inventory_update',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/inventory_updates/3/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -59,6 +72,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/workflow_jobs/4',
|
url: '/api/v2/workflow_jobs/4',
|
||||||
name: 'job 4',
|
name: 'job 4',
|
||||||
type: 'workflow_job',
|
type: 'workflow_job',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/workflow_jobs/4/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -71,6 +88,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/system_jobs/5',
|
url: '/api/v2/system_jobs/5',
|
||||||
name: 'job 5',
|
name: 'job 5',
|
||||||
type: 'system_job',
|
type: 'system_job',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/system_jobs/5/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -83,6 +104,10 @@ const mockResults = [
|
|||||||
url: '/api/v2/ad_hoc_commands/6',
|
url: '/api/v2/ad_hoc_commands/6',
|
||||||
name: 'job 6',
|
name: 'job 6',
|
||||||
type: 'ad_hoc_command',
|
type: 'ad_hoc_command',
|
||||||
|
status: 'running',
|
||||||
|
related: {
|
||||||
|
cancel: '/api/v2/ad_hoc_commands/6/cancel',
|
||||||
|
},
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -273,4 +298,81 @@ describe('<JobList />', () => {
|
|||||||
el => el.props().isOpen === true && el.props().title === 'Error!'
|
el => el.props().isOpen === true && el.props().title === 'Error!'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should send all corresponding delete API requests', async () => {
|
||||||
|
RelatedAPI.post = jest.fn();
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<JobList />);
|
||||||
|
});
|
||||||
|
await waitForLoaded(wrapper);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('DataListToolbar').invoke('onSelectAll')(true);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
wrapper.find('JobListItem');
|
||||||
|
expect(
|
||||||
|
wrapper.find('JobListCancelButton').prop('jobsToCancel')
|
||||||
|
).toHaveLength(6);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('JobListCancelButton').invoke('onCancel')();
|
||||||
|
});
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledTimes(6);
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/project_updates/1/cancel'
|
||||||
|
);
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith('/api/v2/jobs/2/cancel');
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/inventory_updates/3/cancel'
|
||||||
|
);
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/workflow_jobs/4/cancel'
|
||||||
|
);
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/system_jobs/5/cancel'
|
||||||
|
);
|
||||||
|
expect(RelatedAPI.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/ad_hoc_commands/6/cancel'
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error is shown when job not successfully cancelled', async () => {
|
||||||
|
RelatedAPI.post.mockImplementation(() => {
|
||||||
|
throw new Error({
|
||||||
|
response: {
|
||||||
|
config: {
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/v2/jobs/2/cancel',
|
||||||
|
},
|
||||||
|
data: 'An error occurred',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<JobList />);
|
||||||
|
});
|
||||||
|
await waitForLoaded(wrapper);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper
|
||||||
|
.find('JobListItem')
|
||||||
|
.at(1)
|
||||||
|
.invoke('onSelect')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('JobListCancelButton').invoke('onCancel')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'Modal',
|
||||||
|
el => el.props().isOpen === true && el.props().title === 'Error!'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
141
awx/ui_next/src/components/JobList/JobListCancelButton.jsx
Normal file
141
awx/ui_next/src/components/JobList/JobListCancelButton.jsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { arrayOf, func } from 'prop-types';
|
||||||
|
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||||
|
import { Kebabified } from '../../contexts/Kebabified';
|
||||||
|
import AlertModal from '../AlertModal';
|
||||||
|
import { Job } from '../../types';
|
||||||
|
|
||||||
|
function cannotCancel(job) {
|
||||||
|
return !job.summary_fields.user_capabilities.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onCancel();
|
||||||
|
setIsModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTooltip = () => {
|
||||||
|
const jobsUnableToCancel = jobsToCancel
|
||||||
|
.filter(cannotCancel)
|
||||||
|
.map(job => job.name)
|
||||||
|
.join(', ');
|
||||||
|
if (jobsToCancel.some(cannotCancel)) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{i18n.plural({
|
||||||
|
value: jobsToCancel.length,
|
||||||
|
one: 'You do not have permission to cancel the following job: ',
|
||||||
|
other: 'You do not have permission to cancel the following jobs: ',
|
||||||
|
})}
|
||||||
|
{jobsUnableToCancel}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (jobsToCancel.length) {
|
||||||
|
return i18n.plural({
|
||||||
|
value: jobsToCancel.length,
|
||||||
|
one: 'Cancel selected job',
|
||||||
|
other: 'Cancel selected jobs',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return i18n._(t`Select a job to cancel`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
jobsToCancel.length === 0 || jobsToCancel.some(cannotCancel);
|
||||||
|
|
||||||
|
const cancelJobText = i18n.plural({
|
||||||
|
value: jobsToCancel.length < 2,
|
||||||
|
one: 'Cancel job',
|
||||||
|
other: 'Cancel jobs',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Kebabified>
|
||||||
|
{({ isKebabified }) => (
|
||||||
|
<>
|
||||||
|
{isKebabified ? (
|
||||||
|
<DropdownItem
|
||||||
|
key="cancel-job"
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
component="Button"
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
>
|
||||||
|
{cancelJobText}
|
||||||
|
</DropdownItem>
|
||||||
|
) : (
|
||||||
|
<Tooltip content={renderTooltip()} position="top">
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={cancelJobText}
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
>
|
||||||
|
{cancelJobText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{isModalOpen && (
|
||||||
|
<AlertModal
|
||||||
|
variant="danger"
|
||||||
|
title={cancelJobText}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={() => setIsModalOpen(false)}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key="delete"
|
||||||
|
variant="danger"
|
||||||
|
aria-label={cancelJobText}
|
||||||
|
onClick={handleCancel}
|
||||||
|
>
|
||||||
|
{cancelJobText}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Return`)}
|
||||||
|
onClick={() => setIsModalOpen(false)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Return`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{i18n.plural({
|
||||||
|
value: jobsToCancel.length,
|
||||||
|
one: 'This action will cancel the following job:',
|
||||||
|
other: 'This action will cancel the following jobs:',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{jobsToCancel.map(job => (
|
||||||
|
<span key={job.id}>
|
||||||
|
<strong>{job.name}</strong>
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Kebabified>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
JobListCancelButton.propTypes = {
|
||||||
|
jobsToCancel: arrayOf(Job),
|
||||||
|
onCancel: func,
|
||||||
|
};
|
||||||
|
|
||||||
|
JobListCancelButton.defaultProps = {
|
||||||
|
jobsToCancel: [],
|
||||||
|
onCancel: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(JobListCancelButton);
|
||||||
110
awx/ui_next/src/components/JobList/JobListCancelButton.test.jsx
Normal file
110
awx/ui_next/src/components/JobList/JobListCancelButton.test.jsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||||
|
import JobListCancelButton from './JobListCancelButton';
|
||||||
|
|
||||||
|
describe('<JobListCancelButton />', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('should be disabled when no rows are selected', () => {
|
||||||
|
wrapper = mountWithContexts(<JobListCancelButton jobsToCancel={[]} />);
|
||||||
|
expect(wrapper.find('JobListCancelButton button').props().disabled).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(wrapper.find('Tooltip').props().content).toBe(
|
||||||
|
'Select a job to cancel'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('should be disabled when user does not have permissions to cancel selected job', () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<JobListCancelButton
|
||||||
|
jobsToCancel={[
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'some job',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
delete: false,
|
||||||
|
start: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('JobListCancelButton button').props().disabled).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const tooltipContents = wrapper.find('Tooltip').props().content;
|
||||||
|
const renderedTooltipContents = shallow(tooltipContents);
|
||||||
|
expect(
|
||||||
|
renderedTooltipContents.matchesElement(
|
||||||
|
<div>
|
||||||
|
You do not have permission to cancel the following job: some job
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
test('should be enabled when user does have permission to cancel selected job', () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<JobListCancelButton
|
||||||
|
jobsToCancel={[
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'some job',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
delete: true,
|
||||||
|
start: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('JobListCancelButton button').props().disabled).toBe(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
expect(wrapper.find('Tooltip').props().content).toBe('Cancel selected job');
|
||||||
|
});
|
||||||
|
test('modal functions as expected', () => {
|
||||||
|
const onCancel = jest.fn();
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<JobListCancelButton
|
||||||
|
jobsToCancel={[
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'some job',
|
||||||
|
summary_fields: {
|
||||||
|
user_capabilities: {
|
||||||
|
delete: true,
|
||||||
|
start: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onCancel={onCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('AlertModal').length).toBe(0);
|
||||||
|
wrapper.find('JobListCancelButton button').simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AlertModal').length).toBe(1);
|
||||||
|
wrapper.find('AlertModal button[aria-label="Return"]').simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
expect(onCancel).toHaveBeenCalledTimes(0);
|
||||||
|
expect(wrapper.find('AlertModal').length).toBe(0);
|
||||||
|
expect(wrapper.find('AlertModal').length).toBe(0);
|
||||||
|
wrapper.find('JobListCancelButton button').simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AlertModal').length).toBe(1);
|
||||||
|
wrapper
|
||||||
|
.find('AlertModal button[aria-label="Cancel job"]')
|
||||||
|
.simulate('click');
|
||||||
|
wrapper.update();
|
||||||
|
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user