diff --git a/lib/Modeler.js b/lib/Modeler.js
index 273b3bd2..e975db32 100644
--- a/lib/Modeler.js
+++ b/lib/Modeler.js
@@ -185,6 +185,7 @@ Modeler.prototype._modelingModules = [
require('diagram-js/lib/features/hand-tool'),
require('./features/global-connect'),
require('./features/keyboard'),
+ require('./features/copy-paste'),
require('./features/snapping'),
require('./features/modeling'),
require('./features/context-pad'),
@@ -202,4 +203,4 @@ Modeler.prototype._modelingModules = [
Modeler.prototype._modules = [].concat(
Modeler.prototype._modules,
Modeler.prototype._interactionModules,
- Modeler.prototype._modelingModules);
\ No newline at end of file
+ Modeler.prototype._modelingModules);
diff --git a/lib/features/copy-paste/BpmnCopyPaste.js b/lib/features/copy-paste/BpmnCopyPaste.js
new file mode 100644
index 00000000..d0e6fe32
--- /dev/null
+++ b/lib/features/copy-paste/BpmnCopyPaste.js
@@ -0,0 +1,178 @@
+'use strict';
+
+var ModelUtil = require('../../util/ModelUtil'),
+ getBusinessObject = ModelUtil.getBusinessObject,
+ is = ModelUtil.is;
+
+var map = require('lodash/collection/map'),
+ forEach = require('lodash/collection/forEach');
+
+
+function setProperties(descriptor, data, properties) {
+ forEach(properties, function(property) {
+ if (data[property]) {
+ descriptor[property] = data[property];
+ }
+ });
+}
+
+function removeProperties(element, properties) {
+ forEach(properties, function(prop) {
+ if (element[prop]) {
+ delete element[prop];
+ }
+ });
+}
+
+function BpmnCopyPaste(bpmnFactory, eventBus, copyPaste, clipboard, moddle, canvas, bpmnRules) {
+
+ copyPaste.registerDescriptor(function(element, descriptor) {
+ var businessObject = getBusinessObject(element),
+ conditionExpression,
+ eventDefinitions;
+
+ descriptor.type = element.type;
+
+ if (element.type === 'label') {
+ return descriptor;
+ }
+
+ setProperties(descriptor, businessObject, [
+ 'name',
+ 'text',
+ 'processRef',
+ 'isInterrupting',
+ 'isForCompensation',
+ 'associationDirection'
+ ]);
+
+ if (businessObject.default) {
+ descriptor.default = businessObject.default.id;
+ }
+
+ if (businessObject.loopCharacteristics) {
+
+ descriptor.loopCharacteristics = {
+ type: businessObject.loopCharacteristics.$type,
+ isSequential: businessObject.loopCharacteristics.isSequential
+ };
+ }
+
+ setProperties(descriptor, businessObject.di, [ 'isExpanded' ]);
+
+ if (is(businessObject, 'bpmn:SequenceFlow')) {
+ conditionExpression = businessObject.get('conditionExpression');
+
+ if (conditionExpression) {
+ descriptor.conditionExpression = {
+ type: conditionExpression.$type,
+ body: conditionExpression.body
+ };
+ }
+ }
+
+ eventDefinitions = businessObject.get('eventDefinitions') || [];
+
+ if (eventDefinitions.length) {
+ descriptor.eventDefinitions = map(eventDefinitions, function(defs) {
+ return defs.$type;
+ });
+ }
+
+ return descriptor;
+ });
+
+ eventBus.on('element.paste', function(context) {
+ var descriptor = context.descriptor,
+ createdElements = context.createdElements,
+ parent = descriptor.parent,
+ businessObject, eventDefinitions, newEventDefinition, rootElement,
+ conditionExpression, loopCharacteristics,
+ source, target, canConnect;
+
+ if (descriptor.type === 'label') {
+ return;
+ }
+
+ if (is(parent, 'bpmn:Process')) {
+ rootElement = canvas.getRootElement();
+
+ descriptor.parent = is(rootElement, 'bpmn:Collaboration') ? rootElement : parent;
+ }
+
+ if (descriptor.type === 'bpmn:MessageFlow') {
+ descriptor.parent = canvas.getRootElement();
+ }
+
+ // make sure that the correct type of connection is created
+ if (descriptor.waypoints) {
+ source = createdElements[descriptor.source];
+ target = createdElements[descriptor.target];
+
+ if (source && target) {
+ source = source.element;
+ target = target.element;
+ }
+
+ canConnect = bpmnRules.canConnect(source, target);
+
+ if (canConnect) {
+ descriptor.type = canConnect.type;
+ }
+ }
+
+ descriptor.businessObject = businessObject = bpmnFactory.create(descriptor.type);
+
+ setProperties(businessObject, descriptor, [
+ 'name',
+ 'text',
+ ]);
+
+ if (descriptor.loopCharacteristics) {
+ loopCharacteristics = descriptor.loopCharacteristics;
+
+ businessObject.loopCharacteristics = moddle.create(loopCharacteristics.type);
+
+ if (loopCharacteristics.isSequential) {
+ businessObject.loopCharacteristics.isSequential = true;
+ }
+
+ businessObject.loopCharacteristics.$parent = businessObject;
+ }
+
+ if (descriptor.conditionExpression) {
+ conditionExpression = descriptor.conditionExpression;
+
+ businessObject.conditionExpression = moddle.create(conditionExpression.type, { body: conditionExpression.body });
+
+ businessObject.conditionExpression.$parent = businessObject;
+ }
+
+ if (descriptor.eventDefinitions) {
+ eventDefinitions = businessObject.eventDefinitions;
+
+ businessObject.eventDefinitions = map(descriptor.eventDefinitions, function(type) {
+ newEventDefinition = moddle.create(type);
+
+ newEventDefinition.$parent = businessObject;
+
+ return newEventDefinition;
+ });
+ }
+
+ removeProperties(descriptor, [ 'name', 'text', 'eventDefinitions', 'conditionExpression', 'loopCharacteristics' ]);
+ });
+}
+
+
+BpmnCopyPaste.$inject = [
+ 'bpmnFactory',
+ 'eventBus',
+ 'copyPaste',
+ 'clipboard',
+ 'moddle',
+ 'canvas',
+ 'bpmnRules'
+];
+
+module.exports = BpmnCopyPaste;
diff --git a/lib/features/copy-paste/index.js b/lib/features/copy-paste/index.js
new file mode 100644
index 00000000..9036868f
--- /dev/null
+++ b/lib/features/copy-paste/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+ __depends__: [
+ require('diagram-js/lib/features/copy-paste')
+ ],
+ __init__: [ 'bpmnCopyPaste' ],
+ bpmnCopyPaste: [ 'type', require('./BpmnCopyPaste') ]
+};
diff --git a/lib/features/modeling/behavior/CopyPasteBehavior.js b/lib/features/modeling/behavior/CopyPasteBehavior.js
new file mode 100644
index 00000000..e982fe3a
--- /dev/null
+++ b/lib/features/modeling/behavior/CopyPasteBehavior.js
@@ -0,0 +1,61 @@
+'use strict';
+
+var inherits = require('inherits');
+
+var forEach = require('lodash/collection/forEach');
+
+var is = require('../../../util/ModelUtil').is;
+
+var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
+
+
+function CopyPasteBehavior(eventBus, modeling, canvas) {
+
+ CommandInterceptor.call(this, eventBus);
+
+ this.preExecute('elements.paste', 1500, function(context) {
+ var topParent = context.topParent;
+
+ // always grab the latest root
+ if (!topParent.parent) {
+ context.topParent = canvas.getRootElement();
+ }
+ }, true);
+
+ this.postExecute('elements.paste', function(context) {
+
+ var tree = context.tree,
+ createdElements = tree.createdElements;
+
+ forEach(createdElements, function(data) {
+ var element = data.element,
+ businessObject = element.businessObject,
+ descriptor = data.descriptor,
+ defaultFlow;
+
+ if ((is(businessObject, 'bpmn:ExclusiveGateway') || is(businessObject, 'bpmn:InclusiveGateway') ||
+ is(businessObject, 'bpmn:Activity')) && descriptor.default) {
+
+ defaultFlow = createdElements[descriptor.default];
+
+ // if the default flow wasn't created, means that it wasn't copied
+ if (defaultFlow) {
+ defaultFlow = defaultFlow.element;
+ } else {
+ defaultFlow = undefined;
+ }
+
+ delete element.default;
+
+ modeling.updateProperties(element, { default: defaultFlow });
+ }
+ });
+ }, true);
+}
+
+
+CopyPasteBehavior.$inject = [ 'eventBus', 'modeling', 'canvas' ];
+
+inherits(CopyPasteBehavior, CommandInterceptor);
+
+module.exports = CopyPasteBehavior;
diff --git a/lib/features/modeling/behavior/CreateParticipantBehavior.js b/lib/features/modeling/behavior/CreateParticipantBehavior.js
index 2a1e5b11..b268661e 100644
--- a/lib/features/modeling/behavior/CreateParticipantBehavior.js
+++ b/lib/features/modeling/behavior/CreateParticipantBehavior.js
@@ -9,7 +9,7 @@ var is = require('../../../util/ModelUtil').is;
/**
* BPMN specific create participant behavior
*/
-function CreateParticipantBehavior(eventBus, modeling, elementFactory, bpmnFactory) {
+function CreateParticipantBehavior(eventBus, modeling, elementFactory, bpmnFactory, canvas) {
CommandInterceptor.call(this, eventBus);
@@ -24,7 +24,9 @@ function CreateParticipantBehavior(eventBus, modeling, elementFactory, bpmnFacto
shape = context.shape,
position = context.position;
- if (is(parent, 'bpmn:Process') && is(shape, 'bpmn:Participant')) {
+ var rootElement = canvas.getRootElement();
+
+ if (is(parent, 'bpmn:Process') && is(shape, 'bpmn:Participant') && !is(rootElement, 'bpmn:Collaboration')) {
// this is going to detach the process root
// and set the returned collaboration element
@@ -82,7 +84,7 @@ function CreateParticipantBehavior(eventBus, modeling, elementFactory, bpmnFacto
}
-CreateParticipantBehavior.$inject = [ 'eventBus', 'modeling', 'elementFactory', 'bpmnFactory' ];
+CreateParticipantBehavior.$inject = [ 'eventBus', 'modeling', 'elementFactory', 'bpmnFactory', 'canvas' ];
inherits(CreateParticipantBehavior, CommandInterceptor);
diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js
index 190b42f3..3e609ee3 100644
--- a/lib/features/modeling/behavior/index.js
+++ b/lib/features/modeling/behavior/index.js
@@ -1,6 +1,7 @@
module.exports = {
__init__: [
'appendBehavior',
+ 'copyPasteBehavior',
'createBoundaryEventBehavior',
'createDataObjectBehavior',
'createOnFlowBehavior',
@@ -19,6 +20,7 @@ module.exports = {
'unclaimIdBehavior'
],
appendBehavior: [ 'type', require('./AppendBehavior') ],
+ copyPasteBehavior: [ 'type', require('./CopyPasteBehavior') ],
createBoundaryEventBehavior: [ 'type', require('./CreateBoundaryEventBehavior') ],
createDataObjectBehavior: [ 'type', require('./CreateDataObjectBehavior') ],
createOnFlowBehavior: [ 'type', require('./CreateOnFlowBehavior') ],
diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js
index 05937193..e1a73480 100644
--- a/lib/features/rules/BpmnRules.js
+++ b/lib/features/rules/BpmnRules.js
@@ -92,6 +92,33 @@ BpmnRules.prototype.init = function() {
return canAttach([ shape ], target, source, position) || canCreate(shape, target, source, position);
});
+ this.addRule('element.copy', function(context) {
+ var collection = context.collection,
+ element = context.element;
+
+ return canCopy(collection, element);
+ });
+
+ this.addRule('element.paste', function(context) {
+ var parent = context.parent,
+ element = context.element,
+ position = context.position,
+ source = context.source,
+ target = context.target;
+
+ if (source || target) {
+ return canConnect(source, target);
+ }
+
+ return canAttach([ element ], parent, null, position) || canCreate(element, parent, null, position);
+ });
+
+ this.addRule('elements.paste', function(context) {
+ var target = context.target;
+
+ return canPaste(target);
+ });
+
this.addRule([ 'elements.delete' ], function(context) {
// do not allow deletion of labels
@@ -125,6 +152,8 @@ BpmnRules.prototype.canConnect = canConnect;
BpmnRules.prototype.canResize = canResize;
+BpmnRules.prototype.canCopy = canCopy;
+
/**
* Utility functions for rule checking
*/
@@ -395,6 +424,25 @@ function canDrop(element, target, position) {
return false;
}
+function canPaste(target) {
+
+ // disallow to create elements on collapsed pools
+ if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
+ return false;
+ }
+
+ if (is(target, 'bpmn:FlowElementsContainer')) {
+ return isExpanded(target);
+ }
+
+ return isAny(target, [
+ 'bpmn:Collaboration',
+ 'bpmn:Lane',
+ 'bpmn:Participant',
+ 'bpmn:Process',
+ 'bpmn:SubProcess' ]);
+}
+
function isBoundaryEvent(element) {
return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
}
@@ -659,3 +707,19 @@ function canInsert(shape, flow, position) {
!is(shape, 'bpmn:BoundaryEvent') &&
canDrop(shape, flow.parent, position));
}
+
+function contains(collection, element) {
+ return (collection && element) && collection.indexOf(element) !== -1;
+}
+
+function canCopy(collection, element) {
+ if (is(element, 'bpmn:Lane') && !contains(collection, element.parent)) {
+ return false;
+ }
+
+ if (is(element, 'bpmn:BoundaryEvent') && !contains(collection, element.host)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/test/fixtures/bpmn/features/copy-paste/basic.bpmn b/test/fixtures/bpmn/features/copy-paste/basic.bpmn
new file mode 100644
index 00000000..f50f1971
--- /dev/null
+++ b/test/fixtures/bpmn/features/copy-paste/basic.bpmn
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+ SequenceFlow_1rtr33r
+
+
+ SequenceFlow_0y69l8f
+
+
+ SequenceFlow_1rtr33r
+ SequenceFlow_0y69l8f
+ SequenceFlow_07vo2r8
+
+
+
+
+
+
+
+
+
+
+ SequenceFlow_07vo2r8
+
+
+ foo
+
+
+ foo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/bpmn/features/copy-paste/collaboration-multiple.bpmn b/test/fixtures/bpmn/features/copy-paste/collaboration-multiple.bpmn
new file mode 100644
index 00000000..2aa1c357
--- /dev/null
+++ b/test/fixtures/bpmn/features/copy-paste/collaboration-multiple.bpmn
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+ Task_1pamrp2
+
+
+ Task_1pamrp2
+
+
+
+
+
+ Task_0n0k2nj
+
+
+
+
+
+
+
+
+ StartEvent_07r1iyh
+
+
+ IntermediateThrowEvent_0audt6r
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/bpmn/features/copy-paste/collaboration.bpmn b/test/fixtures/bpmn/features/copy-paste/collaboration.bpmn
new file mode 100644
index 00000000..9d9299c9
--- /dev/null
+++ b/test/fixtures/bpmn/features/copy-paste/collaboration.bpmn
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+ StartEvent_0o6vk5g
+ Task_13xbgyg
+ EndEvent_1nef447
+
+
+
+
+ SequenceFlow_0v3q8mo
+
+
+ SequenceFlow_0v3q8mo
+ SequenceFlow_1yvonen
+
+
+
+ SequenceFlow_1yvonen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/features/copy-paste/BpmnCopyPasteSpec.js b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js
new file mode 100644
index 00000000..c6c47412
--- /dev/null
+++ b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js
@@ -0,0 +1,340 @@
+'use strict';
+
+var TestHelper = require('../../../TestHelper');
+
+/* global bootstrapModeler, inject */
+
+var bpmnCopyPasteModule = require('../../../../lib/features/copy-paste'),
+ copyPasteModule = require('diagram-js/lib/features/copy-paste'),
+ modelingModule = require('../../../../lib/features/modeling'),
+ coreModule = require('../../../../lib/core');
+
+var map = require('lodash/collection/map'),
+ forEach = require('lodash/collection/forEach');
+
+var DescriptorTree = require('./DescriptorTree');
+
+
+function mapProperty(shapes, prop) {
+ return map(shapes, function(shape) {
+ return shape[prop];
+ });
+}
+
+function expectCollection(collA, collB, contains) {
+ expect(collA).to.have.length(collB.length);
+
+ forEach(collB, function(element) {
+ if (!element.parent) {
+ return;
+ }
+
+ if (contains) {
+ expect(collA).to.contain(element);
+ } else {
+ expect(collA).to.not.contain(element);
+ }
+ });
+}
+
+
+describe('features/copy-paste', function() {
+
+ var testModules = [ bpmnCopyPasteModule, copyPasteModule, modelingModule, coreModule ];
+
+ var basicXML = require('../../../fixtures/bpmn/features/copy-paste/basic.bpmn'),
+ collaborationXML = require('../../../fixtures/bpmn/features/copy-paste/collaboration.bpmn'),
+ collaborationMultipleXML = require('../../../fixtures/bpmn/features/copy-paste/collaboration-multiple.bpmn');
+
+
+ var integrationTest = function(ids) {
+ return function(canvas, elementRegistry, modeling, copyPaste, commandStack) {
+ // given
+ var shapes = elementRegistry.getAll(),
+ rootElement;
+
+ var initialContext = {
+ type: mapProperty(shapes, 'type'),
+ ids: mapProperty(shapes, 'id'),
+ length: shapes.length
+ },
+ currentContext;
+
+ var elements = map(ids, function(id) {
+ return elementRegistry.get(id);
+ });
+
+ copyPaste.copy(elements);
+
+ modeling.removeElements(elements);
+
+ rootElement = canvas.getRootElement();
+
+ copyPaste.paste({
+ element: rootElement,
+ point: {
+ x: 1100,
+ y: 250
+ }
+ });
+
+ elements = elementRegistry.getAll();
+
+ // remove root
+ elements = elementRegistry.filter(function(element) {
+ return !!element.parent;
+ });
+
+ modeling.moveElements(elements, { x: 50, y: -50 });
+
+ // when
+ commandStack.undo();
+ commandStack.undo();
+ commandStack.undo();
+
+ currentContext = {
+ type: mapProperty(shapes, 'type'),
+ ids: mapProperty(shapes, 'id'),
+ length: shapes.length
+ };
+
+ // then
+ expect(initialContext).to.have.length(currentContext.length);
+
+ expectCollection(initialContext.ids, currentContext.ids, true);
+
+ // when
+ commandStack.redo();
+ commandStack.redo();
+ commandStack.redo();
+
+ currentContext = {
+ type: mapProperty(elementRegistry.getAll(), 'type'),
+ ids: mapProperty(elementRegistry.getAll(), 'id'),
+ length: shapes.length
+ };
+
+ // then
+ expect(initialContext).to.have.length(currentContext.length);
+
+ expectCollection(initialContext.type, currentContext.type, true);
+ expectCollection(initialContext.ids, currentContext.ids, false);
+ };
+ };
+
+ describe('basic diagram', function() {
+
+ beforeEach(bootstrapModeler(basicXML, { modules: testModules }));
+
+ describe('copy', function() {
+
+ it('selected elements', inject(function(elementRegistry, copyPaste) {
+
+ // given
+ var subProcess, startEvent, boundaryEvent, textAnnotation, conditionalFlow, defaultFlow,
+ tree;
+
+ // when
+ copyPaste.copy(elementRegistry.get('SubProcess_1kd6ist'));
+
+ tree = new DescriptorTree(copyPaste._tree);
+
+ startEvent = tree.getElement('StartEvent_1');
+ boundaryEvent = tree.getElement('BoundaryEvent_1c94bi9');
+ subProcess = tree.getElement('SubProcess_1kd6ist');
+ textAnnotation = tree.getElement('TextAnnotation_0h1hhgg');
+ conditionalFlow = tree.getElement('SequenceFlow_07vo2r8');
+ defaultFlow = tree.getElement('Task_1fo63a7');
+
+ // then
+ expect(tree.getLength()).to.equal(3);
+
+ expect(tree.getDepthLength(0)).to.equal(1);
+ expect(tree.getDepthLength(1)).to.equal(3);
+ expect(tree.getDepthLength(2)).to.equal(15);
+
+ expect(subProcess.isExpanded).to.be.true;
+ expect(startEvent.name).to.equal('hello');
+ expect(textAnnotation.text).to.equal('foo');
+ expect(boundaryEvent.eventDefinitions).to.contain('bpmn:TimerEventDefinition');
+ }));
+
+ });
+
+ describe('integration', function() {
+
+ it('should retain label\'s relative position',
+ inject(function(modeling, copyPaste, canvas, elementRegistry) {
+ // given
+ var startEvent = elementRegistry.get('StartEvent_1'),
+ startEventLabel = startEvent.label,
+ seqFlow = elementRegistry.get('SequenceFlow_1rtr33r'),
+ seqFlowLabel = seqFlow.label,
+ task = elementRegistry.get('Task_1fo63a7'),
+ rootElement = canvas.getRootElement(),
+ newStrtEvt, newSeqFlow;
+
+ // when
+ copyPaste.copy([ startEvent, task ]);
+
+ copyPaste.paste({
+ element: rootElement,
+ point: {
+ x: 1100,
+ y: 250
+ }
+ });
+
+ newStrtEvt = elementRegistry.filter(function(element) {
+ return element.parent === rootElement && element.type === 'bpmn:StartEvent';
+ })[0];
+
+ newSeqFlow = elementRegistry.filter(function(element) {
+ return element.parent === rootElement && element.type === 'bpmn:SequenceFlow';
+ })[0];
+
+ // then
+ expect(newStrtEvt.label.x - newStrtEvt.x).to.equal(startEventLabel.x - startEvent.x);
+ expect(newStrtEvt.label.y - newStrtEvt.y).to.equal(startEventLabel.y - startEvent.y);
+
+ expect(newSeqFlow.label.x - newSeqFlow.waypoints[0].x).to.equal(seqFlowLabel.x - seqFlow.waypoints[0].x);
+ expect(newSeqFlow.label.y - newSeqFlow.waypoints[0].y).to.equal(seqFlowLabel.y - seqFlow.waypoints[0].y);
+ }));
+
+
+ it('should retain default & conditional flow property',
+ inject(function(elementRegistry, copyPaste, canvas, modeling) {
+ // given
+ var subProcess = elementRegistry.get('SubProcess_1kd6ist'),
+ rootElement = canvas.getRootElement(),
+ task, defaultFlow, conditionalFlow;
+
+ // when
+ copyPaste.copy(subProcess);
+
+ modeling.removeElements([ subProcess ]);
+
+ copyPaste.paste({
+ element: rootElement,
+ point: {
+ x: 1100,
+ y: 250
+ }
+ });
+
+ task = elementRegistry.filter(function(element) {
+ return element.type === 'bpmn:Task';
+ })[0];
+
+ defaultFlow = elementRegistry.filter(function(element) {
+ return !!(element.type === 'bpmn:SequenceFlow' && task.businessObject.default.id === element.id);
+ })[0];
+
+ conditionalFlow = elementRegistry.filter(function(element) {
+ return !!(element.type === 'bpmn:SequenceFlow' && element.businessObject.conditionExpression);
+ })[0];
+
+ expect(defaultFlow).to.exist;
+ expect(conditionalFlow).to.exist;
+ }));
+
+
+ it('should retain loop characteristics',
+ inject(function(elementRegistry, copyPaste, canvas, modeling) {
+ // given
+ var subProcess = elementRegistry.get('SubProcess_0gev7mx'),
+ rootElement = canvas.getRootElement(),
+ loopCharacteristics;
+
+ // when
+ copyPaste.copy(subProcess);
+
+ modeling.removeElements([ subProcess ]);
+
+ copyPaste.paste({
+ element: rootElement,
+ point: {
+ x: 1100,
+ y: 250
+ }
+ });
+
+ subProcess = elementRegistry.filter(function(element) {
+ return !!(element.id !== 'SubProcess_1kd6ist' && element.type === 'bpmn:SubProcess');
+ })[0];
+
+ loopCharacteristics = subProcess.businessObject.loopCharacteristics;
+
+ expect(loopCharacteristics.$type).to.equal('bpmn:MultiInstanceLoopCharacteristics');
+ expect(loopCharacteristics.isSequential).to.be.true;
+ }));
+
+
+ it('selected elements', inject(integrationTest([ 'SubProcess_1kd6ist' ])));
+
+ });
+
+ describe('rules', function() {
+
+ it('disallow individual boundary events copying', inject(function(copyPaste, elementRegistry, canvas) {
+
+ var boundaryEventA = elementRegistry.get('BoundaryEvent_1404oxd'),
+ boundaryEventB = elementRegistry.get('BoundaryEvent_1c94bi9'),
+ tree;
+
+ // when
+ copyPaste.copy([ boundaryEventA, boundaryEventB ]);
+
+ tree = new DescriptorTree(copyPaste._tree);
+
+ expect(tree.getLength()).to.equal(0);
+ }));
+ });
+
+ });
+
+ describe('basic collaboration', function() {
+
+ beforeEach(bootstrapModeler(collaborationXML, { modules: testModules }));
+
+ describe('integration', function() {
+
+ it('participant with including lanes + elements', inject(integrationTest([ 'Participant_0uu1rvj' ])));
+
+ it('collapsed pool', inject(integrationTest([ 'Participant_145muai' ])));
+
+ });
+
+ describe('rules', function () {
+
+ it('disallow individual lanes copying', inject(function(copyPaste, elementRegistry, canvas) {
+
+ var laneA = elementRegistry.get('Lane_13h648l'),
+ laneB = elementRegistry.get('Lane_1gl63sa'),
+ tree;
+
+ // when
+ copyPaste.copy([ laneA, laneB ]);
+
+ tree = new DescriptorTree(copyPaste._tree);
+
+ expect(tree.getLength()).to.equal(0);
+ }));
+
+ });
+
+ });
+
+ describe('complex collaboration', function() {
+
+ beforeEach(bootstrapModeler(collaborationMultipleXML, { modules: testModules }));
+
+ describe('integration', function() {
+
+ it('multiple participants', inject(integrationTest([ 'Participant_0pgdgt4', 'Participant_1id96b4' ])));
+
+ });
+
+ });
+
+});
diff --git a/test/spec/features/copy-paste/DescriptorTree.js b/test/spec/features/copy-paste/DescriptorTree.js
new file mode 100644
index 00000000..4564d1d2
--- /dev/null
+++ b/test/spec/features/copy-paste/DescriptorTree.js
@@ -0,0 +1,58 @@
+'use strict';
+
+var forEach = require('lodash/collection/forEach');
+
+
+function DescriptorTree(tree) {
+
+ this._tree = {};
+ this._length = 0;
+
+ forEach(tree, function(branch, depth) {
+ if (branch.length) {
+ this._length += 1;
+ }
+
+ forEach(branch, function(element) {
+
+ element.depth = parseInt(depth, 10);
+
+ this._tree[element.id] = element;
+ }, this);
+
+ }, this);
+}
+
+module.exports = DescriptorTree;
+
+DescriptorTree.prototype.getLength = function() {
+ return this._length;
+};
+
+DescriptorTree.prototype.getElement = function(id) {
+ return this._tree[id];
+};
+
+DescriptorTree.prototype.getDepth = function(depth) {
+ var newTree = {};
+
+ forEach(this._tree, function(element) {
+ if (element.depth === depth) {
+ newTree[element.id] = element;
+ }
+ });
+
+ return newTree;
+};
+
+DescriptorTree.prototype.getDepthLength = function(depth) {
+ var length = 0;
+
+ forEach(this._tree, function(element) {
+ if (element.depth === depth) {
+ length += 1;
+ }
+ });
+
+ return length;
+};
diff --git a/test/spec/features/keyboard/BpmnKeyBindingsSpec.js b/test/spec/features/keyboard/BpmnKeyBindingsSpec.js
index 07bbc067..b51e2c09 100644
--- a/test/spec/features/keyboard/BpmnKeyBindingsSpec.js
+++ b/test/spec/features/keyboard/BpmnKeyBindingsSpec.js
@@ -46,7 +46,7 @@ describe('features - keyboard', function() {
it('should include triggers inside editorActions', inject(function(editorActions) {
// then
- expect(editorActions.length()).to.equal(13);
+ expect(editorActions.length()).to.equal(15);
}));