mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 11:00:03 -03:30
Adds basic unit test coverage to visualizer components (not including modals).
This commit is contained in:
parent
1d0e752989
commit
e3cfdb74ba
@ -1,13 +1,17 @@
|
||||
import React from 'react';
|
||||
import { WorkflowStateContext } from '@contexts/Workflow';
|
||||
import {
|
||||
WorkflowDispatchContext,
|
||||
WorkflowStateContext,
|
||||
} from '@contexts/Workflow';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import WorkflowOutputToolbar from './WorkflowOutputToolbar';
|
||||
|
||||
let wrapper;
|
||||
const dispatch = jest.fn();
|
||||
const job = {
|
||||
id: 1,
|
||||
status: 'successful',
|
||||
};
|
||||
|
||||
const workflowContext = {
|
||||
nodes: [],
|
||||
showLegend: false,
|
||||
@ -15,16 +19,7 @@ const workflowContext = {
|
||||
};
|
||||
|
||||
describe('WorkflowOutputToolbar', () => {
|
||||
test('mounts successfully', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<WorkflowOutputToolbar job={job} />
|
||||
</WorkflowStateContext.Provider>
|
||||
);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('shows correct number of nodes', () => {
|
||||
beforeAll(() => {
|
||||
const nodes = [
|
||||
{
|
||||
id: 1,
|
||||
@ -37,12 +32,31 @@ describe('WorkflowOutputToolbar', () => {
|
||||
isDeleted: true,
|
||||
},
|
||||
];
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowStateContext.Provider value={{ ...workflowContext, nodes }}>
|
||||
<WorkflowOutputToolbar job={job} />
|
||||
</WorkflowStateContext.Provider>
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<WorkflowStateContext.Provider value={{ ...workflowContext, nodes }}>
|
||||
<WorkflowOutputToolbar job={job} />
|
||||
</WorkflowStateContext.Provider>
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('Shows correct number of nodes', () => {
|
||||
// The start node (id=1) and deleted nodes (isDeleted=true) should be ignored
|
||||
expect(wrapper.find('Badge').text()).toBe('1');
|
||||
});
|
||||
|
||||
test('Toggle Legend button dispatches as expected', () => {
|
||||
wrapper.find('CompassIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LEGEND' });
|
||||
});
|
||||
|
||||
test('Toggle Tools button dispatches as expected', () => {
|
||||
wrapper.find('WrenchIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_TOOLS' });
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,6 +11,7 @@ function DeleteAllNodesModal({ i18n }) {
|
||||
<AlertModal
|
||||
actions={[
|
||||
<Button
|
||||
id="confirm-delete-all-nodes"
|
||||
key="remove"
|
||||
variant="danger"
|
||||
aria-label={i18n._(t`Confirm removal of all nodes`)}
|
||||
@ -19,6 +20,7 @@ function DeleteAllNodesModal({ i18n }) {
|
||||
{i18n._(t`Remove`)}
|
||||
</Button>,
|
||||
<Button
|
||||
id="cancel-delete-all-nodes"
|
||||
key="cancel"
|
||||
variant="secondary"
|
||||
aria-label={i18n._(t`Cancel node removal`)}
|
||||
|
||||
@ -24,6 +24,7 @@ function LinkModal({ header, i18n, onConfirm }) {
|
||||
onClose={() => dispatch({ type: 'CANCEL_LINK_MODAL' })}
|
||||
actions={[
|
||||
<Button
|
||||
id="link-confirm"
|
||||
key="save"
|
||||
variant="primary"
|
||||
aria-label={i18n._(t`Save link changes`)}
|
||||
@ -32,6 +33,7 @@ function LinkModal({ header, i18n, onConfirm }) {
|
||||
{i18n._(t`Save`)}
|
||||
</Button>,
|
||||
<Button
|
||||
id="link-cancel"
|
||||
key="cancel"
|
||||
variant="secondary"
|
||||
aria-label={i18n._(t`Cancel link changes`)}
|
||||
@ -44,6 +46,7 @@ function LinkModal({ header, i18n, onConfirm }) {
|
||||
<FormGroup fieldId="link-select" label={i18n._(t`Run`)}>
|
||||
<AnsibleSelect
|
||||
id="link-select"
|
||||
name="linkType"
|
||||
value={linkType}
|
||||
data={[
|
||||
{
|
||||
|
||||
@ -0,0 +1,207 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { WorkflowJobTemplateNodesAPI, WorkflowJobTemplatesAPI } from '@api';
|
||||
import Visualizer from './Visualizer';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
const template = {
|
||||
id: 1,
|
||||
name: 'Foo WFJT',
|
||||
summary_fields: {
|
||||
user_capabilities: {
|
||||
edit: true,
|
||||
delete: true,
|
||||
start: true,
|
||||
schedule: true,
|
||||
copy: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockWorkflowNodes = [
|
||||
{
|
||||
id: 8,
|
||||
success_nodes: [10],
|
||||
failure_nodes: [],
|
||||
always_nodes: [9],
|
||||
summary_fields: {
|
||||
unified_job_template: {
|
||||
id: 14,
|
||||
name: 'A Playbook',
|
||||
type: 'job_template',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
success_nodes: [],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
unified_job_template: {
|
||||
id: 14,
|
||||
name: 'A Project Update',
|
||||
type: 'project',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
success_nodes: [],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
unified_job_template: {
|
||||
elapsed: 10,
|
||||
name: 'An Inventory Source Sync',
|
||||
type: 'inventory_source',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
success_nodes: [9],
|
||||
failure_nodes: [],
|
||||
always_nodes: [],
|
||||
summary_fields: {
|
||||
unified_job_template: {
|
||||
id: 14,
|
||||
name: 'Pause',
|
||||
type: 'workflow_approval_template',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('Visualizer', () => {
|
||||
let wrapper;
|
||||
beforeAll(() => {
|
||||
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
|
||||
data: {
|
||||
count: mockWorkflowNodes.length,
|
||||
results: mockWorkflowNodes,
|
||||
},
|
||||
});
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
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>
|
||||
<Visualizer template={template} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ContentError')).toHaveLength(0);
|
||||
expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
|
||||
expect(wrapper.find('VisualizerNode')).toHaveLength(4);
|
||||
expect(wrapper.find('VisualizerLink')).toHaveLength(5);
|
||||
});
|
||||
|
||||
test('Successfully deletes all nodes', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<Visualizer template={template} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('DeleteAllNodesModal').length).toBe(0);
|
||||
wrapper.find('TrashAltIcon').simulate('click');
|
||||
expect(wrapper.find('DeleteAllNodesModal').length).toBe(1);
|
||||
wrapper.find('button#confirm-delete-all-nodes').simulate('click');
|
||||
expect(wrapper.find('VisualizerStartScreen')).toHaveLength(1);
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||
});
|
||||
expect(WorkflowJobTemplateNodesAPI.destroy).toHaveBeenCalledWith(8);
|
||||
expect(WorkflowJobTemplateNodesAPI.destroy).toHaveBeenCalledWith(9);
|
||||
expect(WorkflowJobTemplateNodesAPI.destroy).toHaveBeenCalledWith(10);
|
||||
expect(WorkflowJobTemplateNodesAPI.destroy).toHaveBeenCalledWith(11);
|
||||
});
|
||||
|
||||
test('Successfully changes link type', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<Visualizer template={template} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('LinkEditModal').length).toBe(0);
|
||||
wrapper.find('g#link-2-3').simulate('mouseenter');
|
||||
wrapper.find('#link-edit').simulate('click');
|
||||
expect(wrapper.find('LinkEditModal').length).toBe(1);
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('LinkEditModal')
|
||||
.find('AnsibleSelect')
|
||||
.prop('onChange')(null, 'success');
|
||||
});
|
||||
wrapper.find('button#link-confirm').simulate('click');
|
||||
expect(wrapper.find('LinkEditModal').length).toBe(0);
|
||||
await act(async () => {
|
||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||
});
|
||||
expect(
|
||||
WorkflowJobTemplateNodesAPI.disassociateAlwaysNode
|
||||
).toHaveBeenCalledWith(8, 9);
|
||||
expect(
|
||||
WorkflowJobTemplateNodesAPI.associateSuccessNode
|
||||
).toHaveBeenCalledWith(8, 9);
|
||||
});
|
||||
|
||||
test('Error shown to user when error thrown fetching workflow nodes', async () => {
|
||||
WorkflowJobTemplatesAPI.readNodes.mockRejectedValue(new Error());
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<Visualizer template={template} />
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('ContentError')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -283,8 +283,8 @@ function VisualizerGraph({ i18n, readOnly }) {
|
||||
key={`link-${link.source.id}-${link.target.id}`}
|
||||
link={link}
|
||||
readOnly={readOnly}
|
||||
onUpdateHelpText={setHelpText}
|
||||
onUpdateLinkHelp={setLinkHelp}
|
||||
updateLinkHelp={newLinkHelp => setLinkHelp(newLinkHelp)}
|
||||
updateHelpText={newHelpText => setHelpText(newHelpText)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -297,8 +297,8 @@ function VisualizerGraph({ i18n, readOnly }) {
|
||||
key={`node-${node.id}`}
|
||||
node={node}
|
||||
readOnly={readOnly}
|
||||
onUpdateHelpText={setHelpText}
|
||||
updateNodeHelp={setNodeHelp}
|
||||
updateHelpText={newHelpText => setHelpText(newHelpText)}
|
||||
updateNodeHelp={newNodeHelp => setNodeHelp(newNodeHelp)}
|
||||
{...(addingLink && {
|
||||
onMouseOver: () => drawPotentialLinkToNode(node),
|
||||
})}
|
||||
|
||||
@ -0,0 +1,226 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { WorkflowStateContext } from '@contexts/Workflow';
|
||||
import VisualizerGraph from './VisualizerGraph';
|
||||
|
||||
const workflowContext = {
|
||||
links: [
|
||||
{
|
||||
source: {
|
||||
id: 1,
|
||||
},
|
||||
target: {
|
||||
id: 2,
|
||||
},
|
||||
linkType: 'always',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 1,
|
||||
},
|
||||
target: {
|
||||
id: 5,
|
||||
},
|
||||
linkType: 'always',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 2,
|
||||
},
|
||||
target: {
|
||||
id: 4,
|
||||
},
|
||||
linkType: 'success',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 2,
|
||||
},
|
||||
target: {
|
||||
id: 3,
|
||||
},
|
||||
linkType: 'always',
|
||||
},
|
||||
{
|
||||
source: {
|
||||
id: 5,
|
||||
},
|
||||
target: {
|
||||
id: 3,
|
||||
},
|
||||
linkType: 'success',
|
||||
},
|
||||
],
|
||||
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,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
unifiedJobTemplate: {
|
||||
name: 'Foo JT',
|
||||
type: 'job_template',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
},
|
||||
],
|
||||
showLegend: false,
|
||||
showTools: false,
|
||||
};
|
||||
|
||||
describe('VisualizerGraph', () => {
|
||||
beforeAll(() => {
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
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}>
|
||||
<VisualizerGraph readOnly={false} />
|
||||
</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 }}
|
||||
>
|
||||
<VisualizerGraph readOnly={false} />
|
||||
</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}>
|
||||
<VisualizerGraph readOnly={false} />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
expect(wrapper.find('WorkflowStartNode')).toHaveLength(1);
|
||||
expect(wrapper.find('VisualizerNode')).toHaveLength(4);
|
||||
expect(wrapper.find('VisualizerLink')).toHaveLength(5);
|
||||
expect(wrapper.find('g#link-2-4')).toHaveLength(1);
|
||||
expect(wrapper.find('g#link-2-3')).toHaveLength(1);
|
||||
expect(wrapper.find('g#link-5-3')).toHaveLength(1);
|
||||
expect(wrapper.find('g#link-1-2')).toHaveLength(1);
|
||||
expect(wrapper.find('g#link-1-5')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('proper help text is shown when hovering over nodes', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<VisualizerGraph readOnly={false} />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
|
||||
expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
|
||||
wrapper
|
||||
.find('g#node-2')
|
||||
.find('foreignObject')
|
||||
.first()
|
||||
.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);
|
||||
wrapper
|
||||
.find('g#node-2')
|
||||
.find('foreignObject')
|
||||
.first()
|
||||
.simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowNodeHelp')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('proper help text is shown when hovering over links', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={workflowContext}>
|
||||
<VisualizerGraph readOnly={false} />
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
|
||||
wrapper.find('#link-2-3-overlay').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('#link-2-3-overlay').simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowLinkHelp')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@ -25,9 +25,9 @@ const LinkG = styled.g`
|
||||
function VisualizerLink({
|
||||
i18n,
|
||||
link,
|
||||
onUpdateHelpText,
|
||||
onUpdateLinkHelp,
|
||||
updateLinkHelp,
|
||||
readOnly,
|
||||
updateHelpText,
|
||||
}) {
|
||||
const ref = useRef(null);
|
||||
const [hovering, setHovering] = useState(false);
|
||||
@ -43,7 +43,7 @@ function VisualizerLink({
|
||||
id="link-add-node"
|
||||
key="add"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({
|
||||
type: 'START_ADD_NODE',
|
||||
@ -52,9 +52,9 @@ function VisualizerLink({
|
||||
});
|
||||
}}
|
||||
onMouseEnter={() =>
|
||||
onUpdateHelpText(i18n._(t`Add a new node between these two nodes`))
|
||||
updateHelpText(i18n._(t`Add a new node between these two nodes`))
|
||||
}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<PlusIcon />
|
||||
</WorkflowActionTooltipItem>
|
||||
@ -68,18 +68,26 @@ function VisualizerLink({
|
||||
<WorkflowActionTooltipItem
|
||||
id="link-edit"
|
||||
key="edit"
|
||||
onClick={() => dispatch({ type: 'SET_LINK_TO_EDIT', value: link })}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`Edit this link`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onClick={() => {
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'SET_LINK_TO_EDIT', value: link });
|
||||
}}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`Edit this link`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
<WorkflowActionTooltipItem
|
||||
id="link-delete"
|
||||
key="delete"
|
||||
onClick={() => dispatch({ type: 'START_DELETE_LINK', link })}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`Delete this link`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onClick={() => {
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'START_DELETE_LINK', link });
|
||||
}}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`Delete this link`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<TrashAltIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
@ -124,14 +132,15 @@ function VisualizerLink({
|
||||
>
|
||||
<polygon
|
||||
fill="#E1E1E1"
|
||||
id={`link-${link.source.id}-${link.target.id}-overlay`}
|
||||
id={`link-${link.source.id}-${link.target.id}-background`}
|
||||
opacity={hovering ? '1' : '0'}
|
||||
points={getLinkOverlayPoints(link, nodePositions)}
|
||||
/>
|
||||
<path d={pathD} stroke={pathStroke} strokeWidth="2px" />
|
||||
<polygon
|
||||
onMouseEnter={() => onUpdateLinkHelp(link)}
|
||||
onMouseLeave={() => onUpdateLinkHelp(null)}
|
||||
id={`link-${link.source.id}-${link.target.id}-overlay`}
|
||||
onMouseEnter={() => updateLinkHelp(link)}
|
||||
onMouseLeave={() => updateLinkHelp(null)}
|
||||
opacity="0"
|
||||
points={getLinkOverlayPoints(link, nodePositions)}
|
||||
/>
|
||||
@ -149,8 +158,8 @@ function VisualizerLink({
|
||||
VisualizerLink.propTypes = {
|
||||
link: shape().isRequired,
|
||||
readOnly: bool.isRequired,
|
||||
onUpdateHelpText: func.isRequired,
|
||||
onUpdateLinkHelp: func.isRequired,
|
||||
updateHelpText: func.isRequired,
|
||||
updateLinkHelp: func.isRequired,
|
||||
};
|
||||
|
||||
export default withI18n()(VisualizerLink);
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
WorkflowDispatchContext,
|
||||
WorkflowStateContext,
|
||||
} from '@contexts/Workflow';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import VisualizerLink from './VisualizerLink';
|
||||
|
||||
const link = {
|
||||
source: {
|
||||
id: 2,
|
||||
},
|
||||
target: {
|
||||
id: 3,
|
||||
},
|
||||
linkType: 'success',
|
||||
};
|
||||
|
||||
const mockedContext = {
|
||||
addingLink: false,
|
||||
nodePositions: {
|
||||
1: {
|
||||
width: 72,
|
||||
height: 40,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
2: {
|
||||
width: 180,
|
||||
height: 60,
|
||||
x: 282,
|
||||
y: 40,
|
||||
},
|
||||
3: {
|
||||
width: 180,
|
||||
height: 60,
|
||||
x: 564,
|
||||
y: 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
const updateHelpText = jest.fn();
|
||||
const updateLinkHelp = jest.fn();
|
||||
|
||||
describe('VisualizerLink', () => {
|
||||
let wrapper;
|
||||
beforeAll(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<WorkflowStateContext.Provider value={mockedContext}>
|
||||
<svg>
|
||||
<VisualizerLink
|
||||
link={link}
|
||||
readOnly={false}
|
||||
updateHelpText={updateHelpText}
|
||||
updateLinkHelp={updateLinkHelp}
|
||||
/>
|
||||
</svg>
|
||||
</WorkflowStateContext.Provider>
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
});
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('Displays action tooltip on hover and updates help text on hover', () => {
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
wrapper
|
||||
.find('g')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(1);
|
||||
expect(wrapper.find('WorkflowActionTooltipItem').length).toBe(3);
|
||||
wrapper
|
||||
.find('g')
|
||||
.first()
|
||||
.simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
wrapper
|
||||
.find('#link-2-3-overlay')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
expect(updateLinkHelp).toHaveBeenCalledWith(link);
|
||||
wrapper
|
||||
.find('#link-2-3-overlay')
|
||||
.first()
|
||||
.simulate('mouseleave');
|
||||
expect(updateLinkHelp).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
test('Add Node tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper
|
||||
.find('g')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
wrapper.find('#link-add-node').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(
|
||||
'Add a new node between these two nodes'
|
||||
);
|
||||
wrapper.find('#link-add-node').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#link-add-node').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'START_ADD_NODE',
|
||||
sourceNodeId: 2,
|
||||
targetNodeId: 3,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Edit tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper
|
||||
.find('g')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
wrapper.find('#link-edit').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Edit this link');
|
||||
wrapper.find('#link-edit').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#link-edit').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_LINK_TO_EDIT',
|
||||
value: link,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Delete tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper
|
||||
.find('g')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
wrapper.find('#link-delete').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Delete this link');
|
||||
wrapper.find('#link-delete').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#link-delete').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'START_DELETE_LINK',
|
||||
link,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
});
|
||||
@ -33,20 +33,21 @@ const NodeContents = styled.div`
|
||||
props.isInvalidLinkTarget ? '#D7D7D7' : '#FFFFFF'};
|
||||
`;
|
||||
|
||||
const NodeDefaultLabel = styled.p`
|
||||
const NodeResourceName = styled.p`
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
NodeResourceName.displayName = 'NodeResourceName';
|
||||
|
||||
function VisualizerNode({
|
||||
i18n,
|
||||
node,
|
||||
onMouseOver,
|
||||
readOnly,
|
||||
onUpdateHelpText,
|
||||
updateHelpText,
|
||||
updateNodeHelp,
|
||||
}) {
|
||||
const ref = useRef(null);
|
||||
@ -62,7 +63,7 @@ function VisualizerNode({
|
||||
ref.current.parentNode.appendChild(ref.current);
|
||||
setHovering(true);
|
||||
if (addingLink) {
|
||||
onUpdateHelpText(
|
||||
updateHelpText(
|
||||
node.isInvalidLinkTarget
|
||||
? i18n._(
|
||||
t`Invalid link target. Unable to link to children or ancestor nodes. Graph cycles are not supported.`
|
||||
@ -76,7 +77,7 @@ function VisualizerNode({
|
||||
const handleNodeMouseLeave = () => {
|
||||
setHovering(false);
|
||||
if (addingLink) {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
}
|
||||
};
|
||||
|
||||
@ -91,12 +92,12 @@ function VisualizerNode({
|
||||
id="node-details"
|
||||
key="details"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'SET_NODE_TO_VIEW', value: node });
|
||||
}}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`View node details`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`View node details`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<InfoIcon />
|
||||
</WorkflowActionTooltipItem>
|
||||
@ -109,12 +110,12 @@ function VisualizerNode({
|
||||
id="node-add"
|
||||
key="add"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'START_ADD_NODE', sourceNodeId: node.id });
|
||||
}}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`Add a new node`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`Add a new node`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<PlusIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
@ -123,12 +124,12 @@ function VisualizerNode({
|
||||
id="node-edit"
|
||||
key="edit"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'SET_NODE_TO_EDIT', value: node });
|
||||
}}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`Edit this node`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`Edit this node`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<PencilAltIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
@ -136,14 +137,14 @@ function VisualizerNode({
|
||||
id="node-link"
|
||||
key="link"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'SELECT_SOURCE_FOR_LINKING', node });
|
||||
}}
|
||||
onMouseEnter={() =>
|
||||
onUpdateHelpText(i18n._(t`Link to an available node`))
|
||||
updateHelpText(i18n._(t`Link to an available node`))
|
||||
}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<LinkIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
@ -151,12 +152,12 @@ function VisualizerNode({
|
||||
id="node-delete"
|
||||
key="delete"
|
||||
onClick={() => {
|
||||
onUpdateHelpText(null);
|
||||
updateHelpText(null);
|
||||
setHovering(false);
|
||||
dispatch({ type: 'SET_NODE_TO_DELETE', value: node });
|
||||
}}
|
||||
onMouseEnter={() => onUpdateHelpText(i18n._(t`Delete this node`))}
|
||||
onMouseLeave={() => onUpdateHelpText(null)}
|
||||
onMouseEnter={() => updateHelpText(i18n._(t`Delete this node`))}
|
||||
onMouseLeave={() => updateHelpText(null)}
|
||||
>
|
||||
<TrashAltIcon />
|
||||
</WorkflowActionTooltipItem>,
|
||||
@ -198,11 +199,11 @@ function VisualizerNode({
|
||||
y="1"
|
||||
>
|
||||
<NodeContents isInvalidLinkTarget={node.isInvalidLinkTarget}>
|
||||
<NodeDefaultLabel>
|
||||
<NodeResourceName>
|
||||
{node.unifiedJobTemplate
|
||||
? node.unifiedJobTemplate.name
|
||||
: i18n._(t`DELETED`)}
|
||||
</NodeDefaultLabel>
|
||||
</NodeResourceName>
|
||||
</NodeContents>
|
||||
</foreignObject>
|
||||
{node.unifiedJobTemplate && <WorkflowNodeTypeLetter node={node} />}
|
||||
@ -221,7 +222,7 @@ VisualizerNode.propTypes = {
|
||||
node: shape().isRequired,
|
||||
onMouseOver: func,
|
||||
readOnly: bool.isRequired,
|
||||
onUpdateHelpText: func.isRequired,
|
||||
updateHelpText: func.isRequired,
|
||||
updateNodeHelp: func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,230 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
WorkflowDispatchContext,
|
||||
WorkflowStateContext,
|
||||
} from '@contexts/Workflow';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import VisualizerNode from './VisualizerNode';
|
||||
|
||||
const mockedContext = {
|
||||
addingLink: false,
|
||||
addLinkSourceNode: null,
|
||||
nodePositions: {
|
||||
1: {
|
||||
width: 72,
|
||||
height: 40,
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
2: {
|
||||
width: 180,
|
||||
height: 60,
|
||||
x: 282,
|
||||
y: 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nodeWithJT = {
|
||||
id: 2,
|
||||
unifiedJobTemplate: {
|
||||
id: 77,
|
||||
name: 'Automation JT',
|
||||
type: 'job_template',
|
||||
},
|
||||
};
|
||||
|
||||
const dispatch = jest.fn();
|
||||
const updateHelpText = jest.fn();
|
||||
const updateNodeHelp = jest.fn();
|
||||
|
||||
describe('VisualizerNode', () => {
|
||||
describe('Node with unified job template', () => {
|
||||
let wrapper;
|
||||
beforeAll(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<WorkflowStateContext.Provider value={mockedContext}>
|
||||
<svg>
|
||||
<VisualizerNode
|
||||
mouseEnter={() => {}}
|
||||
mouseLeave={() => {}}
|
||||
node={nodeWithJT}
|
||||
readOnly={false}
|
||||
updateHelpText={updateHelpText}
|
||||
updateNodeHelp={updateNodeHelp}
|
||||
/>
|
||||
</svg>
|
||||
</WorkflowStateContext.Provider>
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
});
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
test('Displays unified job template name inside node', () => {
|
||||
expect(wrapper.find('NodeResourceName').text()).toBe('Automation JT');
|
||||
});
|
||||
test('Displays action tooltip on hover and updates help text on hover', () => {
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(1);
|
||||
expect(wrapper.find('WorkflowActionTooltipItem').length).toBe(5);
|
||||
wrapper.find('VisualizerNode').simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
wrapper
|
||||
.find('foreignObject')
|
||||
.first()
|
||||
.simulate('mouseenter');
|
||||
expect(updateNodeHelp).toHaveBeenCalledWith(nodeWithJT);
|
||||
wrapper
|
||||
.find('foreignObject')
|
||||
.first()
|
||||
.simulate('mouseleave');
|
||||
expect(updateNodeHelp).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
test('Add tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
wrapper.find('#node-add').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Add a new node');
|
||||
wrapper.find('#node-add').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#node-add').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'START_ADD_NODE',
|
||||
sourceNodeId: 2,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Edit tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
wrapper.find('#node-edit').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Edit this node');
|
||||
wrapper.find('#node-edit').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#node-edit').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_NODE_TO_EDIT',
|
||||
value: nodeWithJT,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Details tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
wrapper.find('#node-details').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('View node details');
|
||||
wrapper.find('#node-details').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#node-details').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_NODE_TO_VIEW',
|
||||
value: nodeWithJT,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Link tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
wrapper.find('#node-link').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Link to an available node');
|
||||
wrapper.find('#node-link').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#node-link').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SELECT_SOURCE_FOR_LINKING',
|
||||
node: nodeWithJT,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
|
||||
test('Delete tooltip action hover/click updates help text and dispatches properly', () => {
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
wrapper.find('#node-delete').simulate('mouseenter');
|
||||
expect(updateHelpText).toHaveBeenCalledWith('Delete this node');
|
||||
wrapper.find('#node-delete').simulate('mouseleave');
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
wrapper.find('#node-delete').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_NODE_TO_DELETE',
|
||||
value: nodeWithJT,
|
||||
});
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
});
|
||||
});
|
||||
describe('Node actions while adding a new link', () => {
|
||||
let wrapper;
|
||||
beforeAll(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<WorkflowStateContext.Provider
|
||||
value={{
|
||||
...mockedContext,
|
||||
addingLink: true,
|
||||
addLinkSourceNode: 323,
|
||||
}}
|
||||
>
|
||||
<svg>
|
||||
<VisualizerNode
|
||||
mouseEnter={() => {}}
|
||||
mouseLeave={() => {}}
|
||||
node={nodeWithJT}
|
||||
readOnly={false}
|
||||
updateHelpText={updateHelpText}
|
||||
updateNodeHelp={updateNodeHelp}
|
||||
/>
|
||||
</svg>
|
||||
</WorkflowStateContext.Provider>
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
});
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
test('Displays correct help text when hovering over node while adding link', () => {
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
wrapper.find('VisualizerNode').simulate('mouseenter');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
expect(updateHelpText).toHaveBeenCalledWith(
|
||||
'Click to create a new link to this node.'
|
||||
);
|
||||
wrapper.find('VisualizerNode').simulate('mouseleave');
|
||||
expect(wrapper.find('WorkflowActionTooltip').length).toBe(0);
|
||||
expect(updateHelpText).toHaveBeenCalledWith(null);
|
||||
});
|
||||
test('Dispatches properly when node is clicked', () => {
|
||||
wrapper
|
||||
.find('foreignObject')
|
||||
.first()
|
||||
.simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_ADD_LINK_TARGET_NODE',
|
||||
value: nodeWithJT,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Node without unified job template', () => {
|
||||
test('Displays DELETED text inside node when unified job template is missing', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<svg>
|
||||
<WorkflowStateContext.Provider value={mockedContext}>
|
||||
<VisualizerNode
|
||||
mouseEnter={() => {}}
|
||||
mouseLeave={() => {}}
|
||||
node={{
|
||||
id: 2,
|
||||
}}
|
||||
readOnly={false}
|
||||
updateHelpText={() => {}}
|
||||
updateNodeHelp={() => {}}
|
||||
/>
|
||||
</WorkflowStateContext.Provider>
|
||||
</svg>
|
||||
);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('NodeResourceName').text()).toBe('DELETED');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { WorkflowDispatchContext } from '@contexts/Workflow';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import VisualizerStartScreen from './VisualizerStartScreen';
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
describe('VisualizerStartScreen', () => {
|
||||
test('dispatches properly when start button clicked', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<VisualizerStartScreen />
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
wrapper.find('Button').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'START_ADD_NODE',
|
||||
sourceNodeId: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -99,7 +99,11 @@ function VisualizerToolbar({ i18n, onClose, onSave, template }) {
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
<VerticalSeparator />
|
||||
<Button variant="primary" onClick={onSave}>
|
||||
<Button
|
||||
aria-label={i18n._(t`Save`)}
|
||||
variant="primary"
|
||||
onClick={onSave}
|
||||
>
|
||||
{i18n._(t`Save`)}
|
||||
</Button>
|
||||
<VerticalSeparator />
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
WorkflowDispatchContext,
|
||||
WorkflowStateContext,
|
||||
} from '@contexts/Workflow';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import VisualizerToolbar from './VisualizerToolbar';
|
||||
|
||||
let wrapper;
|
||||
const close = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
const save = jest.fn();
|
||||
const template = {
|
||||
id: 1,
|
||||
name: 'Test JT',
|
||||
};
|
||||
const workflowContext = {
|
||||
nodes: [],
|
||||
showLegend: false,
|
||||
showTools: false,
|
||||
};
|
||||
|
||||
describe('VisualizerToolbar', () => {
|
||||
beforeAll(() => {
|
||||
const nodes = [
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
isDeleted: true,
|
||||
},
|
||||
];
|
||||
wrapper = mountWithContexts(
|
||||
<WorkflowDispatchContext.Provider value={dispatch}>
|
||||
<WorkflowStateContext.Provider value={{ ...workflowContext, nodes }}>
|
||||
<VisualizerToolbar
|
||||
onClose={close}
|
||||
onSave={save}
|
||||
template={template}
|
||||
/>
|
||||
</WorkflowStateContext.Provider>
|
||||
</WorkflowDispatchContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('Shows correct number of nodes', () => {
|
||||
// The start node (id=1) and deleted nodes (isDeleted=true) should be ignored
|
||||
expect(wrapper.find('Badge').text()).toBe('1');
|
||||
});
|
||||
|
||||
test('Toggle Legend button dispatches as expected', () => {
|
||||
wrapper.find('CompassIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LEGEND' });
|
||||
});
|
||||
|
||||
test('Toggle Tools button dispatches as expected', () => {
|
||||
wrapper.find('WrenchIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_TOOLS' });
|
||||
});
|
||||
|
||||
test('Delete All button dispatches as expected', () => {
|
||||
wrapper.find('TrashAltIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_SHOW_DELETE_ALL_NODES_MODAL',
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('Delete All button dispatches as expected', () => {
|
||||
wrapper.find('TrashAltIcon').simulate('click');
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
type: 'SET_SHOW_DELETE_ALL_NODES_MODAL',
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('Save button calls expected function', () => {
|
||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||
expect(save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Close button calls expected function', () => {
|
||||
wrapper.find('TimesIcon').simulate('click');
|
||||
expect(close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user