diff --git a/lib/features/modeling/BpmnUpdater.js b/lib/features/modeling/BpmnUpdater.js index d6066412..57f4d8af 100644 --- a/lib/features/modeling/BpmnUpdater.js +++ b/lib/features/modeling/BpmnUpdater.js @@ -127,7 +127,7 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) { // attach / detach connection function updateConnection(e) { - self.updateConnection(e.context.connection); + self.updateConnection(e.context); } this.executed([ @@ -168,6 +168,71 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) { 'connection.reconnectStart' ], ifBpmn(updateConnectionWaypoints)); + + // update Default & Conditional flows + this.executed([ + 'connection.reconnectEnd', + 'connection.reconnectStart' + ], ifBpmn(function(e) { + var context = e.context, + connection = context.connection, + businessObject = getBusinessObject(connection), + oldSource = getBusinessObject(context.oldSource), + oldTarget = getBusinessObject(context.oldTarget), + newSource = getBusinessObject(connection.source), + newTarget = getBusinessObject(connection.target); + + if (oldSource === newSource || oldTarget === newTarget) { + return; + } + + // on reconnectStart -> default flow + if (oldSource && oldSource.default) { + context.default = oldSource.default; + oldSource.default = undefined; + } + + // on reconnectEnd -> default flow + if ((businessObject.sourceRef && businessObject.sourceRef.default) && !is(newTarget, 'bpmn:Activity')) { + context.default = businessObject.sourceRef.default; + businessObject.sourceRef.default = undefined; + } + + // on reconnectStart -> condtional flow + if ((businessObject.conditionExpression) && is(oldSource, 'bpmn:Activity')) { + context.conditionExpression = businessObject.conditionExpression; + businessObject.conditionExpression = undefined; + } + + // on reconnectEnd -> condtional flow + if ((businessObject.conditionExpression) && !is(newTarget, 'bpmn:Activity')) { + context.conditionExpression = businessObject.conditionExpression; + businessObject.conditionExpression = undefined; + } + })); + + this.reverted([ + 'connection.reconnectEnd', + 'connection.reconnectStart' + ], ifBpmn(function(e) { + var context = e.context, + connection = context.connection, + businessObject = getBusinessObject(connection), + newSource = getBusinessObject(connection.source); + + // default flow + if (context.default) { + if (is(newSource, 'bpmn:ExclusiveGateway') || is(newSource, 'bpmn:InclusiveGateway')) { + newSource.default = context.default; + } + } + + // conditional flow + if (context.conditionExpression && is(newSource, 'bpmn:Activity')) { + businessObject.conditionExpression = context.conditionExpression; + } + })); + // update attachments function updateAttachment(e) { self.updateAttachment(e.context); @@ -428,9 +493,10 @@ BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) { }; -BpmnUpdater.prototype.updateConnection = function(connection) { +BpmnUpdater.prototype.updateConnection = function(context) { - var businessObject = getBusinessObject(connection), + var connection = context.connection, + businessObject = getBusinessObject(connection), newSource = getBusinessObject(connection.source), newTarget = getBusinessObject(connection.target); @@ -492,4 +558,4 @@ function ifBpmn(fn) { fn(event); } }; -} \ No newline at end of file +} diff --git a/lib/features/replace/BpmnReplace.js b/lib/features/replace/BpmnReplace.js index e1a72e2b..69b82f35 100644 --- a/lib/features/replace/BpmnReplace.js +++ b/lib/features/replace/BpmnReplace.js @@ -102,6 +102,13 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin businessObject.loopCharacteristics = oldBusinessObject.loopCharacteristics; } + // retain default flow's reference between inclusive and exclusive gateways + if ((is(oldBusinessObject, 'bpmn:ExclusiveGateway') || is(oldBusinessObject, 'bpmn:InclusiveGateway')) && + (is(businessObject, 'bpmn:ExclusiveGateway') || is(businessObject, 'bpmn:InclusiveGateway'))) + { + businessObject.default = oldBusinessObject.default; + } + newElement = replace.replaceElement(element, newElement); if (hints.select !== false) { @@ -375,7 +382,7 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin // Add entries to replace menu forEach(filteredEntries, function(definition) { var entry = addMenuEntry(definition); - + menuEntries.push(entry); }); } diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index a3720f0d..41008604 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -9,8 +9,6 @@ var modelingModule = require('../../../../lib/features/modeling'), 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, @@ -975,7 +973,8 @@ describe('features/replace', function() { })); - it('should replace SequenceFlow with DefaultFlow -> undo', inject(function(elementRegistry, bpmnReplace, commandStack) { + it('should replace SequenceFlow with DefaultFlow -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { // given var sequenceFlow = elementRegistry.get('SequenceFlow_3'); @@ -1013,6 +1012,152 @@ describe('features/replace', function() { expect(gateway.businessObject.default).to.equal(sequenceFlow.businessObject); })); + + it('should replace DefaultFlow with SequenceFlow when changing source', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_1'), + task = elementRegistry.get('Task_2'); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectStart(sequenceFlow, task, [ + { x: 686, y: 267, original: { x: 686, y: 307 } }, + { x: 686, y: 207, original: { x: 686, y: 187 } } + ]); + + var gateway = elementRegistry.get('ExclusiveGateway_1'); + + // then + expect(gateway.businessObject.default).to.not.exist; + })); + + + it('should replace DefaultFlow with SequenceFlow when changing source -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_1'), + task = elementRegistry.get('Task_2'); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectStart(sequenceFlow, task, [ + { x: 686, y: 267, original: { x: 686, y: 307 } }, + { x: 686, y: 207, original: { x: 686, y: 187 } } + ]); + + commandStack.undo(); + + var gateway = elementRegistry.get('ExclusiveGateway_1'); + + // then + expect(gateway.businessObject.default).equal(sequenceFlow.businessObject); + })); + + + it('should replace DefaultFlow with SequenceFlow when changing target', + inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_1'), + root = canvas.getRootElement(); + + var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'}); + + modeling.createShape(intermediateEvent, { x: 686, y: 50 }, root); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectEnd(sequenceFlow, intermediateEvent, [ + { x: 686, y: 267, original: { x: 686, y: 307 } }, + { x: 686, y: 50, original: { x: 686, y: 75 } } + ]); + + var gateway = elementRegistry.get('ExclusiveGateway_1'); + + // then + expect(gateway.businessObject.default).to.not.exist; + })); + + + it('should replace DefaultFlow with SequenceFlow when changing target -> undo', + inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling, commandStack) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_1'), + root = canvas.getRootElement(); + + var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'}); + + modeling.createShape(intermediateEvent, { x: 686, y: 50 }, root); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectEnd(sequenceFlow, intermediateEvent, [ + { x: 686, y: 267, original: { x: 686, y: 307 } }, + { x: 686, y: 50, original: { x: 686, y: 75 } } + ]); + + commandStack.undo(); + + var gateway = elementRegistry.get('ExclusiveGateway_1'); + + // then + expect(gateway.businessObject.default).equal(sequenceFlow.businessObject); + })); + + + it('should keep DefaultFlow when morphing Gateway', inject(function(elementRegistry, bpmnReplace) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + exclusiveGateway = elementRegistry.get('ExclusiveGateway_1'); + + // when + var opts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + opts[0].action(); + + var inclusiveGateway = bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway'}); + + // then + expect(inclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject); + })); + + + it('should keep DefaultFlow when morphing Gateway -> undo', + inject(function(elementRegistry, bpmnReplace, commandStack) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + exclusiveGateway = elementRegistry.get('ExclusiveGateway_1'); + + // when + var opts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger DefaultFlow replacement + opts[0].action(); + + bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway'}); + + commandStack.undo(); + + // then + expect(exclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject); + })); }); @@ -1098,6 +1243,104 @@ describe('features/replace', function() { expect(sequenceFlow.businessObject.conditionExpression).to.not.exist; })); + + it('should replace ConditionalFlow with SequenceFlow when changing source', + inject(function(elementRegistry, bpmnReplace, modeling) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + startEvent = elementRegistry.get('StartEvent_1'); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger ConditionalFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectStart(sequenceFlow, startEvent, [ + { x: 196, y: 197, original: { x: 178, y: 197 } }, + { x: 497, y: 278, original: { x: 547, y: 278 } } + ]); + + // then + expect(sequenceFlow.businessObject.conditionExpression).to.not.exist; + })); + + + it('should replace ConditionalFlow with SequenceFlow when changing source -> undo', + inject(function(elementRegistry, bpmnReplace, modeling, commandStack) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + startEvent = elementRegistry.get('StartEvent_1'); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger ConditionalFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectStart(sequenceFlow, startEvent, [ + { x: 196, y: 197, original: { x: 178, y: 197 } }, + { x: 497, y: 278, original: { x: 547, y: 278 } } + ]); + + commandStack.undo(); + + // then + expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression'); + })); + + + it('should replace ConditionalFlow with SequenceFlow when changing target', + inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + root = canvas.getRootElement(), + intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'}); + + modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger ConditionalFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectEnd(sequenceFlow, intermediateEvent, [ + { x: 389, y: 197, original: { x: 389, y: 197 } }, + { x: 497, y: 197, original: { x: 497, y: 197 } } + ]); + + // then + expect(sequenceFlow.businessObject.conditionExpression).to.not.exist; + })); + + + it('should replace ConditionalFlow with SequenceFlow when changing target -> undo', + inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling, commandStack) { + // given + var sequenceFlow = elementRegistry.get('SequenceFlow_3'), + root = canvas.getRootElement(), + intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'}); + + modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root); + + var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow); + + // trigger ConditionalFlow replacement + sequenceFlowOpts[0].action(); + + // when + modeling.reconnectEnd(sequenceFlow, intermediateEvent, [ + { x: 389, y: 197, original: { x: 389, y: 197 } }, + { x: 497, y: 197, original: { x: 497, y: 197 } } + ]); + + commandStack.undo(); + + // then + expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression'); + })); + }); });