From 50c74a2ec859ee0da1eda4c75171d45b851de9ef Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 6 Feb 2020 10:29:23 -0500 Subject: [PATCH] Adds test coverage for node modal components --- .../Modals/DeleteAllNodesModal.test.jsx | 4 +- .../Modals/LinkModals/LinkAddModal.test.jsx | 14 +- .../Modals/LinkModals/LinkDeleteModal.jsx | 2 +- .../LinkModals/LinkDeleteModal.test.jsx | 13 +- .../Modals/LinkModals/LinkEditModal.test.jsx | 20 +- .../Modals/LinkModals/LinkModal.test.jsx | 85 ++++ .../Modals/NodeModals/NodeAddModal.jsx | 3 +- .../Modals/NodeModals/NodeAddModal.test.jsx | 42 ++ .../Modals/NodeModals/NodeDeleteModal.jsx | 2 + .../NodeModals/NodeDeleteModal.test.jsx | 85 ++++ .../Modals/NodeModals/NodeEditModal.jsx | 4 +- .../Modals/NodeModals/NodeEditModal.test.jsx | 48 ++ .../Modals/NodeModals/NodeModal.jsx | 10 +- .../Modals/NodeModals/NodeModal.test.jsx | 414 ++++++++++++++++++ .../Modals/NodeModals/NodeNextButton.jsx | 1 + .../Modals/NodeModals/NodeNextButton.test.jsx | 48 ++ .../NodeModals/NodeTypeStep/NodeTypeStep.jsx | 56 +-- .../Modals/NodeModals/NodeViewModal.test.jsx | 22 + .../Modals/NodeModals/RunStep.jsx | 3 + .../Modals/NodeModals/RunStep.test.jsx | 40 ++ .../Modals/UnsavedChangesModal.test.jsx | 4 +- 21 files changed, 862 insertions(+), 58 deletions(-) create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeDeleteModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/RunStep.test.jsx diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.test.jsx index 5c28c06adb..45f426755d 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/DeleteAllNodesModal.test.jsx @@ -1,7 +1,5 @@ import React from 'react'; -import { - WorkflowDispatchContext, -} from '@contexts/Workflow'; +import { WorkflowDispatchContext } from '@contexts/Workflow'; import { mountWithContexts } from '@testUtils/enzymeHelpers'; import DeleteAllNodesModal from './DeleteAllNodesModal'; diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.test.jsx index e7b5b94c40..bb68a69161 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.test.jsx @@ -1,12 +1,15 @@ import React from 'react'; import { mountWithContexts } from '@testUtils/enzymeHelpers'; -import { WorkflowDispatchContext, WorkflowStateContext } from '@contexts/Workflow'; +import { + WorkflowDispatchContext, + WorkflowStateContext, +} from '@contexts/Workflow'; import LinkAddModal from './LinkAddModal'; const dispatch = jest.fn(); const workflowContext = { - linkToEdit: null + linkToEdit: null, }; describe('LinkAddModal', () => { @@ -17,8 +20,11 @@ describe('LinkAddModal', () => { - ); + ); wrapper.find('button#link-confirm').simulate('click'); - expect(dispatch).toHaveBeenCalledWith({ type: 'CREATE_LINK', linkType: 'success' }); + expect(dispatch).toHaveBeenCalledWith({ + type: 'CREATE_LINK', + linkType: 'success', + }); }); }); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.jsx index d868e12083..216ecb71e3 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkDeleteModal.jsx @@ -28,7 +28,7 @@ function LinkDeleteModal({ i18n }) { {i18n._(t`Remove`)} , )} - diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.jsx new file mode 100644 index 0000000000..5cd9840b57 --- /dev/null +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.jsx @@ -0,0 +1,414 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { + WorkflowDispatchContext, + WorkflowStateContext, +} from '@contexts/Workflow'; +import { mountWithContexts } from '@testUtils/enzymeHelpers'; +import { + InventorySourcesAPI, + JobTemplatesAPI, + ProjectsAPI, + WorkflowJobTemplatesAPI, +} from '@api'; +import NodeModal from './NodeModal'; + +jest.mock('@api/models/InventorySources'); +jest.mock('@api/models/JobTemplates'); +jest.mock('@api/models/Projects'); +jest.mock('@api/models/WorkflowJobTemplates'); + +let wrapper; +const dispatch = jest.fn(); +const onSave = jest.fn(); + +describe('NodeModal', () => { + beforeAll(() => { + JobTemplatesAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Test Job Template', + type: 'job_template', + url: '/api/v2/job_templates/1', + }, + ], + }, + }); + ProjectsAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Test Project', + type: 'project', + url: '/api/v2/projects/1', + }, + ], + }, + }); + InventorySourcesAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Test Inventory Source', + type: 'inventory_source', + url: '/api/v2/inventory_sources/1', + }, + ], + }, + }); + WorkflowJobTemplatesAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Test Workflow Job Template', + type: 'workflow_job_template', + url: '/api/v2/workflow_job_templates/1', + }, + ], + }, + }); + }); + afterAll(() => { + jest.clearAllMocks(); + }); + describe('Add new node', () => { + beforeEach(async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + + + ); + }); + }); + + afterAll(() => { + wrapper.unmount(); + }); + + test('Can successfully create a new job template node', async () => { + act(() => { + wrapper.find('#link-type-always').simulate('click'); + }); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + wrapper.update(); + wrapper.find('DataListRadio').simulate('click'); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + id: 1, + name: 'Test Job Template', + type: 'job_template', + url: '/api/v2/job_templates/1', + }, + 'always' + ); + }); + + test('Can successfully create a new project sync node', async () => { + act(() => { + wrapper.find('#link-type-failure').simulate('click'); + }); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')(null, 'project_sync'); + }); + wrapper.update(); + wrapper.find('DataListRadio').simulate('click'); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + id: 1, + name: 'Test Project', + type: 'project', + url: '/api/v2/projects/1', + }, + 'failure' + ); + }); + + test('Can successfully create a new inventory source sync node', async () => { + act(() => { + wrapper.find('#link-type-failure').simulate('click'); + }); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')( + null, + 'inventory_source_sync' + ); + }); + wrapper.update(); + wrapper.find('DataListRadio').simulate('click'); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + id: 1, + name: 'Test Inventory Source', + type: 'inventory_source', + url: '/api/v2/inventory_sources/1', + }, + 'failure' + ); + }); + + test('Can successfully create a new workflow job template node', async () => { + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')( + null, + 'workflow_job_template' + ); + }); + wrapper.update(); + wrapper.find('DataListRadio').simulate('click'); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + id: 1, + name: 'Test Workflow Job Template', + type: 'workflow_job_template', + url: '/api/v2/workflow_job_templates/1', + }, + 'success' + ); + }); + + test('Can successfully create a new approval template node', async () => { + act(() => { + wrapper.find('#link-type-always').simulate('click'); + }); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')(null, 'approval'); + }); + wrapper.update(); + + await act(async () => { + wrapper.find('input#approval-name').simulate('change', { + target: { value: 'Test Approval', name: 'name' }, + }); + wrapper.find('input#approval-description').simulate('change', { + target: { value: 'Test Approval Description', name: 'description' }, + }); + wrapper.find('input#approval-timeout-minutes').simulate('change', { + target: { value: 5, name: 'timeoutMinutes' }, + }); + }); + + // Updating the minutes and seconds is split to avoid a race condition. + // They both update the same state variable in the parent so triggering + // them syncronously creates flakey test results. + await act(async () => { + wrapper.find('input#approval-timeout-seconds').simulate('change', { + target: { value: 30, name: 'timeoutSeconds' }, + }); + }); + wrapper.update(); + + expect(wrapper.find('input#approval-name').prop('value')).toBe( + 'Test Approval' + ); + expect(wrapper.find('input#approval-description').prop('value')).toBe( + 'Test Approval Description' + ); + expect(wrapper.find('input#approval-timeout-minutes').prop('value')).toBe( + 5 + ); + expect(wrapper.find('input#approval-timeout-seconds').prop('value')).toBe( + 30 + ); + + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + description: 'Test Approval Description', + name: 'Test Approval', + timeout: 330, + type: 'workflow_approval_template', + }, + 'always' + ); + }); + + test('Cancel button dispatches as expected', () => { + wrapper.find('button#cancel-node-modal').simulate('click'); + expect(dispatch).toHaveBeenCalledWith({ + type: 'CANCEL_NODE_MODAL', + }); + }); + }); + describe('Edit existing node', () => { + afterEach(() => { + wrapper.unmount(); + }); + + test('Can successfully change project sync node to workflow approval node', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + + + ); + }); + expect(wrapper.find('AnsibleSelect').prop('value')).toBe('project_sync'); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')(null, 'approval'); + }); + wrapper.update(); + await act(async () => { + wrapper.find('input#approval-name').simulate('change', { + target: { value: 'Test Approval', name: 'name' }, + }); + wrapper.find('input#approval-description').simulate('change', { + target: { value: 'Test Approval Description', name: 'description' }, + }); + wrapper.find('input#approval-timeout-minutes').simulate('change', { + target: { value: 5, name: 'timeoutMinutes' }, + }); + }); + + // Updating the minutes and seconds is split to avoid a race condition. + // They both update the same state variable in the parent so triggering + // them syncronously creates flakey test results. + await act(async () => { + wrapper.find('input#approval-timeout-seconds').simulate('change', { + target: { value: 30, name: 'timeoutSeconds' }, + }); + }); + wrapper.update(); + + expect(wrapper.find('input#approval-name').prop('value')).toBe( + 'Test Approval' + ); + expect(wrapper.find('input#approval-description').prop('value')).toBe( + 'Test Approval Description' + ); + expect(wrapper.find('input#approval-timeout-minutes').prop('value')).toBe( + 5 + ); + expect(wrapper.find('input#approval-timeout-seconds').prop('value')).toBe( + 30 + ); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + + expect(onSave).toBeCalledWith( + { + description: 'Test Approval Description', + name: 'Test Approval', + timeout: 330, + type: 'workflow_approval_template', + }, + null + ); + }); + + test('Can successfully change approval node to workflow job template node', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + + + ); + }); + expect(wrapper.find('AnsibleSelect').prop('value')).toBe('approval'); + await act(async () => { + wrapper.find('AnsibleSelect').prop('onChange')( + null, + 'workflow_job_template' + ); + }); + wrapper.update(); + wrapper.find('DataListRadio').simulate('click'); + await act(async () => { + wrapper.find('button#next-node-modal').simulate('click'); + }); + expect(onSave).toBeCalledWith( + { + id: 1, + name: 'Test Workflow Job Template', + type: 'workflow_job_template', + url: '/api/v2/workflow_job_templates/1', + }, + null + ); + }); + }); +}); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.jsx index 046b2b4db8..43ae2b681e 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeNextButton.jsx @@ -18,6 +18,7 @@ function NodeNextButton({ return (