mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 11:00:03 -03:30
Adds test coverage to the workflow output and workflow output graph components
This commit is contained in:
parent
fc3f19bd2b
commit
ef854aabb7
@ -33,6 +33,7 @@ const fetchWorkflowNodes = async (jobId, pageNo = 1, nodes = []) => {
|
||||
page_size: 200,
|
||||
page: pageNo,
|
||||
});
|
||||
|
||||
if (data.next) {
|
||||
return fetchWorkflowNodes(jobId, pageNo + 1, nodes.concat(data.results));
|
||||
}
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { WorkflowJobsAPI } from '@api';
|
||||
import WorkflowOutput from './WorkflowOutput';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
const job = {
|
||||
id: 1,
|
||||
name: 'Foo JT',
|
||||
status: 'successful',
|
||||
};
|
||||
|
||||
const mockWorkflowJobNodes = [
|
||||
{
|
||||
id: 8,
|
||||
success_nodes: [10],
|
||||
failure_nodes: [],
|
||||
always_nodes: [9],
|
||||
summary_fields: {
|
||||
job: {
|
||||
elapsed: 10,
|
||||
id: 14,
|
||||
name: 'A Playbook',
|
||||
status: 'successful',
|
||||
type: 'job',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
success_nodes: [],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
job: {
|
||||
elapsed: 10,
|
||||
id: 14,
|
||||
name: 'A Project Update',
|
||||
status: 'successful',
|
||||
type: 'project_update',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
success_nodes: [],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
job: {
|
||||
elapsed: 10,
|
||||
id: 14,
|
||||
name: 'An Inventory Source Sync',
|
||||
status: 'successful',
|
||||
type: 'inventory_update',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
success_nodes: [9],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
job: {
|
||||
elapsed: 10,
|
||||
id: 14,
|
||||
name: 'Pause',
|
||||
status: 'successful',
|
||||
type: 'workflow_approval',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('WorkflowOutput', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
WorkflowJobsAPI.readNodes.mockResolvedValue({
|
||||
data: {
|
||||
count: mockWorkflowJobNodes.length,
|
||||
results: mockWorkflowJobNodes,
|
||||
},
|
||||
});
|
||||
window.SVGElement.prototype.height = {
|
||||
baseVal: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
window.SVGElement.prototype.width = {
|
||||
baseVal: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
window.SVGElement.prototype.getBBox = () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
});
|
||||
|
||||
window.SVGElement.prototype.getBoundingClientRect = () => ({
|
||||
x: 303,
|
||||
y: 252.359375,
|
||||
width: 1329,
|
||||
height: 259.640625,
|
||||
top: 252.359375,
|
||||
right: 1632,
|
||||
bottom: 512,
|
||||
left: 303,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
delete window.SVGElement.prototype.getBBox;
|
||||
delete window.SVGElement.prototype.getBoundingClientRect;
|
||||
delete window.SVGElement.prototype.height;
|
||||
delete window.SVGElement.prototype.width;
|
||||
});
|
||||
|
||||
test('renders successfully', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowOutput job={job} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ContentError')).toHaveLength(0);
|
||||
expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
|
||||
expect(wrapper.find('WorkflowOutputNode')).toHaveLength(4);
|
||||
expect(wrapper.find('WorkflowOutputLink')).toHaveLength(5);
|
||||
});
|
||||
|
||||
test('error shown to user when error thrown fetching workflow job nodes', async () => {
|
||||
WorkflowJobsAPI.readNodes.mockRejectedValue(new Error());
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowOutput job={job} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ContentError')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -173,7 +173,8 @@ function WorkflowOutputGraph() {
|
||||
<WorkflowOutputLink
|
||||
key={`link-${link.source.id}-${link.target.id}`}
|
||||
link={link}
|
||||
onUpdateLinkHelp={setLinkHelp}
|
||||
mouseEnter={() => setLinkHelp(link)}
|
||||
mouseLeave={() => setLinkHelp(null)}
|
||||
/>
|
||||
)),
|
||||
nodes.map(node => {
|
||||
|
||||
@ -0,0 +1,235 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { WorkflowStateContext } from '@contexts/Workflow';
|
||||
import WorkflowOutputGraph from './WorkflowOutputGraph';
|
||||
|
||||
const workflowContext = {
|
||||
links: [
|
||||
{
|
||||
source: {
|
||||
id: 2,
|
||||
},
|
||||
target: {
|
||||
id: 4,
|
||||
},
|
||||
linkType: 'success',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 2,
|
||||
},
|
||||
target: {
|
||||
id: 3,
|
||||
},
|
||||
linkType: 'always',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 5,
|
||||
},
|
||||
target: {
|
||||
id: 3,
|
||||
},
|
||||
linkType: 'success',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 1,
|
||||
},
|
||||
target: {
|
||||
id: 2,
|
||||
},
|
||||
linkType: 'always',
|
||||
type: 'link',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 1,
|
||||
},
|
||||
target: {
|
||||
id: 5,
|
||||
},
|
||||
linkType: 'success',
|
||||
type: 'link',
|
||||
},
|
||||
],
|
||||
nodePositions: {
|
||||
1: { label: '', width: 72, height: 40, x: 36, y: 85 },
|
||||
2: { label: '', width: 180, height: 60, x: 282, y: 40 },
|
||||
3: { label: '', width: 180, height: 60, x: 582, y: 130 },
|
||||
4: { label: '', width: 180, height: 60, x: 582, y: 30 },
|
||||
5: { label: '', width: 180, height: 60, x: 282, y: 140 },
|
||||
},
|
||||
nodes: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'node',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'node',
|
||||
job: {
|
||||
name: 'Foo JT',
|
||||
type: 'job',
|
||||
status: 'successful',
|
||||
elapsed: 60,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'node',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'node',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'node',
|
||||
},
|
||||
],
|
||||
showLegend: false,
|
||||
showTools: false,
|
||||
};
|
||||
|
||||
describe('WorkflowOutputGraph', () => {
|
||||
beforeEach(() => {
|
||||
window.SVGElement.prototype.height = {
|
||||
baseVal: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
window.SVGElement.prototype.width = {
|
||||
baseVal: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
window.SVGElement.prototype.getBBox = () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 500,
|
||||
height: 250,
|
||||
});
|
||||
|
||||
window.SVGElement.prototype.getBoundingClientRect = () => ({
|
||||
x: 303,
|
||||
y: 252.359375,
|
||||
width: 1329,
|
||||
height: 259.640625,
|
||||
top: 252.359375,
|
||||
right: 1632,
|
||||
bottom: 512,
|
||||
left: 303,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete window.SVGElement.prototype.getBBox;
|
||||
delete window.SVGElement.prototype.getBoundingClientRect;
|
||||
delete window.SVGElement.prototype.height;
|
||||
delete window.SVGElement.prototype.width;
|
||||
});
|
||||
|
||||
test('mounts successfully', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<WorkflowOutputGraph />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('tools and legend are shown when flags are true', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider
|
||||
value={{ ...workflowContext, showLegend: true, showTools: true }}
|
||||
>
|
||||
<WorkflowOutputGraph />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
expect(wrapper.find('WorkflowLegend')).toHaveLength(1);
|
||||
expect(wrapper.find('WorkflowTools')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('nodes and links are properly rendered', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<WorkflowOutputGraph />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
|
||||
expect(wrapper.find('WorkflowOutputNode')).toHaveLength(4);
|
||||
expect(wrapper.find('WorkflowOutputLink')).toHaveLength(5);
|
||||
expect(wrapper.find('#link-2-4')).toHaveLength(1);
|
||||
expect(wrapper.find('#link-2-3')).toHaveLength(1);
|
||||
expect(wrapper.find('#link-5-3')).toHaveLength(1);
|
||||
expect(wrapper.find('#link-1-2')).toHaveLength(1);
|
||||
expect(wrapper.find('#link-1-5')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('proper help text is shown when hovering over links and nodes', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<WorkflowOutputGraph />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
|
||||
expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
|
||||
wrapper.find('g#node-2').simulate('mouseenter');
|
||||
expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(1);
|
||||
expect(wrapper.find('WorkflowNodeHelp').contains(<b>Name</b>)).toEqual(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper.find('WorkflowNodeHelp').containsMatchingElement(<dd>Foo JT</dd>)
|
||||
).toEqual(true);
|
||||
expect(wrapper.find('WorkflowNodeHelp').contains(<b>Type</b>)).toEqual(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('WorkflowNodeHelp')
|
||||
.containsMatchingElement(<dd>Job Template</dd>)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper.find('WorkflowNodeHelp').contains(<b>Job Status</b>)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('WorkflowNodeHelp')
|
||||
.containsMatchingElement(<dd>Successful</dd>)
|
||||
).toEqual(true);
|
||||
expect(wrapper.find('WorkflowNodeHelp').contains(<b>Elapsed</b>)).toEqual(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('WorkflowNodeHelp')
|
||||
.containsMatchingElement(<dd>00:01:00</dd>)
|
||||
).toEqual(true);
|
||||
wrapper.find('g#node-2').simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
|
||||
wrapper.find('g#link-2-3').simulate('mouseenter');
|
||||
expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(1);
|
||||
expect(wrapper.find('WorkflowLinkHelp').contains(<b>Run</b>)).toEqual(true);
|
||||
expect(
|
||||
wrapper.find('WorkflowLinkHelp').containsMatchingElement(<dd>Always</dd>)
|
||||
).toEqual(true);
|
||||
wrapper.find('g#link-2-3').simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@ -1,13 +1,13 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { WorkflowStateContext } from '@contexts/Workflow';
|
||||
import { shape } from 'prop-types';
|
||||
import { func, shape } from 'prop-types';
|
||||
import {
|
||||
generateLine,
|
||||
getLinePoints,
|
||||
getLinkOverlayPoints,
|
||||
} from '@components/Workflow/WorkflowUtils';
|
||||
|
||||
function WorkflowOutputLink({ link, onUpdateLinkHelp }) {
|
||||
function WorkflowOutputLink({ link, mouseEnter, mouseLeave }) {
|
||||
const ref = useRef(null);
|
||||
const [hovering, setHovering] = useState(false);
|
||||
const [pathD, setPathD] = useState();
|
||||
@ -17,11 +17,13 @@ function WorkflowOutputLink({ link, onUpdateLinkHelp }) {
|
||||
const handleLinkMouseEnter = () => {
|
||||
ref.current.parentNode.appendChild(ref.current);
|
||||
setHovering(true);
|
||||
mouseEnter();
|
||||
};
|
||||
|
||||
const handleLinkMouseLeave = () => {
|
||||
ref.current.parentNode.prepend(ref.current);
|
||||
setHovering(null);
|
||||
mouseLeave();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -56,8 +58,8 @@ function WorkflowOutputLink({ link, onUpdateLinkHelp }) {
|
||||
/>
|
||||
<path d={pathD} stroke={pathStroke} strokeWidth="2px" />
|
||||
<polygon
|
||||
onMouseEnter={() => onUpdateLinkHelp(link)}
|
||||
onMouseLeave={() => onUpdateLinkHelp(null)}
|
||||
onMouseEnter={() => mouseEnter()}
|
||||
onMouseLeave={() => mouseLeave()}
|
||||
opacity="0"
|
||||
points={getLinkOverlayPoints(link, nodePositions)}
|
||||
/>
|
||||
@ -67,6 +69,8 @@ function WorkflowOutputLink({ link, onUpdateLinkHelp }) {
|
||||
|
||||
WorkflowOutputLink.propTypes = {
|
||||
link: shape().isRequired,
|
||||
mouseEnter: func.isRequired,
|
||||
mouseLeave: func.isRequired,
|
||||
};
|
||||
|
||||
export default WorkflowOutputLink;
|
||||
|
||||
@ -35,7 +35,8 @@ describe('WorkflowOutputLink', () => {
|
||||
<WorkflowOutputLink
|
||||
link={link}
|
||||
nodePositions={nodePositions}
|
||||
onUpdateLinkHelp={() => {}}
|
||||
mouseEnter={() => {}}
|
||||
mouseLeave={() => {}}
|
||||
/>
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
|
||||
@ -103,7 +103,7 @@ function WorkflowOutputNode({ history, i18n, mouseEnter, mouseLeave, node }) {
|
||||
{node.job ? (
|
||||
<>
|
||||
<JobTopLine>
|
||||
<StatusIcon status={node.job.status} />
|
||||
{node.job.status && <StatusIcon status={node.job.status} />}
|
||||
<p>{node.job.name}</p>
|
||||
</JobTopLine>
|
||||
<Elapsed>{secondsToHHMMSS(node.job.elapsed)}</Elapsed>
|
||||
|
||||
@ -76,6 +76,7 @@ function WorkflowOutputToolbar({ i18n, job }) {
|
||||
<VerticalSeparator />
|
||||
<Tooltip content={i18n._(t`Toggle Legend`)} position="bottom">
|
||||
<ActionButton
|
||||
id="workflow-output-legend-button"
|
||||
isActive={showLegend}
|
||||
onClick={() => dispatch({ type: 'TOGGLE_LEGEND' })}
|
||||
variant="plain"
|
||||
@ -85,6 +86,7 @@ function WorkflowOutputToolbar({ i18n, job }) {
|
||||
</Tooltip>
|
||||
<Tooltip content={i18n._(t`Toggle Tools`)} position="bottom">
|
||||
<ActionButton
|
||||
id="workflow-output-tools-button"
|
||||
isActive={showTools}
|
||||
onClick={() => dispatch({ type: 'TOGGLE_TOOLS' })}
|
||||
variant="plain"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user