Repository: ProcessMaker/modeler
Pull request #1009 (2020-01-08 21:07:02)

Extract node class and responsibilities
This PR introduces a node class and moves code into that class. There is much more that can be done with this but I wanted to open this PR before continuing as there's a danger that there wold be just too much code to review.


Patch:
diff --git a/src/boundaryEventValidation.js b/src/boundaryEventValidation.js
index 02742326..eb31d9df 100644
--- a/src/boundaryEventValidation.js
+++ b/src/boundaryEventValidation.js
@@ -6,13 +6,9 @@ export function canAddBoundaryEventToTarget(boundaryEventType, dropTarget) {
     return false;
   }
 
-  if (getEmptyBoundaryEventPositionsForShape(dropTarget).length === 0) {
-    return false;
-  }
-
-  return true;
+  return getEmptyBoundaryEventPositionsForShape(dropTarget).length !== 0;
 }
 
 export function isValidBoundaryEscalationEvent(component) {
-  return component && component.node.definition.$type === 'bpmn:CallActivity';
+  return component && component.node.isBpmnType('bpmn:CallActivity');
 }
diff --git a/src/components/crown/crownButtons/associationFlowButton.vue b/src/components/crown/crownButtons/associationFlowButton.vue
index 98e772f7..5f3013a4 100644
--- a/src/components/crown/crownButtons/associationFlowButton.vue
+++ b/src/components/crown/crownButtons/associationFlowButton.vue
@@ -1,6 +1,6 @@
 <template>
   <crown-button
-    v-if="node.type === 'processmaker-modeler-text-annotation'"
+    v-if="node.isType('processmaker-modeler-text-annotation')"
     :title="$t('Association Flow')"
     v-b-tooltip.hover.viewport.d50="{ customClass: 'no-pointer-events' }"
     id="association-flow-button"
@@ -15,6 +15,7 @@
 import connectIcon from '@/assets/connect-artifacts.svg';
 import CrownButton from '@/components/crown/crownButtons/crownButton';
 import { direction } from '@/components/nodes/association/associationConfig';
+import Node from '@/components/nodes/node';
 
 export default {
   components: { CrownButton },
@@ -33,11 +34,11 @@ export default {
         associationDirection: direction.none,
       });
 
-      this.$emit('add-node', {
-        type: 'processmaker-modeler-association',
-        definition: associationLink,
-        diagram: this.moddle.create('bpmndi:BPMNEdge'),
-      });
+      this.$emit('add-node', new Node(
+        'processmaker-modeler-association',
+        associationLink,
+        this.moddle.create('bpmndi:BPMNEdge'),
+      ));
     },
   },
 };
diff --git a/src/components/crown/crownButtons/crownBoundaryEventDropdown.vue b/src/components/crown/crownButtons/crownBoundaryEventDropdown.vue
index 88a5ac38..f6cfce5d 100644
--- a/src/components/crown/crownButtons/crownBoundaryEventDropdown.vue
+++ b/src/components/crown/crownButtons/crownBoundaryEventDropdown.vue
@@ -40,6 +40,7 @@ import boundaryEventIcon from '@/assets/boundary-event.svg';
 import { getEmptyBoundaryEventPositionsForShape } from '@/portsUtils';
 import { canAddBoundaryEventToTarget } from '@/boundaryEventValidation';
 import store from '@/store';
+import Node from '@/components/nodes/node';
 
 export default {
   name: 'CrownBoundaryEventDropdown',
@@ -82,11 +83,7 @@ export default {
       diagram.bounds.x = emptyPort.x - (diagram.bounds.width / 2);
       diagram.bounds.y = emptyPort.y - (diagram.bounds.height / 2);
 
-      const node = {
-        definition,
-        diagram,
-        type: nodeType,
-      };
+      const node = new Node(nodeType, definition, diagram);
 
       store.commit('highlightNode', node);
 
diff --git a/src/components/crown/crownButtons/deleteButton.vue b/src/components/crown/crownButtons/deleteButton.vue
index 74b1e802..52f27342 100644
--- a/src/components/crown/crownButtons/deleteButton.vue
+++ b/src/components/crown/crownButtons/deleteButton.vue
@@ -21,6 +21,7 @@
 <script>
 import trashIcon from '@/assets/trash-alt-solid.svg';
 import CrownButton from '@/components/crown/crownButtons/crownButton';
+import { removeFlows } from '@/components/crown/utils.js';
 
 export default {
   components: { CrownButton },
@@ -36,16 +37,9 @@ export default {
     },
   },
   methods: {
+    removeFlows,
     removeShape() {
-      this.graph.getConnectedLinks(this.shape).forEach(shape => this.$emit('remove-node', shape.component.node));
-      this.shape.getEmbeddedCells({ deep: true }).forEach(cell => {
-        if (cell.component) {
-          this.graph.getConnectedLinks(cell).forEach(shape => this.$emit('remove-node', shape.component.node));
-          this.shape.unembed(cell);
-          this.$emit('remove-node', cell.component.node);
-        }
-      });
-
+      this.removeFlows(this.graph, this.shape);
       this.$emit('remove-node', this.node);
     },
     removePoolLaneShape() {
diff --git a/src/components/crown/crownButtons/messageFlowButton.vue b/src/components/crown/crownButtons/messageFlowButton.vue
index 4def9671..2b0e54c7 100644
--- a/src/components/crown/crownButtons/messageFlowButton.vue
+++ b/src/components/crown/crownButtons/messageFlowButton.vue
@@ -13,6 +13,7 @@
 <script>
 import messageFlow from '@/assets/message-flow.svg';
 import CrownButton from '@/components/crown/crownButtons/crownButton';
+import Node from '@/components/nodes/node';
 
 export default {
   components: { CrownButton },
@@ -39,11 +40,11 @@ export default {
         targetRef: { x, y },
       });
 
-      this.$emit('add-node', {
-        type: 'processmaker-modeler-message-flow',
-        definition: messageFlowDefinition,
-        diagram: this.moddle.create('bpmndi:BPMNEdge'),
-      });
+      this.$emit('add-node', new Node(
+        'processmaker-modeler-message-flow',
+        messageFlowDefinition,
+        this.moddle.create('bpmndi:BPMNEdge'),
+      ));
     },
   },
 };
diff --git a/src/components/crown/crownButtons/sequenceFlowButton.vue b/src/components/crown/crownButtons/sequenceFlowButton.vue
index e173499f..1fb4f6da 100644
--- a/src/components/crown/crownButtons/sequenceFlowButton.vue
+++ b/src/components/crown/crownButtons/sequenceFlowButton.vue
@@ -13,6 +13,7 @@
 <script>
 import sequenceFlow from '@/assets/connect-elements.svg';
 import CrownButton from '@/components/crown/crownButtons/crownButton';
+import Node from '@/components/nodes/node';
 
 export default {
   components: { CrownButton },
@@ -48,11 +49,11 @@ export default {
         });
       }
 
-      this.$emit('add-node', {
-        type: 'processmaker-modeler-sequence-flow',
-        definition: sequenceLink,
-        diagram: this.sequenceFlowConfig.diagram(this.moddle),
-      });
+      this.$emit('add-node', new Node(
+        'processmaker-modeler-sequence-flow',
+        sequenceLink,
+        this.sequenceFlowConfig.diagram(this.moddle),
+      ));
     },
   },
   created() {
diff --git a/src/components/crown/crownConfig/crownConfig.vue b/src/components/crown/crownConfig/crownConfig.vue
index 8bd81125..e75e9be5 100644
--- a/src/components/crown/crownConfig/crownConfig.vue
+++ b/src/components/crown/crownConfig/crownConfig.vue
@@ -34,7 +34,7 @@
       :moddle="moddle"
       :shape="shape"
       :task-dropdown-initially-open="taskDropdownInitiallyOpen"
-      @replace-node-type="confirmReplace"
+      @replace-node-type="replaceNodeTypePrompt"
       v-on="$listeners"
     />
 
@@ -53,7 +53,7 @@
       :cancel-title="$t('Cancel')"
       v-model="showReplaceModal"
       @hidden="showReplaceModal = false"
-      @ok="$emit('replace-node', nodeToReplace)"
+      @ok="confirmedReplaceNodeType"
     >
       <p>{{ $t('Changing this type will replace your current configuration') }}</p>
     </b-modal>
@@ -67,6 +67,7 @@ import SequenceFlowButton from '@/components/crown/crownButtons/sequenceFlowButt
 import AssociationFlowButton from '@/components/crown/crownButtons/associationFlowButton';
 import CrownDropdowns from '@/components/crown/crownButtons/crownDropdowns';
 import poolLaneCrownConfig from '@/mixins/poolLaneCrownConfig';
+import { removeFlows } from '@/components/crown/utils.js';
 import pull from 'lodash/pull';
 import store from '@/store';
 
@@ -118,7 +119,7 @@ export default {
       showCrown: false,
       savePositionOnPointerupEventSet: false,
       style: null,
-      taskDropdownInitiallyOpen: true,
+      taskDropdownInitiallyOpen: this.paperNotRendered(),
       showReplaceModal: false,
       nodeToReplace: null,
     };
@@ -143,7 +144,11 @@ export default {
     },
   },
   methods: {
-    confirmReplace(node) {
+    paperNotRendered() {
+      return !this.isRendering;
+    },
+    removeFlows,
+    replaceNodeTypePrompt(node) {
       if (this.taskDropdownInitiallyOpen) {
         this.$emit('replace-node', node);
         return;
@@ -151,6 +156,10 @@ export default {
       this.showReplaceModal = true;
       this.nodeToReplace = node;
     },
+    confirmedReplaceNodeType() {
+      this.removeFlows(this.graph, this.shape);
+      this.$emit('replace-node', this.nodeToReplace);
+    },
     setNodePosition() {
       this.shape.stopListening(this.paper, 'element:pointerup', this.setNodePosition);
       this.savePositionOnPointerupEventSet = false;
diff --git a/src/components/crown/utils.js b/src/components/crown/utils.js
new file mode 100644
index 00000000..4cbd43a2
--- /dev/null
+++ b/src/components/crown/utils.js
@@ -0,0 +1,10 @@
+export function removeFlows(graph, shape) {
+  graph.getConnectedLinks(shape).forEach(shape => this.$emit('remove-node', shape.component.node));
+  shape.getEmbeddedCells({ deep: true })
+    .filter(cell => cell.component)
+    .forEach(cell => {
+      graph.getConnectedLinks(cell).forEach(shape => this.$emit('remove-node', shape.component.node));
+      shape.unembed(cell);
+      this.$emit('remove-node', cell.component.node);
+    });
+}
diff --git a/src/components/modeler/Modeler.vue b/src/components/modeler/Modeler.vue
index 822095e8..fb997088 100644
--- a/src/components/modeler/Modeler.vue
+++ b/src/components/modeler/Modeler.vue
@@ -64,9 +64,9 @@
         :graph="graph"
         :paper="paper"
         :node="node"
-        :id="node.definition.id"
+        :id="node.id"
         :highlighted="highlightedNode === node"
-        :has-error="invalidNodes.includes(node.definition.id)"
+        :has-error="invalidNodes.includes(node.id)"
         :collaboration="collaboration"
         :process-node="processNode"
         :processes="processes"
@@ -121,15 +121,11 @@ import PaperManager from '../paperManager';
 import registerInspectorExtension from '@/components/InspectorExtensionManager';
 
 import initAnchor from '@/mixins/linkManager.js';
-import {
-  addIdToNodeAndSetUpDiagramReference,
-  addNodeToProcess,
-  getTargetProcess,
-  isBoundaryEvent,
-} from '@/components/nodeManager';
 import ensureShapeIsNotCovered from '@/components/shapeStackUtils';
 import ToolBar from '@/components/toolbar/ToolBar';
 import moveShapeByKeypress from '@/components/modeler/moveWithArrowKeys';
+import Node from '@/components/nodes/node';
+import { addNodeToProcess } from '@/components/nodeManager';
 
 const version = '1.0';
 
@@ -448,11 +444,11 @@ export default {
       this.processes = this.getProcesses();
       this.plane = this.getPlane();
       this.planeElements = this.getPlaneElements();
-      this.processNode = {
-        type: 'processmaker-modeler-process',
-        definition: this.processes[0],
-        diagram: this.planeElements.find(diagram => diagram.bpmnElement.id === this.processes[0].id),
-      };
+      this.processNode = new Node(
+        'processmaker-modeler-process',
+        this.processes[0],
+        this.planeElements.find(diagram => diagram.bpmnElement.id === this.processes[0].id),
+      );
     },
     removeUnsupportedElementAttributes(definition) {
       const unsupportedElements = ['documentation', 'extensionElements'];
@@ -511,11 +507,7 @@ export default {
         definition.set('name', '');
       }
 
-      store.commit('addNode', {
-        type,
-        definition,
-        diagram,
-      });
+      store.commit('addNode', new Node(type, definition, diagram));
     },
     hasSourceAndTarget(definition) {
       const hasSource = definition.sourceRef && this.parsers[definition.sourceRef.$type];
@@ -599,9 +591,9 @@ export default {
       diagram.bounds.x = x;
       diagram.bounds.y = y;
 
-      const node = { type: control.type, definition, diagram };
+      const node = new Node(control.type, definition, diagram);
 
-      if (isBoundaryEvent(node)) {
+      if (node.isBpmnType('bpmn:BoundaryEvent')) {
         this.setShapeCenterUnderCursor(diagram);
       }
 
@@ -615,9 +607,9 @@ export default {
     addNode(node) {
       node.pool = this.poolTarget;
 
-      const targetProcess = getTargetProcess(node, this.processes, this.processNode);
+      const targetProcess = node.getTargetProcess(this.processes, this.processNode);
       addNodeToProcess(node, targetProcess);
-      addIdToNodeAndSetUpDiagramReference(node, this.nodeIdGenerator);
+      node.setIds(this.nodeIdGenerator);
 
       this.planeElements.push(node.diagram);
 
diff --git a/src/components/nodeManager.js b/src/components/nodeManager.js
index 4d0d62df..0210a984 100644
--- a/src/components/nodeManager.js
+++ b/src/components/nodeManager.js
@@ -1,25 +1,12 @@
 import { id as poolId } from './nodes/pool';
 import { id as laneId } from '@/components/nodes/poolLane';
 
-export function addIdToNodeAndSetUpDiagramReference(node, nodeIdGenerator) {
-  const [nodeId, diagramId] = nodeIdGenerator.generate();
-
-  if (!node.definition.id) {
-    node.definition.id = nodeId;
-  }
-
-  if (node.diagram) {
-    node.diagram.id = diagramId;
-    node.diagram.bpmnElement = node.definition;
-  }
-}
-
 export function addNodeToProcess(node, targetProcess) {
-  if (node.type === poolId || node.definition.$type === 'bpmn:MessageFlow') {
+  if (node.isType(poolId) || node.isBpmnType('bpmn:MessageFlow')) {
     return;
   }
 
-  if (node.type === laneId) {
+  if (node.isType(laneId)) {
     targetProcess
       .get('laneSets')[0]
       .get('lanes')
@@ -27,20 +14,10 @@ export function addNodeToProcess(node, targetProcess) {
     return;
   }
 
-  if (node.definition.$type === 'bpmn:TextAnnotation' || node.definition.$type === 'bpmn:Association') {
-    targetProcess.get('artifacts').push(node.definition);
-    return;
+  let target = 'flowElements';
+  if (node.isBpmnType('bpmn:TextAnnotation') || node.isBpmnType('bpmn:Association')) {
+    target = 'artifacts';
   }
 
-  targetProcess.get('flowElements').push(node.definition);
-}
-
-export function getTargetProcess(node, processes, processNode) {
-  return node.pool
-    ? processes.find(({id}) => id === node.pool.component.node.definition.get('processRef').id)
-    : processNode.definition;
-}
-
-export function isBoundaryEvent(node) {
-  return node.definition.$type === 'bpmn:BoundaryEvent';
+  targetProcess.get(target).push(node.definition);
 }
diff --git a/src/components/nodes/association/association.vue b/src/components/nodes/association/association.vue
index 0aed38a5..360ba7fe 100644
--- a/src/components/nodes/association/association.vue
+++ b/src/components/nodes/association/association.vue
@@ -11,9 +11,7 @@
     :process-node="processNode"
     :plane-elements="planeElements"
     :is-rendering="isRendering"
-    @remove-node="$emit('remove-node', $event)"
-    @add-node="$emit('add-node', $event)"
-    @save-state="$emit('save-state', $event)"
+    v-on="$listeners"
   />
 </template>
 
diff --git a/src/components/nodes/boundaryEvent/validBoundaryEventTargets.js b/src/components/nodes/boundaryEvent/validBoundaryEventTargets.js
index 03018c0f..ebb78274 100644
--- a/src/components/nodes/boundaryEvent/validBoundaryEventTargets.js
+++ b/src/components/nodes/boundaryEvent/validBoundaryEventTargets.js
@@ -11,5 +11,5 @@ const validBoundaryEventTargets = [
 ];
 
 export default function isValidBoundaryEventTarget(component) {
-  return component && validBoundaryEventTargets.includes(component.node.definition.$type);
+  return component && validBoundaryEventTargets.some(targetType => component.node.isBpmnType(targetType));
 }
diff --git a/src/components/nodes/boundaryMessageEvent/boundaryMessageEvent.vue b/src/components/nodes/boundaryMessageEvent/boundaryMessageEvent.vue
index 6793580d..e48c2f4e 100644
--- a/src/components/nodes/boundaryMessageEvent/boundaryMessageEvent.vue
+++ b/src/components/nodes/boundaryMessageEvent/boundaryMessageEvent.vue
@@ -8,7 +8,7 @@ export default {
   methods: {
     isValidBoundaryEventTarget(model) {
       const component = model.component;
-      return component && component.node.definition.$type === 'bpmn:CallActivity';
+      return component && component.node.isBpmnType('bpmn:CallActivity');
     },
   },
   mounted() {
diff --git a/src/components/nodes/messageFlow/messageFlow.vue b/src/components/nodes/messageFlow/messageFlow.vue
index 8ae17cd2..1fe747a3 100644
--- a/src/components/nodes/messageFlow/messageFlow.vue
+++ b/src/components/nodes/messageFlow/messageFlow.vue
@@ -75,8 +75,6 @@ export default {
         this.allowOutgoingFlow();
     },
     targetIsValidType() {
-      const targetBpmnType = this.targetNode.definition.$type;
-
       return [
         'bpmn:Task',
         'bpmn:ScriptTask',
@@ -85,7 +83,7 @@ export default {
         'bpmn:ServiceTask',
         'bpmn:IntermediateCatchEvent',
         'bpmn:Participant',
-      ].includes(targetBpmnType);
+      ].some(type => this.targetNode.isBpmnType(type));
     },
     hasTargetType() {
       return this.targetType != null;
@@ -106,7 +104,7 @@ export default {
       return sourcePool != null && sourcePool !== targetPool;
     },
     targetIsNotSource() {
-      return this.targetNode.definition.id !== this.sourceNode.definition.id;
+      return this.targetNode.id !== this.sourceNode.id;
     },
     allowOutgoingFlow() {
       return typeof this.sourceConfig.allowOutgoingFlow === 'undefined' ||
diff --git a/src/components/nodes/node.js b/src/components/nodes/node.js
new file mode 100644
index 00000000..1186e374
--- /dev/null
+++ b/src/components/nodes/node.js
@@ -0,0 +1,47 @@
+export default class Node {
+  type;
+  definition;
+  diagram;
+  pool;
+
+  constructor(type, definition, diagram) {
+    this.type = type;
+    this.definition = definition;
+    this.diagram = diagram;
+  }
+
+  isBpmnType(type) {
+    return this.definition.$type === type;
+  }
+
+  isType(type) {
+    return this.type === type;
+  }
+
+  get id() {
+    return this.definition.id;
+  }
+
+  set id(id) {
+    this.definition.id = id;
+  }
+
+  setIds(nodeIdGenerator) {
+    const [nodeId, diagramId] = nodeIdGenerator.generate();
+
+    if (!this.id) {
+      this.id = nodeId;
+    }
+
+    if (this.diagram) {
+      this.diagram.id = diagramId;
+      this.diagram.bpmnElement = this.definition;
+    }
+  }
+
+  getTargetProcess(processes, processNode) {
+    return this.pool
+      ? processes.find(({ id }) => id === this.pool.component.node.definition.get('processRef').id)
+      : processNode.definition;
+  }
+}
diff --git a/src/components/nodes/pool/pool.vue b/src/components/nodes/pool/pool.vue
index be1db43b..cfd87afb 100644
--- a/src/components/nodes/pool/pool.vue
+++ b/src/components/nodes/pool/pool.vue
@@ -43,6 +43,7 @@ import AddLaneAboveButton from '@/components/crown/crownButtons/addLaneAboveButt
 import AddLaneBelowButton from '@/components/crown/crownButtons/addLaneBelowButton';
 import { configurePool } from '@/components/nodes/pool/poolUtils';
 import PoolEventHandlers from '@/components/nodes/pool/poolEventHandlers';
+import Node from '@/components/nodes/node';
 
 export default {
   components: {
@@ -189,16 +190,16 @@ export default {
       const diagram = Lane.diagram(this.moddle);
       diagram.bounds.width = this.shape.getBBox().width;
 
-      this.$emit('add-node', {
-        type: Lane.id,
+      this.$emit('add-node', new Node(
+        Lane.id,
         definition,
         diagram,
-      });
+      ));
 
       return this.$nextTick();
     },
     addToPool(element) {
-      if (element.component.node.definition.$type === 'bpmn:BoundaryEvent') {
+      if (element.component.node.isBpmnType('bpmn:BoundaryEvent')) {
         return;
       }
       this.shape.unembed(element);
@@ -386,7 +387,7 @@ export default {
     },
     captureChildren() {
       this.graph.getElements().filter(({ component }) => component && component !== this).forEach(({ component }) => {
-        if (component.node.definition.$type === 'bpmn:BoundaryEvent') {
+        if (component.node.isBpmnType('bpmn:BoundaryEvent')) {
           return;
         }
         this.shape.embed(component.shape);
diff --git a/src/components/nodes/pool/poolUtils.js b/src/components/nodes/pool/poolUtils.js
index 4fac454d..044061f0 100644
--- a/src/components/nodes/pool/poolUtils.js
+++ b/src/components/nodes/pool/poolUtils.js
@@ -23,7 +23,7 @@ function addAllElementsToPool(graph, pool) {
   graph
     .getElements()
     .filter((shape) => shape !== pool)
-    .filter(({ component }) => component.node.definition.$type !== 'bpmn:BoundaryEvent')
+    .filter(({ component }) => component.node.isBpmnType('bpmn:BoundaryEvent'))
     .forEach(({ component }) => {
       pool.embed(component.shape);
       component.node.pool = pool;
diff --git a/src/components/nodes/sequenceFlow/sequenceFlow.vue b/src/components/nodes/sequenceFlow/sequenceFlow.vue
index 9bcf0733..3689d066 100644
--- a/src/components/nodes/sequenceFlow/sequenceFlow.vue
+++ b/src/components/nodes/sequenceFlow/sequenceFlow.vue
@@ -120,8 +120,8 @@ export default {
         this.eventBasedGatewayTarget();
     },
     eventBasedGatewayTarget() {
-      const isSourceEventBasedGateway = this.sourceNode.definition.$type === 'bpmn:EventBasedGateway';
-      const isTargetIntermediateCatchEvent = this.targetNode.definition.$type === 'bpmn:IntermediateCatchEvent';
+      const isSourceEventBasedGateway = this.sourceNode.isBpmnType('bpmn:EventBasedGateway');
+      const isTargetIntermediateCatchEvent = this.targetNode.isBpmnType('bpmn:IntermediateCatchEvent');
 
       return !isSourceEventBasedGateway || isTargetIntermediateCatchEvent;
     },
@@ -138,7 +138,7 @@ export default {
       return !sourcePool || sourcePool === targetPool;
     },
     targetIsNotSource() {
-      return this.targetNode.definition.id !== this.sourceNode.definition.id;
+      return this.targetNode.id !== this.sourceNode.id;
     },
     allowOutgoingFlow() {
       return this.sourceConfig.allowOutgoingFlow == null ||
diff --git a/src/portsUtils.js b/src/portsUtils.js
index 8304a102..02c063d2 100644
--- a/src/portsUtils.js
+++ b/src/portsUtils.js
@@ -42,7 +42,7 @@ export function getEmptyBoundaryEventPositionsForShape(model) {
   const allPortPoints = getModelPortPoints(model, boundaryGroup);
   const boundaryEventPositions = model
     .getEmbeddedCells()
-    .filter(shape => shape.component && shape.component.node.definition.$type === 'bpmn:BoundaryEvent')
+    .filter(shape => shape.component && shape.component.node.isBpmnType('bpmn:BoundaryEvent'))
     .map(getShapeCenterPosition);
 
   return differenceWith(allPortPoints, boundaryEventPositions, isEqual);
diff --git a/src/setup/registerNodes.js b/src/setup/registerNodes.js
index f4d1fd02..542d94c8 100644
--- a/src/setup/registerNodes.js
+++ b/src/setup/registerNodes.js
@@ -31,6 +31,7 @@ import {
 import bpmnExtension from '@processmaker/processmaker-bpmn-moddle/resources/processmaker';
 
 const nodeTypes = [
+  startEvent,
   endEvent,
   task,
   scriptTask,
@@ -73,7 +74,6 @@ const customParserFactory = (nodeType, primaryIdentifier, secondaryIdentifier) =
 };
 
 export default function registerNodes({ registerNode, registerBpmnExtension }) {
-  registerNode(startEvent);
   customEventNodes.forEach(([nodeType, primaryIdentifier, secondaryIdentifier]) => {
     registerNode(nodeType, customParserFactory(nodeType, primaryIdentifier, secondaryIdentifier));
   });


Test patch:
diff --git a/tests/e2e/specs/MessageFlows.spec.js b/tests/e2e/specs/MessageFlows.spec.js
index 045cd98b..67aedca1 100644
--- a/tests/e2e/specs/MessageFlows.spec.js
+++ b/tests/e2e/specs/MessageFlows.spec.js
@@ -6,7 +6,10 @@ import {
   getLinksConnectedToElement,
   getNumberOfLinks,
   isElementCovered,
+  modalConfirm,
   moveElement,
+  removeIndentationAndLinebreaks,
+  waitToRenderAllShapes,
 } from '../support/utils';
 import { nodeTypes } from '../support/constants';
 
@@ -142,4 +145,47 @@ describe('Message Flows', () => {
       .then($links => $links[0])
       .then(isElementCovered).should(isCovered => expect(isCovered).to.be.false);
   });
+
+  it('Removes message flows when switching task type', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const pool1Position = { x: 250, y: 250 };
+    const pool2Position = { x: 250, y: 500 };
+    const offset = 100;
+    const taskPosition = { x: pool2Position.x + offset, y: pool2Position.y + offset };
+    let numberOfMessageFlowsAdded = 1;
+
+    const messageFlow = '<bpmn:messageFlow id="node_5" name="" sourceRef="node_1" targetRef="node_4"';
+
+    dragFromSourceToDest(nodeTypes.pool, pool1Position);
+    dragFromSourceToDest(nodeTypes.pool, pool2Position);
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    connectNodesWithFlow('message-flow-button', startEventPosition, taskPosition);
+
+    getElementAtPosition(taskPosition).then(getLinksConnectedToElement).should($links => {
+      expect($links.length).to.eq(numberOfMessageFlowsAdded);
+    });
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.contain(messageFlow);
+    });
+
+    waitToRenderAllShapes();
+
+    cy.get('[data-test=select-type-dropdown]').click();
+    cy.get('[data-test=switch-to-manual-task]').click();
+    modalConfirm();
+
+    waitToRenderAllShapes();
+
+    getElementAtPosition(taskPosition).then(getLinksConnectedToElement).should($links => {
+      expect($links.length).to.eq(--numberOfMessageFlowsAdded);
+    });
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.not.contain(messageFlow);
+    });
+  });
 });
diff --git a/tests/e2e/specs/SequenceFlows.spec.js b/tests/e2e/specs/SequenceFlows.spec.js
index 640f59ea..e9a29cab 100644
--- a/tests/e2e/specs/SequenceFlows.spec.js
+++ b/tests/e2e/specs/SequenceFlows.spec.js
@@ -1,9 +1,12 @@
 import {
+  addNodeTypeToPaper,
   connectNodesWithFlow,
   dragFromSourceToDest,
   getElementAtPosition,
   getLinksConnectedToElement,
+  modalConfirm,
   moveElement,
+  removeIndentationAndLinebreaks,
   typeIntoTextInput,
   waitToRenderAllShapes,
 } from '../support/utils';
@@ -259,4 +262,43 @@ describe('Sequence Flows', () => {
       then($el => $el.find('[joint-selector="body"]')).
       should('have.attr', 'fill', endColor);
   });
+
+  it('Removes sequence flows when switching task type', () => {
+    const startPosition = { x: 150, y: 150 };
+    const taskPosition = { x: 250, y: 250 };
+    let numberOfSequenceFlowsAdded = 1;
+
+    const sequenceFlow = '<bpmn:sequenceFlow id="node_4" name="New Sequence Flow" sourceRef="node_1" targetRef="node_3"';
+
+    addNodeTypeToPaper(taskPosition, nodeTypes.task, 'switch-to-script-task');
+    getElementAtPosition(taskPosition).getType().should('equal', nodeTypes.scriptTask);
+
+    connectNodesWithFlow('sequence-flow-button', startPosition, taskPosition);
+
+    getElementAtPosition(taskPosition).then(getLinksConnectedToElement).should($links => {
+      expect($links.length).to.eq(numberOfSequenceFlowsAdded);
+    });
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.contain(sequenceFlow);
+    });
+
+    waitToRenderAllShapes();
+
+    cy.get('[data-test=select-type-dropdown]').click();
+    cy.get('[data-test=switch-to-manual-task]').click();
+    modalConfirm();
+
+    waitToRenderAllShapes();
+
+    getElementAtPosition(taskPosition).then(getLinksConnectedToElement).should($links => {
+      expect($links.length).to.eq(--numberOfSequenceFlowsAdded);
+    });
+
+    cy.get('[data-test=downloadXMLBtn]').click();
+    cy.window().its('xml').then(removeIndentationAndLinebreaks).then(xml => {
+      expect(xml).to.not.contain(sequenceFlow);
+    });
+  });
 });
diff --git a/tests/unit/nodeManager.spec.js b/tests/unit/nodeManager.spec.js
index ab9d275e..cf24ad41 100644
--- a/tests/unit/nodeManager.spec.js
+++ b/tests/unit/nodeManager.spec.js
@@ -1,58 +1,59 @@
-import { addIdToNodeAndSetUpDiagramReference, addNodeToProcess, getTargetProcess } from '@/components/nodeManager';
+import { addNodeToProcess } from '@/components/nodeManager';
 import { id as poolId } from '@/components/nodes/pool';
 import { id as laneId } from '@/components/nodes/poolLane';
+import Node from '@/components/nodes/node';
 
 describe('nodeManager', () => {
-  describe('addIdToNodeAndSetUpDiagramReference', () => {
+  describe('add id to to node and set up diagram reference', () => {
     it('should not change node ID if it already has an ID set', () => {
       const nodeId = 'foobar';
       const newNodeId = 'baz';
       const diagramId = 'diagram_id';
-      const node = {
-        definition: { id: nodeId },
-        diagram: {},
-      };
+      const node = new Node(
+        'foo',
+        { id: nodeId },
+        {},
+      );
       const nodeIdGenerator = {
         generate: () => ([newNodeId, diagramId]),
       };
 
-      addIdToNodeAndSetUpDiagramReference(node, nodeIdGenerator);
+      node.setIds(nodeIdGenerator);
 
-      expect(node.definition.id).toBe(nodeId);
+      expect(node.id).toBe(nodeId);
       expect(node.diagram.id).toBe(diagramId);
       expect(node.diagram.bpmnElement).toBe(node.definition);
     });
 
     it('should not throw if node does not have a diagram', () => {
-      const node = {
-        definition: { id: 123 },
-      };
+      const node = new Node(
+        'foo',
+        { id: 123 },
+        {},
+      );
       const nodeIdGenerator = {
         generate: () => ([]),
       };
 
-      expect(() => addIdToNodeAndSetUpDiagramReference(node, nodeIdGenerator)).not.toThrow();
+      expect(() => node.setIds(nodeIdGenerator)).not.toThrow();
     });
 
     it('should use ID generator if definition does not have ID', () => {
       const nodeId = 'foobar';
       const diagramId = 'baz';
       const nodeIdGenerator = { generate: () => [nodeId, diagramId] };
-      const node = {
-        definition: {},
-        diagram: {},
-      };
+      const node = new Node('foo', {}, {});
 
-      addIdToNodeAndSetUpDiagramReference(node, nodeIdGenerator);
+      node.setIds(nodeIdGenerator);
 
-      expect(node.definition.id).toBe(nodeId);
+      expect(node.id).toBe(nodeId);
       expect(node.diagram.id).toBe(diagramId);
     });
   });
 
   describe('addNodeToProcess', () => {
     it('should not modify target process for pool nodes', () => {
-      const node = { type: poolId };
+      const node = new Node(poolId, {}, {});
       const targetProcess = {};
 
       addNodeToProcess(node, targetProcess);
@@ -61,7 +62,7 @@ describe('nodeManager', () => {
     });
 
     it('should not modify target process for node definition $type "bpmn:MessageFlow"', () => {
-      const node = { definition: { $type: 'bpmn:MessageFlow' } };
+      const node = new Node('foo', { $type: 'bpmn:MessageFlow' }, {});
       const targetProcess = {};
 
       addNodeToProcess(node, targetProcess);
@@ -70,7 +71,7 @@ describe('nodeManager', () => {
     });
 
     it('should set lane definition on target process lanes', () => {
-      const node = {type: laneId, definition: {}};
+      const node = new Node(laneId, {}, {});
       const targetProcess = {
         laneSets: [{
           lanes: [],
@@ -93,7 +94,7 @@ describe('nodeManager', () => {
       'bpmn:TextAnnotation',
       'bpmn:Association',
     ])('should set definition on target process artifacts for node definition type %s', $type => {
-      const node = { definition: { $type } };
+      const node = new Node('foo', { $type }, {});
       const targetProcess = {
         artifacts: [],
         get(key) {
@@ -108,7 +109,7 @@ describe('nodeManager', () => {
     });
 
     it('should set definition on target process flowElements for non-message-flow node', () => {
-      const node = { definition: { $type: 'bpmn:NotMessageFlow' } };
+      const node = new Node('Foo', { $type: 'bpmn:NotMessageFlow' }, {});
       const targetProcess = {
         flowElements: [],
         get(key) {
@@ -123,12 +124,12 @@ describe('nodeManager', () => {
     });
   });
 
-  describe('getTargetProcess', () => {
+  describe('get target process', () => {
     it('should return processNode definition for node that does not have pool', () => {
-      const node = {};
-      const processNode = { definition: {} };
+      const node = new Node('Foo', {}, {});
+      const processNode = new Node('Bar', {}, {});
 
-      const targetProcess = getTargetProcess(node, [], processNode);
+      const targetProcess = node.getTargetProcess([], processNode);
 
       expect(targetProcess).toBe(processNode.definition);
     });
@@ -136,17 +137,26 @@ describe('nodeManager', () => {
     it('should return matching pool process for node that has pool', () => {
       const targetProcessId = 'foobar';
       const process = { id: targetProcessId };
-      const node = { pool: { component: { node: {definition: {
-        processRef: { id: targetProcessId },
-        get(key) {
-          return this[key];
+      const node = new Node('Foo', {}, {});
+      node.pool = {
+        component: {
+          node: new Node('FooBar',
+            {
+              processRef: { id: targetProcessId },
+              get(key) {
+                return this[key];
+              },
+            },
+            {},
+          ),
         },
-      }}}}};
+      };
       const processes = [{}, {}, process, {}, {}];
 
-      const targetProcess = getTargetProcess(node, processes, {});
+      const targetProcess = node.getTargetProcess(processes, {});
 
       expect(targetProcess).toBe(process);
     });
   });
-});
+})
+;