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); }));