diff --git a/lib/features/modeling/behavior/MoveStartEventBehavior.js b/lib/features/modeling/behavior/MoveStartEventBehavior.js deleted file mode 100644 index 1435b6cb..00000000 --- a/lib/features/modeling/behavior/MoveStartEventBehavior.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -var inherits = require('inherits'); - -var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); - -var forEach = require('lodash/collection/forEach'); - -var isEventSubProcess = require('../../../util/DiUtil').isEventSubProcess; - -/** - * Defines the behavior when a start event is moved - */ -function MoveStartEventBehavior(eventBus, bpmnReplace, bpmnRules, elementRegistry, selection) { - CommandInterceptor.call(this, eventBus); - - this.postExecuted([ 'elements.move' ], function(event) { - - var context = event.context, - target = context.newParent, - elements = []; - - forEach(context.closure.topLevel, function(topLevelElements) { - if (isEventSubProcess(topLevelElements)) { - elements = elements.concat(topLevelElements.children); - } else { - elements = elements.concat(topLevelElements); - } - }); - - var canReplace = bpmnRules.canReplace(elements, target); - - forEach(canReplace.replacements, function(replacements) { - - var newElement = { - type: replacements.newElementType - }; - - var oldElement = elementRegistry.get(replacements.oldElementId); - - var idx = elements.indexOf(oldElement); - elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false }); - - }); - - if (canReplace.replacements) { - selection.select(elements); - } - - }); -} - -MoveStartEventBehavior.$inject = [ 'eventBus', 'bpmnReplace', 'bpmnRules', 'elementRegistry', 'selection' ]; - -inherits(MoveStartEventBehavior, CommandInterceptor); - -module.exports = MoveStartEventBehavior; diff --git a/lib/features/modeling/behavior/ReplaceElementBehaviour.js b/lib/features/modeling/behavior/ReplaceElementBehaviour.js new file mode 100644 index 00000000..ad9988e2 --- /dev/null +++ b/lib/features/modeling/behavior/ReplaceElementBehaviour.js @@ -0,0 +1,107 @@ +'use strict'; + +var inherits = require('inherits'); + +var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); + +var forEach = require('lodash/collection/forEach'); + +var isEventSubProcess = require('../../../util/DiUtil').isEventSubProcess; +var is = require('../../../util/ModelUtil').is; + +/** + * Defines the behaviour of what happens to the elements inside a container + * that morphs into another BPMN element + */ +function ReplaceElementBehaviour(eventBus, bpmnReplace, bpmnRules, elementRegistry, selection, modeling) { + CommandInterceptor.call(this, eventBus); + + this._bpmnReplace = bpmnReplace; + this._elementRegistry = elementRegistry; + this._selection = selection; + this._modeling = modeling; + + this.postExecuted([ 'elements.move' ], 500, function(event) { + + var context = event.context, + target = context.newParent, + newHost = context.newHost, + elements = []; + + forEach(context.closure.topLevel, function(topLevelElements) { + if (isEventSubProcess(topLevelElements)) { + elements = elements.concat(topLevelElements.children); + } else { + elements = elements.concat(topLevelElements); + } + }); + + // Change target to host when the moving element is a `bpmn:BoundaryEvent` + if (elements.length === 1 && newHost) { + target = newHost; + } + + var canReplace = bpmnRules.canReplace(elements, target); + + if (canReplace) { + this.replaceElements(elements, canReplace.replacements, newHost); + } + }, this); + + // update attachments if the host is replaced + this.postExecute([ 'shape.replace' ], 1500, function(e) { + + var context = e.context, + oldShape = context.oldShape, + newShape = context.newShape, + attachers = oldShape.attachers, + canReplace; + + if (attachers && attachers.length) { + canReplace = bpmnRules.canReplace(attachers, newShape); + + this.replaceElements(attachers, canReplace.replacements); + } + + }, this); +} + +inherits(ReplaceElementBehaviour, CommandInterceptor); + + +ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements, newHost) { + var elementRegistry = this._elementRegistry, + bpmnReplace = this._bpmnReplace, + selection = this._selection, + modeling = this._modeling; + + forEach(newElements, function(replacement) { + + var newElement = { + type: replacement.newElementType + }; + + var oldElement = elementRegistry.get(replacement.oldElementId); + + if (newHost && is(oldElement, 'bpmn:BoundaryEvent')) { + modeling.updateAttachment(oldElement, null); + } + + var idx = elements.indexOf(oldElement); + + elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false }); + + if (newHost && is(elements[idx], 'bpmn:BoundaryEvent')) { + modeling.updateAttachment(elements[idx], newHost); + } + }); + + if (newElements) { + selection.select(elements); + } +}; + +ReplaceElementBehaviour.$inject = [ 'eventBus', 'bpmnReplace', 'bpmnRules', 'elementRegistry', + 'selection', 'modeling' ]; + +module.exports = ReplaceElementBehaviour; diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 21601101..9a01af93 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -6,7 +6,7 @@ module.exports = { 'createOnFlowBehavior', 'createParticipantBehavior', 'modelingFeedback', - 'moveStartEventBehavior', + 'replaceElementBehaviour', 'removeParticipantBehavior', 'replaceConnectionBehavior' ], @@ -16,7 +16,7 @@ module.exports = { createOnFlowBehavior: [ 'type', require('./CreateOnFlowBehavior') ], createParticipantBehavior: [ 'type', require('./CreateParticipantBehavior') ], modelingFeedback: [ 'type', require('./ModelingFeedback') ], - moveStartEventBehavior: [ 'type', require('./MoveStartEventBehavior') ], + replaceElementBehaviour: [ 'type', require('./ReplaceElementBehaviour') ], removeParticipantBehavior: [ 'type', require('./RemoveParticipantBehavior') ], replaceConnectionBehavior: [ 'type', require('./ReplaceConnectionBehavior') ] }; diff --git a/lib/features/replace/BpmnReplace.js b/lib/features/replace/BpmnReplace.js index 77353d8a..071fc03d 100644 --- a/lib/features/replace/BpmnReplace.js +++ b/lib/features/replace/BpmnReplace.js @@ -289,6 +289,10 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin // filter for boundary events if (is(businessObject, 'bpmn:BoundaryEvent')) { + if (target.eventDefinition == 'bpmn:CancelEventDefinition' && + !is(businessObject.attachedToRef, 'bpmn:Transaction')) { + return false; + } var cancelActivity = target.cancelActivity !== false; var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity; @@ -305,6 +309,11 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin return !(isEventDefinitionEqual && isEventDefinitionEqual && isInterruptingEqual); } + if (is(businessObject, 'bpmn:EndEvent') && target.eventDefinition == 'bpmn:CancelEventDefinition' && + !is(businessObject.$parent, 'bpmn:Transaction')) { + return false; + } + // filter for all other elements return (!isEventDefinitionEqual && isEventTypeEqual) || !isEventTypeEqual; } diff --git a/lib/features/replace/ReplaceOptions.js b/lib/features/replace/ReplaceOptions.js index f9869b20..7613d4ea 100644 --- a/lib/features/replace/ReplaceOptions.js +++ b/lib/features/replace/ReplaceOptions.js @@ -533,6 +533,15 @@ module.exports.BOUNDARY_EVENT = [ eventDefinition: 'bpmn:ErrorEventDefinition' } }, + { + label: 'Cancel Boundary Event', + actionName: 'replace-with-cancel-boundary', + className: 'icon-intermediate-event-catch-cancel', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + } + }, { label: 'Signal Boundary Event', actionName: 'replace-with-signal-boundary', diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js index e842ab87..df0ee7fc 100644 --- a/lib/features/rules/BpmnRules.js +++ b/lib/features/rules/BpmnRules.js @@ -77,7 +77,7 @@ BpmnRules.prototype.init = function() { position = context.position; return canAttach(shapes, target, null, position) || - canReplace(shapes, target) || + canReplace(shapes, target, position) || canMove(shapes, target, position); }); @@ -286,7 +286,7 @@ function canConnect(source, target, connection) { * * @return {Boolean} */ -function canDrop(element, target) { +function canDrop(element, target, position) { // can move labels everywhere if (isLabel(element) && !isConnection(target)) { @@ -346,6 +346,9 @@ function isBoundaryCandidate(element) { function canAttach(elements, target, source, position) { + if (!Array.isArray(elements)) { + elements = [ elements ]; + } // disallow appending as boundary event if (source) { @@ -412,7 +415,7 @@ function canAttach(elements, target, source, position) { * * @return {Object} an object containing all elements which have to be replaced */ -function canReplace(elements, target) { +function canReplace(elements, target, position) { if (!target) { return false; @@ -439,6 +442,26 @@ function canReplace(elements, target) { }); } } + + if (!is(target, 'bpmn:Transaction')) { + if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') && + element.type !== 'label') { + + if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) { + canExecute.replacements.push({ + oldElementId: element.id, + newElementType: 'bpmn:EndEvent' + }); + } + + if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) { + canExecute.replacements.push({ + oldElementId: element.id, + newElementType: 'bpmn:BoundaryEvent' + }); + } + } + } }); return canExecute.replacements.length ? canExecute : false; diff --git a/test/fixtures/bpmn/features/replace/cancel-events.bpmn b/test/fixtures/bpmn/features/replace/cancel-events.bpmn new file mode 100644 index 00000000..4e781881 --- /dev/null +++ b/test/fixtures/bpmn/features/replace/cancel-events.bpmn @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/MoveStartEventBehaviorSpec.js b/test/spec/features/modeling/behavior/MoveStartEventBehaviorSpec.js deleted file mode 100644 index 525f3d3f..00000000 --- a/test/spec/features/modeling/behavior/MoveStartEventBehaviorSpec.js +++ /dev/null @@ -1,95 +0,0 @@ -'use strict'; - -var TestHelper = require('../../../../TestHelper'); - -/* global bootstrapModeler, inject */ - -var replacePreviewModule = require('../../../../../lib/features/replace-preview'), - modelingModule = require('../../../../../lib/features/modeling'), - coreModule = require('../../../../../lib/core'); - -var is = require('../../../../../lib/util/ModelUtil').is, - canvasEvent = require('../../../../util/MockEvents').createCanvasEvent; - - -describe('features/modeling - move start event behavior', function() { - - var testModules = [ replacePreviewModule, modelingModule, coreModule ]; - - var diagramXML = require('../../../../fixtures/bpmn/event-sub-processes.bpmn'); - - var moveShape; - - beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); - - beforeEach(inject(function(move, dragging, elementRegistry) { - - moveShape = function(shape, target, position) { - var startPosition = { x: shape.x + 10 + shape.width / 2, y: shape.y + 30 + shape.height/2 }; - - move.start(canvasEvent(startPosition), shape); - - dragging.hover({ - element: target, - gfx: elementRegistry.getGraphics(target) - }); - - dragging.move(canvasEvent(position)); - }; - })); - - - it('should select the replacement after replacing the start event', - inject(function(elementRegistry, canvas, dragging, move, selection) { - - // given - var startEvent = elementRegistry.get('StartEvent_1'), - rootElement = canvas.getRootElement(); - - // when - moveShape(startEvent, rootElement, { x: 140, y: 250 }); - - dragging.end(); - - var replacement = elementRegistry.filter(function(element) { - if(is(element, 'bpmn:StartEvent') && element.parent === rootElement) { - return true; - } - })[0]; - - // then - expect(selection.get()).to.include(replacement); - expect(selection.get()).not.to.include(startEvent); - - })); - - - it('should select all moved shapes after some of them got replaced', - inject(function(elementRegistry, canvas, dragging, move, selection) { - - // given - var startEvent1 = elementRegistry.get('StartEvent_1'), - startEvent2 = elementRegistry.get('StartEvent_2'), - startEvent3 = elementRegistry.get('StartEvent_3'), - rootElement = canvas.getRootElement(); - - // when - selection.select([ startEvent1, startEvent2, startEvent3 ]); - moveShape(startEvent1, rootElement, { x: 140, y: 250 }); - - dragging.end(); - - var replacements = elementRegistry.filter(function(element) { - if(is(element, 'bpmn:StartEvent') && element.type !== 'label') { - return true; - } - }); - - // then - expect(selection.get()).to.include(replacements[0]); - expect(selection.get()).to.include(replacements[1]); - expect(selection.get()).to.include(replacements[2]); - - })); - -}); diff --git a/test/spec/features/modeling/behavior/ReplaceElementBehaviourSpec.js b/test/spec/features/modeling/behavior/ReplaceElementBehaviourSpec.js new file mode 100644 index 00000000..9d63e1d7 --- /dev/null +++ b/test/spec/features/modeling/behavior/ReplaceElementBehaviourSpec.js @@ -0,0 +1,351 @@ +'use strict'; + +var TestHelper = require('../../../../TestHelper'); + +/* global bootstrapModeler, inject */ + +var replacePreviewModule = require('../../../../../lib/features/replace-preview'), + modelingModule = require('../../../../../lib/features/modeling'), + moveModule = require('diagram-js/lib/features/move'), + coreModule = require('../../../../../lib/core'); + +var is = require('../../../../../lib/util/ModelUtil').is, + canvasEvent = require('../../../../util/MockEvents').createCanvasEvent; + + +describe('features/modeling - move start event behavior', function() { + + var testModules = [ replacePreviewModule, modelingModule, coreModule, moveModule ]; + + describe('Start Events', function() { + var diagramXML = require('../../../../fixtures/bpmn/event-sub-processes.bpmn'); + + var moveShape; + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + beforeEach(inject(function(move, dragging, elementRegistry) { + + moveShape = function(shape, target, position) { + var startPosition = { x: shape.x + 10 + shape.width / 2, y: shape.y + 30 + shape.height/2 }; + + move.start(canvasEvent(startPosition), shape); + + dragging.hover({ + element: target, + gfx: elementRegistry.getGraphics(target) + }); + + dragging.move(canvasEvent(position)); + }; + })); + + + it('should select the replacement after replacing the start event', + inject(function(elementRegistry, canvas, dragging, move, selection) { + + // given + var startEvent = elementRegistry.get('StartEvent_1'), + rootElement = canvas.getRootElement(); + + // when + moveShape(startEvent, rootElement, { x: 140, y: 250 }); + + dragging.end(); + + var replacement = elementRegistry.filter(function(element) { + if(is(element, 'bpmn:StartEvent') && element.parent === rootElement) { + return true; + } + })[0]; + + // then + expect(selection.get()).to.include(replacement); + expect(selection.get()).not.to.include(startEvent); + + })); + + + it('should select all moved shapes after some of them got replaced', + inject(function(elementRegistry, canvas, dragging, move, selection) { + + // given + var startEvent1 = elementRegistry.get('StartEvent_1'), + startEvent2 = elementRegistry.get('StartEvent_2'), + startEvent3 = elementRegistry.get('StartEvent_3'), + rootElement = canvas.getRootElement(); + + // when + selection.select([ startEvent1, startEvent2, startEvent3 ]); + moveShape(startEvent1, rootElement, { x: 140, y: 250 }); + + dragging.end(); + + var replacements = elementRegistry.filter(function(element) { + if(is(element, 'bpmn:StartEvent') && element.type !== 'label') { + return true; + } + }); + + // then + expect(selection.get()).to.include(replacements[0]); + expect(selection.get()).to.include(replacements[1]); + expect(selection.get()).to.include(replacements[2]); + + })); + + }); + + describe('Cancel Events', function () { + + var diagramXML = require('../../../../fixtures/bpmn/features/replace/cancel-events.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + describe('- normal -', function() { + + it('should replace CancelEvent when morphing transaction', + inject(function(elementRegistry, bpmnReplace) { + // given + var transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + var newEndEvent = subProcess.children[0].businessObject; + + // then + expect(subProcess.children).to.have.length(2); + expect(newEndEvent.eventDefinitions).to.not.exist; + })); + + + it('should replace CancelEvent when morphing transaction -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { + // given + var transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + commandStack.undo(); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(2); + expect(endEventAfter.businessObject.eventDefinitions).to.exist; + })); + + + it('should replace a CancelEvent when moving outside of a transaction', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + var cancelEvent = bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.parent === process && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(0); + expect(endEventAfter.businessObject.eventDefinitions).to.not.exist; + })); + + + it('should replace a CancelEvent when moving outside of a transaction -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + var cancelEvent = bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process); + + commandStack.undo(); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(2); + expect(endEventAfter.businessObject.eventDefinitions).to.exist; + })); + + }); + + describe('- boundary events -', function() { + + it('should replace CancelBoundaryEvent when morphing from a transaction', + inject(function(elementRegistry, bpmnReplace) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + var newBoundaryEvent = subProcess.attachers[0].businessObject; + + // then + expect(newBoundaryEvent.eventDefinitions).to.not.exist; + expect(newBoundaryEvent.attachedToRef).to.equal(subProcess.businessObject); + expect(elementRegistry.get('Transaction_1')).to.not.exist; + })); + + + it('should replace CancelBoundaryEvent when morphing from a transaction -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + commandStack.undo(); + + var afterBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(afterBoundaryEvent.businessObject.eventDefinitions).exist; + expect(afterBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject); + expect(transaction.attachers).to.have.length(1); + })); + + + it('should replace CancelBoundaryEvent when attaching to a NON-transaction', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + subProcess = elementRegistry.get('SubProcess_1'), + process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, true); + + var movedBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(movedBoundaryEvent.businessObject.eventDefinitions).to.not.exist; + expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(subProcess.businessObject); + expect(movedBoundaryEvent.parent).to.equal(process); + + expect(movedBoundaryEvent.host).to.equal(subProcess); + expect(subProcess.attachers).to.contain(movedBoundaryEvent); + expect(transaction.attachers).to.be.empty; + })); + + + it('should replace CancelBoundaryEvent when attaching to a NON-transaction -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'), + subProcess = elementRegistry.get('SubProcess_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, true); + + commandStack.undo(); + + var movedBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(movedBoundaryEvent.businessObject.eventDefinitions).to.exist; + expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject); + + expect(movedBoundaryEvent.host).to.equal(transaction); + expect(transaction.attachers).to.contain(movedBoundaryEvent); + expect(subProcess.attachers).to.have.length(1); + })); + + it('should NOT allow morphing into an IntermediateEvent', + inject(function(elementRegistry, bpmnReplace, commandStack, move, dragging) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + subProcess = elementRegistry.get('SubProcess_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + move.start(canvasEvent({ x: 0, y: 0 }), newBoundaryEvent); + + dragging.hover({ + gfx: elementRegistry.getGraphics(subProcess), + element: subProcess + }); + dragging.move(canvasEvent({ x: 450, y: -50 })); + + var canExecute = dragging.active().data.context.canExecute; + + // then + expect(canExecute).to.be.false; + })); + + }); + + }); + +}); diff --git a/test/spec/features/popup-menu/PopupMenuSpec.js b/test/spec/features/popup-menu/PopupMenuSpec.js index f2efd201..6a9f4af3 100644 --- a/test/spec/features/popup-menu/PopupMenuSpec.js +++ b/test/spec/features/popup-menu/PopupMenuSpec.js @@ -618,7 +618,7 @@ describe('features/popup-menu', function() { // then expect(queryEntry(popupMenu, 'replace-with-none-end')).to.be.null; - expect(entriesContainer.childNodes.length).to.equal(9); + expect(entriesContainer.childNodes.length).to.equal(8); })); diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index ec689366..fe9f7c85 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -6,8 +6,12 @@ var TestHelper = require('../../../TestHelper'); var modelingModule = require('../../../../lib/features/modeling'), replaceModule = require('../../../../lib/features/replace'), - coreModule = require('../../../../lib/core'), - is = require('../../../../lib/util/ModelUtil').is, + moveModule = require('diagram-js/lib/features/move'), + coreModule = require('../../../../lib/core'); + +var canvasEvent = require('../../../util/MockEvents').createCanvasEvent; + +var is = require('../../../../lib/util/ModelUtil').is, isExpanded = require('../../../../lib/util/DiUtil').isExpanded, isInterrupting = require('../../../../lib/util/DiUtil').isInterrupting, isEventSubProcess = require('../../../../lib/util/DiUtil').isEventSubProcess; @@ -15,7 +19,7 @@ var modelingModule = require('../../../../lib/features/modeling'), describe('features/replace', function() { - var testModules = [ coreModule, modelingModule, replaceModule ]; + var testModules = [ coreModule, modelingModule, replaceModule, moveModule ]; describe('should replace', function() { @@ -857,4 +861,256 @@ describe('features/replace', function() { }); + + describe('Cancel Events', function () { + + var diagramXML = require('../../../fixtures/bpmn/features/replace/cancel-events.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + describe('- normal -', function() { + + it('should replace CancelEvent when morphing transaction', + inject(function(elementRegistry, bpmnReplace) { + // given + var transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + var newEndEvent = subProcess.children[0].businessObject; + + // then + expect(subProcess.children).to.have.length(2); + expect(newEndEvent.eventDefinitions).to.not.exist; + })); + + + it('should replace CancelEvent when morphing transaction -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { + // given + var transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + commandStack.undo(); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(2); + expect(endEventAfter.businessObject.eventDefinitions).to.exist; + })); + + + it('should replace a CancelEvent when moving outside of a transaction', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + var cancelEvent = bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.parent === process && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(0); + expect(endEventAfter.businessObject.eventDefinitions).to.not.exist; + })); + + + it('should replace a CancelEvent when moving outside of a transaction -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'), + endEvent = elementRegistry.get('EndEvent_1'); + + // when + var cancelEvent = bpmnReplace.replaceElement(endEvent, { + type: 'bpmn:EndEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ cancelEvent ], { x: 0, y: 150 }, process); + + commandStack.undo(); + + var endEventAfter = elementRegistry.filter(function(element) { + return (element.id !== 'EndEvent_2' && element.type === 'bpmn:EndEvent'); + })[0]; + + // then + expect(transaction.children).to.have.length(2); + expect(endEventAfter.businessObject.eventDefinitions).to.exist; + })); + + }); + + describe('- boundary events -', function() { + + it('should replace CancelBoundaryEvent when morphing from a transaction', + inject(function(elementRegistry, bpmnReplace) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + var subProcess = bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + var newBoundaryEvent = subProcess.attachers[0].businessObject; + + // then + expect(newBoundaryEvent.eventDefinitions).to.not.exist; + expect(newBoundaryEvent.attachedToRef).to.equal(subProcess.businessObject); + expect(elementRegistry.get('Transaction_1')).to.not.exist; + })); + + + it('should replace CancelBoundaryEvent when morphing from a transaction -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + bpmnReplace.replaceElement(transaction, { type: 'bpmn:SubProcess' }); + + commandStack.undo(); + + var afterBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(afterBoundaryEvent.businessObject.eventDefinitions).exist; + expect(afterBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject); + expect(transaction.attachers).to.have.length(1); + })); + + + it('should replace CancelBoundaryEvent when attaching to a NON-transaction', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + subProcess = elementRegistry.get('SubProcess_1'), + process = elementRegistry.get('Process_1'), + transaction = elementRegistry.get('Transaction_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, true); + + var movedBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(movedBoundaryEvent.businessObject.eventDefinitions).to.not.exist; + expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(subProcess.businessObject); + expect(movedBoundaryEvent.parent).to.equal(process); + + expect(movedBoundaryEvent.host).to.equal(subProcess); + expect(subProcess.attachers).to.contain(movedBoundaryEvent); + expect(transaction.attachers).to.be.empty; + })); + + + it('should replace CancelBoundaryEvent when attaching to a NON-transaction -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + transaction = elementRegistry.get('Transaction_1'), + subProcess = elementRegistry.get('SubProcess_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + modeling.moveElements([ newBoundaryEvent ], { x: 500, y: 0 }, subProcess, true); + + commandStack.undo(); + + var movedBoundaryEvent = elementRegistry.filter(function(element) { + return (element.type === 'bpmn:BoundaryEvent' && element.id !== 'BoundaryEvent_2'); + })[0]; + + // then + expect(movedBoundaryEvent.businessObject.eventDefinitions).to.exist; + expect(movedBoundaryEvent.businessObject.attachedToRef).to.equal(transaction.businessObject); + + expect(movedBoundaryEvent.host).to.equal(transaction); + expect(transaction.attachers).to.contain(movedBoundaryEvent); + expect(subProcess.attachers).to.have.length(1); + })); + + it('should NOT allow morphing into an IntermediateEvent', + inject(function(elementRegistry, bpmnReplace, commandStack, move, dragging) { + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + subProcess = elementRegistry.get('SubProcess_1'); + + // when + var newBoundaryEvent = bpmnReplace.replaceElement(boundaryEvent, { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:CancelEventDefinition' + }); + + move.start(canvasEvent({ x: 0, y: 0 }), newBoundaryEvent); + + dragging.hover({ + gfx: elementRegistry.getGraphics(subProcess), + element: subProcess + }); + dragging.move(canvasEvent({ x: 450, y: -50 })); + + var canExecute = dragging.active().data.context.canExecute; + + expect(canExecute).to.be.false; + })); + + }); + + }); + });