Repository: ProcessMaker/modeler
Pull request #1667 (2023-09-08 18:29:27)

FOUR-8066 Process Testing part 1
## Process Testing part 1

- Run a process in an isolated environment.
- Save scenarios
- Run scenarios
- Run processes from modeler



Patch:
diff --git a/src/components/modeler/Modeler.vue b/src/components/modeler/Modeler.vue
index 44641ea1..b2603fd8 100644
--- a/src/components/modeler/Modeler.vue
+++ b/src/components/modeler/Modeler.vue
@@ -2,6 +2,7 @@
   <span data-test="body-container">
     <tool-bar
       v-if="showComponent"
+      ref="tool-bar"
       :canvas-drag-position="canvasDragPosition"
       :cursor="cursor"
       :is-rendering="isRendering"
@@ -12,6 +13,7 @@
       :warnings="allWarnings"
       :xml-manager="xmlManager"
       :validationBar="validationBar"
+      :extra-actions="extraActions"
       @load-xml="loadXML"
       @toggle-panels-compressed="panelsCompressed = !panelsCompressed"
       @toggle-mini-map-open="miniMapOpen = $event"
@@ -21,6 +23,7 @@
       @close="close"
       @save-state="pushToUndoStack"
       @clearSelection="clearSelection"
+      @action="handleToolbarAction"
     />
     <b-row class="modeler h-100">
       <b-tooltip
@@ -242,6 +245,7 @@ export default {
   mixins: [hotkeys, cloneSelection],
   data() {
     return {
+      extraActions: [],
       pasteInProgress: false,
       cloneInProgress: false,
       internalClipboard: [],
@@ -354,6 +358,11 @@ export default {
     showComponent: () => store.getters.showComponent,
   },
   methods: {
+    handleToolbarAction(action) {
+      if (action.handler instanceof Function) {
+        action.handler(this);
+      }
+    },
     handleToggleInspector(value) {
       this.isOpenInspector = value;
     },
@@ -689,6 +698,15 @@ export default {
       });
       nodeTypesStore.commit('setPmBlockNodeTypes', this.pmBlockNodes);
     },
+    registerMenuAction(action) {
+      if (!action.value || typeof action.value !== 'string') {
+        throw new Error('Menu action must have a action.value');
+      }
+      if (!action.content || typeof action.content !== 'string') {
+        throw new Error('Menu action must have a action.content');
+      }
+      this.extraActions.push(action);
+    },
     addMessageFlows() {
       if (this.collaboration) {
         this.collaboration
@@ -1330,6 +1348,9 @@ export default {
     this.registerNode(Process);
     /* Initialize the BpmnModdle and its extensions */
     window.ProcessMaker.EventBus.$emit('modeler-init', {
+      $t: this.$t,
+      modeler: this,
+      registerMenuAction: this.registerMenuAction,
       registerInspectorExtension,
       registerBpmnExtension: this.registerBpmnExtension,
       registerNode: this.registerNode,
@@ -1464,6 +1485,9 @@ export default {
 
     /* Register custom nodes */
     window.ProcessMaker.EventBus.$emit('modeler-start', {
+      $t: this.$t,
+      modeler: this,
+      registerMenuAction: this.registerMenuAction,
       loadXML: async(xml) => {
         await this.loadXML(xml);
         await undoRedoStore.dispatch('pushState', xml);
diff --git a/src/components/nodes/sequenceFlow/sequenceFlow.vue b/src/components/nodes/sequenceFlow/sequenceFlow.vue
index 0b3b4e3f..54c976d0 100644
--- a/src/components/nodes/sequenceFlow/sequenceFlow.vue
+++ b/src/components/nodes/sequenceFlow/sequenceFlow.vue
@@ -20,6 +20,7 @@ import { shapes } from 'jointjs';
 import linkConfig from '@/mixins/linkConfig';
 import get from 'lodash/get';
 import { namePosition } from './sequenceFlowConfig';
+import highlightConfig from '@/mixins/highlightConfig';
 import CrownConfig from '@/components/crown/crownConfig/crownConfig';
 import SequenceFlow from '@/components/nodes/genericFlow/SequenceFlow';
 
@@ -40,7 +41,7 @@ export default {
     'planeElements',
     'isRendering',
   ],
-  mixins: [linkConfig],
+  mixins: [highlightConfig, linkConfig],
   data() {
     return {
       shape: null,
diff --git a/src/components/toolbar/ToolBar.vue b/src/components/toolbar/ToolBar.vue
index f6aab8e3..d9d9207d 100644
--- a/src/components/toolbar/ToolBar.vue
+++ b/src/components/toolbar/ToolBar.vue
@@ -56,7 +56,7 @@
               {{ $t('Close') }}
             </a>
             <EllipsisMenu
-              :actions="ellipsisMenuActions"
+              :actions="combinedMenuActions"
               :divider="false"
               @navigate="onNavigate"
               @show="onShow"
@@ -129,6 +129,7 @@ export default {
     'warnings',
     'xmlManager',
     'validationBar',
+    'extraActions',
   ],
   watch: {
     miniMapOpen(isOpen) {
@@ -146,6 +147,9 @@ export default {
     },
   },
   computed: {
+    combinedMenuActions() {
+      return this.extraActions ? this.ellipsisMenuActions.concat(this.extraActions) : this.ellipsisMenuActions;
+    },
     canUndo() {
       return undoRedoStore.getters.canUndo;
     },
@@ -237,7 +241,7 @@ export default {
           this.$emit('publishPmBlock');
           break;
         default:
-          break;
+          this.$emit('action', action);
       }
     },
     onShow() {
diff --git a/src/mixins/highlightConfig.js b/src/mixins/highlightConfig.js
index 3991cba3..6e40360a 100644
--- a/src/mixins/highlightConfig.js
+++ b/src/mixins/highlightConfig.js
@@ -113,7 +113,7 @@ export default {
       return this.shape.findView(this.paperManager.paper);
     },
     shapeBody() {
-      return this.shapeView.$el.find('[joint-selector=body]');
+      return this.shapeView.$el.find('[joint-selector=body]') || this.shapeView.$el;
     },
   },
   methods: {


Test patch:
diff --git a/tests/unit/components/toolbar/toolbarExtraMenu.spec.js b/tests/unit/components/toolbar/toolbarExtraMenu.spec.js
new file mode 100644
index 00000000..723fa9a8
--- /dev/null
+++ b/tests/unit/components/toolbar/toolbarExtraMenu.spec.js
@@ -0,0 +1,61 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Toolbar from '@/components/toolbar/ToolBar';
+import BootstrapVue from 'bootstrap-vue';
+
+describe('Toolbar.vue', () => {
+  const localVue = createLocalVue();
+  localVue.use(BootstrapVue);
+
+  let wrapper;
+  const mocks = {
+    $t(label) { return label; },
+  };
+  const stubs = ['b-row', 'b-modal', 'b-btn', 'b-tooltip'];
+  const propsData = {
+    canvasDragPosition: {}, // add the expected value or mock data
+    cursor: {},             // add the expected value or mock data
+    paperManager: {},       // add the expected value or mock data
+    isRendering: {},        // add the expected value or mock data
+    breadcrumbData: [],     // add the expected value or mock data
+    panelsCompressed: {},   // add the expected value or mock data
+    validationErrors: {},   // add the expected value or mock data
+    warnings: [],           // add the expected value or mock data
+    xmlManager: {},         // add the expected value or mock data
+    validationBar: {},      // add the expected value or mock data
+  };
+
+
+  describe('combinedMenuActions computed property', () => {
+    it('should combine ellipsisMenuActions and extraActions when extraActions are provided', () => {
+      const extraActions = [
+        {
+          value: 'mock-action',
+          content: 'Mock Action',
+          icon: '',
+        },
+      ];
+      wrapper = shallowMount(Toolbar, {
+        localVue,
+        propsData: {
+          ...propsData,
+          extraActions,
+        },
+        mocks,
+        stubs,
+      });
+
+      expect(wrapper.vm.combinedMenuActions).toEqual([...wrapper.vm.ellipsisMenuActions, ...extraActions]);
+    });
+
+    it('should only return ellipsisMenuActions when no extraActions are provided', () => {
+      wrapper = shallowMount(Toolbar, {
+        localVue,
+        propsData,
+        mocks,
+        stubs,
+      });
+
+      expect(wrapper.vm.combinedMenuActions).toEqual(wrapper.vm.ellipsisMenuActions);
+    });
+  });
+});