fix(features/bpmn-modeling): reuse created elements during redo

This commit fixes the append node command by caching and reusing created
shapes and bpmn elements.

This ensures we do not invalidate actions that build on these element
references.

Related to #6
This commit is contained in:
Nico Rehwaldt 2014-07-18 14:39:15 +02:00
parent 0bb2f9c1ed
commit f1b023f419
8 changed files with 500 additions and 216 deletions

View File

@ -1,83 +1,9 @@
'use strict';
var _ = require('lodash');
var Refs = require('object-refs');
function hasLabel(semantic) {
return semantic.$instanceOf('bpmn:Event') ||
semantic.$instanceOf('bpmn:Gateway') ||
semantic.$instanceOf('bpmn:DataStoreReference') ||
semantic.$instanceOf('bpmn:DataObjectReference') ||
semantic.$instanceOf('bpmn:SequenceFlow') ||
semantic.$instanceOf('bpmn:MessageFlow');
}
function isCollapsed(semantic) {
return semantic.$instanceOf('bpmn:SubProcess') && !semantic.di.isExpanded;
}
function getWaypointsMid(waypoints) {
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
return {
x: first.x + (second.x - first.x) / 2,
y: first.y + (second.y - first.y) / 2
};
}
/**
* Returns the bounds of an elements label, parsed from the elements DI or
* generated from its bounds.
*/
function getLabelBounds(semantic, element) {
var mid,
size,
bounds,
di = semantic.di,
label = di.label;
if (label && label.bounds) {
bounds = label.bounds;
size = {
width: Math.max(150, bounds.width),
height: bounds.height
};
mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y
};
} else {
if (element.waypoints) {
mid = getWaypointsMid(element.waypoints);
} else {
mid = {
x: element.x + element.width / 2,
y: element.y + element.height - 5
};
}
size = {
width: 90,
height: 50
};
}
return _.extend({
x: mid.x - size.width / 2,
y: mid.y
}, size);
}
var hasExternalLabel = require('../util/Label').hasExternalLabel,
isExpanded = require('../util/Di').isExpanded;
/**
* An importer that adds bpmn elements to the canvas
@ -85,97 +11,56 @@ function getLabelBounds(semantic, element) {
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
function BpmnImporter(eventBus, canvas) {
function BpmnImporter(eventBus, canvas, elementFactory) {
this._eventBus = eventBus;
this._canvas = canvas;
this._elementFactory = elementFactory;
}
BpmnImporter.$inject = [ 'eventBus', 'canvas' ];
BpmnImporter.$inject = [ 'eventBus', 'canvas', 'elementFactory' ];
/**
* Add bpmn element (semantic) to the canvas onto the
* specified parent shape.
*/
BpmnImporter.prototype.add = function(semantic, parentShape) {
BpmnImporter.prototype.add = function(semantic, parentElement) {
var events = this._eventBus,
canvas = this._canvas;
var element,
di = semantic.di;
/**
* add label for the element
*/
function addLabel(semantic, element) {
var labelBounds = getLabelBounds(semantic, element);
var label = canvas.create('label', _.extend({
id: semantic.id + '_label',
labelTarget: element,
type: 'label',
hidden: element.hidden,
parent: element.parent,
businessObject: semantic
}, labelBounds));
canvas.addShape(label);
}
var di = semantic.di,
element;
// handle the special case that we deal with a
// invisible root element (process or collaboration)
if (di.$instanceOf('bpmndi:BPMNPlane')) {
// add a virtual element (not being drawn)
element = canvas.create('root', _.extend({
id: semantic.id,
type: semantic.$type,
businessObject: semantic
}));
} else
if (di.$instanceOf('bpmndi:BPMNShape')) {
var bounds = di.bounds;
var collapsed = isCollapsed(semantic);
var hidden = parentShape && (parentShape.hidden || parentShape.collapsed);
element = canvas.create('shape', {
id: semantic.id,
type: semantic.$type,
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
collapsed: collapsed,
hidden: hidden,
parent: parentShape,
businessObject: semantic
});
canvas.addShape(element);
} else {
var waypoints = _.collect(di.waypoint, function(p) {
return { x: p.x, y: p.y };
});
element = canvas.create('connection', {
id: semantic.id,
type: semantic.$type,
waypoints: waypoints,
parent: parentShape,
businessObject: semantic
});
element = canvas.addConnection(element);
element = this._elementFactory.createRoot(semantic);
}
if (hasLabel(semantic)) {
addLabel(semantic, element);
// SHAPE
else if (di.$instanceOf('bpmndi:BPMNShape')) {
var collapsed = !isExpanded(semantic);
var hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
element = this._elementFactory.createShape(semantic, {
collapsed: collapsed,
hidden: hidden
});
this._canvas.addShape(element, parentElement);
}
// CONNECTION
else {
element = this._elementFactory.createConnection(semantic, parentElement);
this._canvas.addConnection(element, parentElement);
}
// (optional) LABEL
if (hasExternalLabel(semantic)) {
this.addLabel(semantic, element);
}
this._eventBus.fire('bpmnElement.added', { element: element });
@ -184,4 +69,13 @@ BpmnImporter.prototype.add = function(semantic, parentShape) {
};
/**
* add label for an element
*/
BpmnImporter.prototype.addLabel = function (semantic, element) {
var label = this._elementFactory.createLabel(semantic, element);
return this._canvas.addShape(label, element.parent);
};
module.exports = BpmnImporter;

View File

@ -0,0 +1,78 @@
'use strict';
var _ = require('lodash');
var LabelUtil = require('../util/Label');
var getExternalLabelBounds = LabelUtil.getExternalLabelBounds;
/**
* A factory for diagram-js shapes
*
* @param {ElementFactory} canvas
*/
function ElementFactory(canvas) {
this._canvas = canvas;
}
ElementFactory.$inject = [ 'canvas' ];
module.exports = ElementFactory;
ElementFactory.prototype.createRoot = function(semantic) {
return this._canvas.create('root', _.extend({
id: semantic.id,
type: semantic.$type,
businessObject: semantic
}));
};
ElementFactory.prototype.createLabel = function(semantic, element) {
var labelBounds = getExternalLabelBounds(semantic, element);
var labelData = _.extend({
id: semantic.id + '_label',
labelTarget: element,
type: 'label',
hidden: element.hidden,
businessObject: semantic
}, labelBounds);
return this._canvas.create('label', labelData);
};
ElementFactory.prototype.createShape = function(semantic, attrs) {
var bounds = semantic.di.bounds;
var shapeData = _.extend({
id: semantic.id,
type: semantic.$type,
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
businessObject: semantic
}, attrs);
return this._canvas.create('shape', shapeData);
};
ElementFactory.prototype.createConnection = function(semantic) {
var waypoints = _.collect(semantic.di.waypoint, function(p) {
return { x: p.x, y: p.y };
});
return this._canvas.create('connection', {
id: semantic.id,
type: semantic.$type,
waypoints: waypoints,
businessObject: semantic
});
};

View File

@ -1,3 +1,4 @@
module.exports = {
bpmnImporter: [ 'type', require('./BpmnImporter') ]
bpmnImporter: [ 'type', require('./BpmnImporter') ],
elementFactory: [ 'type', require('./ElementFactory') ]
};

View File

@ -4,6 +4,7 @@ var _ = require('lodash');
var BpmnModdle = require('bpmn-moddle');
var Collections = require('diagram-js/lib/util/Collections');
function BpmnFactory() {
@ -14,20 +15,32 @@ function BpmnFactory() {
BpmnFactory.$inject = [ ];
BpmnFactory.prototype._needsId = function(element) {
return element.$instanceOf('bpmn:RootElement') ||
element.$instanceOf('bpmn:FlowElement') ||
element.$instanceOf('bpmn:Artifact') ||
element.$instanceOf('bpmndi:BPMNShape') ||
element.$instanceOf('bpmndi:BPMNEdge') ||
element.$instanceOf('bpmndi:BPMNDiagram') ||
element.$instanceOf('bpmndi:BPMNPlane');
};
BpmnFactory.prototype._ensureId = function(element) {
if (element.id === undefined) {
if (!element.id && this._needsId(element)) {
element.id = '' + (++this._uuid);
}
};
BpmnFactory.prototype.create = function(type, attrs) {
var element = this._model.create(type, attrs);
var element = this._model.create(type, attrs || {});
this._ensureId(element);
return element;
};
BpmnFactory.prototype.createDiShape = function(semantic, position, attrs) {
position = position || { x: 0, y: 0 };
@ -51,18 +64,18 @@ BpmnFactory.prototype.createDiShape = function(semantic, position, attrs) {
}, attrs));
};
BpmnFactory.prototype.createDiBounds = function(bounds) {
return this.create('dc:Bounds', bounds);
};
BpmnFactory.prototype.createDiWaypoint = function(point) {
return this.create('dc:Point', point);
};
BpmnFactory.prototype.createDiEdge = function(sequenceFlow, points, attrs) {
var sourceDi = sequenceFlow.sourceRef.di,
targetDi = sequenceFlow.targetRef.di;
BpmnFactory.prototype.createDiEdge = function(sequenceFlow, points, attrs) {
var waypoints = _.map(points, function(pos) {
return this.createDiWaypoint(pos);
@ -74,17 +87,27 @@ BpmnFactory.prototype.createDiEdge = function(sequenceFlow, points, attrs) {
}, attrs));
};
BpmnFactory.prototype.createSequenceFlow = function(source, target, attrs) {
var sequenceFlow = this.create('bpmn:SequenceFlow', _.extend({
BpmnFactory.prototype.disconnectSequenceFlow = function(sequenceFlow) {
Collections.remove(sequenceFlow.sourceRef && sequenceFlow.sourceRef.get('outgoing'), sequenceFlow.sourceRef);
Collections.remove(sequenceFlow.targetRef && sequenceFlow.targetRef.get('incoming'), sequenceFlow.targetRef);
_.extend(sequenceFlow, {
sourceRef: null,
targetRef: null
});
};
BpmnFactory.prototype.connectSequenceFlow = function(sequenceFlow, source, target) {
_.extend(sequenceFlow, {
sourceRef: source,
targetRef: target
}, attrs));
});
source.get('outgoing').push(sequenceFlow);
target.get('incoming').push(sequenceFlow);
return sequenceFlow;
};

View File

@ -1,6 +1,9 @@
'use strict';
var LayoutUtil = require('../LayoutUtil');
var LayoutUtil = require('../LayoutUtil'),
Collections = require('diagram-js/lib/util/Collections');
var hasExternalLabel = require('../../../util/Label').hasExternalLabel;
var AppendShapeHandler = require('diagram-js/lib/features/modeling/cmd/AppendShapeHandler');
@ -9,22 +12,27 @@ var Refs = require('object-refs');
var diRefs = new Refs({ name: 'bpmnElement', enumerable: true }, { name: 'di' });
function AppendFlowNodeHandler(canvas, bpmnFactory, bpmnImporter) {
function AppendFlowNodeHandler(canvas, bpmnFactory, bpmnImporter, elementFactory) {
AppendShapeHandler.call(this);
this._bpmnImporter = bpmnImporter;
this._bpmnFactory = bpmnFactory;
this._canvas = canvas;
this._bpmnFactory = bpmnFactory;
this._bpmnImporter = bpmnImporter;
this._elementFactory = elementFactory;
}
AppendFlowNodeHandler.prototype = Object.create(AppendShapeHandler.prototype);
AppendFlowNodeHandler.$inject = [ 'canvas', 'bpmnFactory', 'bpmnImporter' ];
AppendFlowNodeHandler.$inject = [ 'canvas', 'bpmnFactory', 'bpmnImporter', 'elementFactory' ];
AppendFlowNodeHandler.prototype.createShape = function(source, position, parent, context) {
var bpmnFactory = this._bpmnFactory,
elementFactory = this._elementFactory,
canvas = this._canvas;
var sourceSemantic = source.businessObject,
parentSemantic = parent.businessObject;
@ -32,83 +40,144 @@ AppendFlowNodeHandler.prototype.createShape = function(source, position, parent,
targetSemantic,
targetDi;
// create semantic
targetSemantic = this._bpmnFactory.create(context.type, {
id: target && target.businessObject.id
});
// we need to create shape + bpmn elements
if (!target) {
// create semantic
targetSemantic = bpmnFactory.create(context.type);
// add to model
// create di
targetDi = bpmnFactory.createDiShape(targetSemantic, position, {
id: targetSemantic.id + '_di'
});
// bind semantic -> di -> semantic
diRefs.bind(targetSemantic, 'di');
targetSemantic.di = targetDi;
// create node
target = elementFactory.createShape(targetSemantic);
// add label
if (hasExternalLabel(targetSemantic)) {
target.label = elementFactory.createLabel(targetSemantic, target);
}
}
// load di + semantic from target
else {
targetSemantic = target.businessObject;
targetDi = targetSemantic.di;
}
// reconnect everything
// wire semantic
parentSemantic.get('flowElements').push(targetSemantic);
targetSemantic.$parent = parentSemantic;
// create di
targetDi = this._bpmnFactory.createDiShape(targetSemantic, position, {
id: targetSemantic.id + '_di'
});
diRefs.bind(targetSemantic, 'di');
targetSemantic.di = targetDi;
// add to model
// wire di
sourceSemantic.di.$parent.get('planeElement').push(targetDi);
targetDi.$parent = sourceSemantic.di.$parent;
return this._bpmnImporter.add(targetSemantic, parent);
canvas.addShape(target, parent);
if (target.label) {
canvas.addShape(target.label, parent);
}
return target;
};
AppendFlowNodeHandler.prototype.createConnection = function(source, target, parent, context) {
var bpmnFactory = this._bpmnFactory,
elementFactory = this._elementFactory,
canvas = this._canvas;
var sourceSemantic = source.businessObject,
targetSemantic = target.businessObject,
parentSemantic = parent.businessObject;
var flowSemantic,
flowDi;
var connection = context.connection,
connectionSemantic,
connectionDi;
var connection = context.connection;
// create semantic
flowSemantic = this._bpmnFactory.createSequenceFlow(sourceSemantic, targetSemantic, {
id: connection && connection.businessObject.id
});
// we need to create connection + bpmn elements
if (!connection) {
// add to model
parentSemantic.get('flowElements').push(flowSemantic);
flowSemantic.$parent = parentSemantic;
// create semantic
connectionSemantic = bpmnFactory.create('bpmn:SequenceFlow');
// create di
var waypoints = LayoutUtil.getDirectConnectionPoints(sourceSemantic.di.bounds, targetSemantic.di.bounds);
// create di
var waypoints = LayoutUtil.getDirectConnectionPoints(sourceSemantic.di.bounds, targetSemantic.di.bounds);
flowDi = this._bpmnFactory.createDiEdge(flowSemantic, waypoints, {
id: flowSemantic.id + '_di'
});
connectionDi = bpmnFactory.createDiEdge(connectionSemantic, waypoints, {
id: connectionSemantic.id + '_di'
});
diRefs.bind(flowSemantic, 'di');
flowSemantic.di = flowDi;
// bind semantic -> di -> semantic
diRefs.bind(connectionSemantic, 'di');
connectionSemantic.di = connectionDi;
// add to model
sourceSemantic.di.$parent.get('planeElement').push(flowDi);
flowDi.$parent = sourceSemantic.di.$parent;
// create connection
connection = elementFactory.createConnection(connectionSemantic);
return this._bpmnImporter.add(flowSemantic, parent);
// add label
if (hasExternalLabel(connectionSemantic)) {
connection.label = elementFactory.createLabel(connectionSemantic, connection);
}
}
// load di + semantic from target
else {
connectionSemantic = connection.businessObject;
connectionDi = connectionSemantic.di;
}
// connect
bpmnFactory.connectSequenceFlow(connectionSemantic, sourceSemantic, targetSemantic);
// TODO(nre): connect connection
// wire semantic
parentSemantic.get('flowElements').push(connectionSemantic);
connectionSemantic.$parent = parentSemantic;
// wire di
sourceSemantic.di.$parent.get('planeElement').push(connectionDi);
connectionDi.$parent = sourceSemantic.di.$parent;
canvas.addConnection(connection, parent);
if (connection.label) {
canvas.addShape(connection.label, parent);
}
return connection;
};
AppendFlowNodeHandler.prototype.removeShape = function(shape) {
var semantic = shape.businessObject,
di = semantic.di,
parentSemantic = semantic.$parent;
// remove semantic
// undo wire semantic
parentSemantic.get('flowElements').splice(parentSemantic.get('flowElements').indexOf(semantic), 1);
semantic.$parent = null;
// remove di
var di = semantic.di;
// undo wire di
di.$parent.get('planeElement').splice(di.$parent.get('planeElement').indexOf(di), 1);
di.$parent = null;
// remove label
if (shape.label) {
this._canvas.removeShape(shape.label);
}
// actual remove shape
return this._canvas.removeShape(shape);
};
@ -117,23 +186,27 @@ AppendFlowNodeHandler.prototype.removeShape = function(shape) {
AppendFlowNodeHandler.prototype.removeConnection = function(connection) {
var semantic = connection.businessObject,
di = semantic.di,
parentSemantic = semantic.$parent;
// remove semantic
parentSemantic.get('flowElements').splice(parentSemantic.get('flowElements').indexOf(semantic), 1);
// undo wire semantic
Collections.remove(parentSemantic.get('flowElements'), semantic);
semantic.$parent = null;
// remove di
var di = semantic.di;
di.$parent.get('planeElement').splice(di.$parent.get('planeElement').indexOf(di), 1);
// undo wire di
Collections.remove(di.$parent.get('planeElement'), di);
di.$parent = null;
// remove refs in source / target
semantic.sourceRef.outgoing.splice(semantic.sourceRef.outgoing.indexOf(semantic), 1);
semantic.targetRef.incoming.splice(semantic.targetRef.incoming.indexOf(semantic), 1);
// undo wire refs
this._bpmnFactory.disconnectSequenceFlow(semantic);
semantic.sourceRef = null;
semantic.targetRef = null;
semantic.$parent = null;
// TODO(nre): disconnect connection
// remove label
if (connection.label) {
this._canvas.removeShape(connection.label);
}
// actual remove connection
return this._canvas.removeConnection(connection);

View File

@ -5,5 +5,5 @@ module.exports.isExpandedPool = function(semantic) {
};
module.exports.isExpanded = function(semantic) {
return semantic.di.isExpanded;
return !semantic.$instanceOf('bpmn:SubProcess') || semantic.di.isExpanded;
};

91
lib/util/Label.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
var _ = require('lodash');
/**
* Returns true if the given semantic has an external label
*
* @param {BpmnElement} semantic
* @return {Boolean} true if has label
*/
module.exports.hasExternalLabel = function(semantic) {
return semantic.$instanceOf('bpmn:Event') ||
semantic.$instanceOf('bpmn:Gateway') ||
semantic.$instanceOf('bpmn:DataStoreReference') ||
semantic.$instanceOf('bpmn:DataObjectReference') ||
semantic.$instanceOf('bpmn:SequenceFlow') ||
semantic.$instanceOf('bpmn:MessageFlow');
};
/**
* Get the middle of a number of waypoints
*
* @param {Array<Point>} waypoints
* @return {Point} the mid point
*/
module.exports.getWaypointsMid = function(waypoints) {
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
return {
x: first.x + (second.x - first.x) / 2,
y: first.y + (second.y - first.y) / 2
};
};
/**
* Returns the bounds of an elements label, parsed from the elements DI or
* generated from its bounds.
*
* @param {BpmnElement} semantic
* @param {djs.model.Base} element
*/
module.exports.getExternalLabelBounds = function(semantic, element) {
var mid,
size,
bounds,
di = semantic.di,
label = di.label;
if (label && label.bounds) {
bounds = label.bounds;
size = {
width: Math.max(150, bounds.width),
height: bounds.height
};
mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y
};
} else {
if (element.waypoints) {
mid = module.exports.getWaypointsMid(element.waypoints);
} else {
mid = {
x: element.x + element.width / 2,
y: element.y + element.height - 5
};
}
size = {
width: 90,
height: 50
};
}
return _.extend({
x: mid.x - size.width / 2,
y: mid.y
}, size);
};

View File

@ -128,6 +128,35 @@ describe('features - bpmn-modeling', function() {
}));
it('should undo add shape label', inject(function(elementRegistry, bpmnModeling, commandStack) {
// given
var startEventShape = elementRegistry.getById('StartEvent_1');
var subProcessShape = elementRegistry.getById('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = bpmnModeling.appendFlowNode(startEventShape, null, 'bpmn:EndEvent'),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
});
// when
commandStack.undo();
// then
expect(connection.sourceRef).toBe(null);
expect(connection.targetRef).toBe(null);
expect(connection.$parent).toBe(null);
expect(subProcess.di.$parent.get('planeElement')).not.toContain(connection.di);
expect(elementRegistry.getById(targetShape.label.id)).not.toBeDefined();
}));
it('should undo add connection', inject(function(elementRegistry, bpmnModeling, commandStack) {
// given
@ -152,6 +181,101 @@ describe('features - bpmn-modeling', function() {
expect(connection.targetRef).toBe(null);
expect(connection.$parent).toBe(null);
expect(subProcess.di.$parent.get('planeElement')).not.toContain(connection.di);
expect(elementRegistry.getById(targetShape.id)).not.toBeDefined();
}));
it('should undo add connection label', inject(function(elementRegistry, bpmnModeling, commandStack) {
// given
var startEventShape = elementRegistry.getById('StartEvent_1');
var subProcessShape = elementRegistry.getById('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = bpmnModeling.appendFlowNode(startEventShape, null, 'bpmn:Task'),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
});
// when
commandStack.undo();
// then
expect(connection.sourceRef).toBe(null);
expect(connection.targetRef).toBe(null);
expect(connection.$parent).toBe(null);
expect(subProcess.di.$parent.get('planeElement')).not.toContain(connection.di);
expect(elementRegistry.getById(connection.id + '_label')).not.toBeDefined();
}));
it('should undo/redo appending multiple shapes', inject(function(elementRegistry, bpmnModeling, commandStack) {
// given
var startEventShape = elementRegistry.getById('StartEvent_1');
var subProcessShape = elementRegistry.getById('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = bpmnModeling.appendFlowNode(startEventShape, null, 'bpmn:Task'),
target = targetShape.businessObject;
var targetShape2 = bpmnModeling.appendFlowNode(targetShape, null, 'bpmn:UserTask');
// when
commandStack.undo();
commandStack.undo();
commandStack.redo();
commandStack.redo();
// then
// expect redo to work on original target object
expect(targetShape.parent).toBe(subProcessShape);
// when
commandStack.undo();
commandStack.undo();
// then
expect(targetShape2.parent).toBe(null);
expect(elementRegistry.getById(targetShape2.id)).not.toBeDefined();
}));
it('should undo/redo add connection', inject(function(elementRegistry, bpmnModeling, commandStack) {
// given
var startEventShape = elementRegistry.getById('StartEvent_1');
var subProcessShape = elementRegistry.getById('SubProcess_1');
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = bpmnModeling.appendFlowNode(startEventShape, null, 'bpmn:Task'),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
return e.sourceRef === startEvent && e.targetRef === target;
});
// when
commandStack.undo();
commandStack.redo();
commandStack.undo();
// then
expect(connection.sourceRef).toBe(null);
expect(connection.targetRef).toBe(null);
expect(connection.$parent).toBe(null);
expect(subProcess.di.$parent.get('planeElement')).not.toContain(connection.di);
}));
});