diff --git a/lib/features/modeling/behavior/MessageFlowBehavior.js b/lib/features/modeling/behavior/MessageFlowBehavior.js new file mode 100644 index 00000000..44f52f5c --- /dev/null +++ b/lib/features/modeling/behavior/MessageFlowBehavior.js @@ -0,0 +1,89 @@ +import inherits from 'inherits'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { is } from '../../../util/ModelUtil'; + +import { isExpanded } from '../../../util/DiUtil'; + +import { selfAndAllChildren } from 'diagram-js/lib/util/Elements'; + +import { + getResizedSourceAnchor, + getResizedTargetAnchor +} from 'diagram-js/lib/features/modeling/cmd/helper/AnchorsHelper'; + +/** + * BPMN-specific message flow behavior. + */ +export default function MessageFlowBehavior(eventBus, modeling) { + + CommandInterceptor.call(this, eventBus); + + this.postExecute('shape.replace', function(context) { + var oldShape = context.oldShape, + newShape = context.newShape; + + if (!isParticipantCollapse(oldShape, newShape)) { + return; + } + + var messageFlows = getMessageFlows(oldShape); + + messageFlows.incoming.forEach(function(incoming) { + var anchor = getResizedTargetAnchor(incoming, newShape, oldShape); + + modeling.reconnectEnd(incoming, newShape, anchor); + }); + + messageFlows.outgoing.forEach(function(outgoing) { + var anchor = getResizedSourceAnchor(outgoing, newShape, oldShape); + + modeling.reconnectStart(outgoing, newShape, anchor); + }); + }, true); + +} + +MessageFlowBehavior.$inject = [ 'eventBus', 'modeling' ]; + +inherits(MessageFlowBehavior, CommandInterceptor); + +// helpers ////////// + +function isParticipantCollapse(oldShape, newShape) { + return is(oldShape, 'bpmn:Participant') + && isExpanded(oldShape) + && is(newShape, 'bpmn:Participant') + && !isExpanded(newShape); +} + +function getMessageFlows(parent) { + var elements = selfAndAllChildren([ parent ], false); + + var incoming = [], + outgoing = []; + + elements.forEach(function(element) { + if (element === parent) { + return; + } + + element.incoming.forEach(function(connection) { + if (is(connection, 'bpmn:MessageFlow')) { + incoming.push(connection); + } + }); + + element.outgoing.forEach(function(connection) { + if (is(connection, 'bpmn:MessageFlow')) { + outgoing.push(connection); + } + }); + }, []); + + return { + incoming: incoming, + outgoing: outgoing + }; +} \ No newline at end of file diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 6115259d..6c99ee6f 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -18,6 +18,7 @@ import GroupBehavior from './GroupBehavior'; import ImportDockingFix from './ImportDockingFix'; import IsHorizontalFix from './IsHorizontalFix'; import LabelBehavior from './LabelBehavior'; +import MessageFlowBehavior from './MessageFlowBehavior'; import ModelingFeedback from './ModelingFeedback'; import ReplaceConnectionBehavior from './ReplaceConnectionBehavior'; import RemoveParticipantBehavior from './RemoveParticipantBehavior'; @@ -54,6 +55,7 @@ export default { 'importDockingFix', 'isHorizontalFix', 'labelBehavior', + 'messageFlowBehavior', 'modelingFeedback', 'removeElementBehavior', 'removeParticipantBehavior', @@ -88,6 +90,7 @@ export default { importDockingFix: [ 'type', ImportDockingFix ], isHorizontalFix: [ 'type', IsHorizontalFix ], labelBehavior: [ 'type', LabelBehavior ], + messageFlowBehavior: [ 'type', MessageFlowBehavior ], modelingFeedback: [ 'type', ModelingFeedback ], replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ], removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ], diff --git a/test/spec/features/modeling/behavior/MessageFlowBehavior.bpmn b/test/spec/features/modeling/behavior/MessageFlowBehavior.bpmn new file mode 100644 index 00000000..919234d8 --- /dev/null +++ b/test/spec/features/modeling/behavior/MessageFlowBehavior.bpmn @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/MessageFlowBehaviorSpec.js b/test/spec/features/modeling/behavior/MessageFlowBehaviorSpec.js new file mode 100644 index 00000000..8b0942f2 --- /dev/null +++ b/test/spec/features/modeling/behavior/MessageFlowBehaviorSpec.js @@ -0,0 +1,151 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; + + +describe('features/modeling - message flow behavior', function() { + + var testModules = [ coreModule, modelingModule ]; + + + describe('when collapsing participant', function() { + + var processDiagramXML = require('./MessageFlowBehavior.bpmn'); + + beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + + + it('should reconnect message flows to collapsed participant (incoming)', inject( + function(bpmnReplace, elementRegistry) { + + // given + var participant = elementRegistry.get('Participant_1'); + + // when + participant = bpmnReplace.replaceElement(participant, { + type: 'bpmn:Participant', + isExpanded: false + }); + + // then + expect(participant.incoming).to.have.length(2); + + expect(elementRegistry.get('Flow_1').waypoints).to.eql([ + { + original: { + x: 350, + y: 520 + }, + x: 350, + y: 480 + }, + { + original: { + x: 350, + y: 110 + }, + x: 350, + y: 140 + } + ]); + + expect(elementRegistry.get('Flow_2').waypoints).to.eql([ + { + original: { + x: 790, + y: 520 + }, + x: 790, + y: 480 + }, + { + x: 790, + y: 360 + }, + { + x: 370, + y: 360 + }, + { + original: { + x: 370, + y: 110 + }, + x: 370, + y: 140 + } + ]); + } + )); + + + it('should reconnect message flows to collapsed participant (outgoing)', inject( + function(bpmnReplace, elementRegistry) { + + // given + var participant = elementRegistry.get('Participant_4'); + + // when + participant = bpmnReplace.replaceElement(participant, { + type: 'bpmn:Participant', + isExpanded: false + }); + + // then + expect(participant.outgoing).to.have.length(2); + + expect(elementRegistry.get('Flow_3').waypoints).to.eql([ + { + original: { + x: 780, + y: 750 + }, + x: 780, + y: 720 + }, + { + x: 780, + y: 680 + }, + { + x: 360, + y: 680 + }, + { + original: { + x: 360, + y: 520 + }, + x: 360, + y: 560 + } + ]); + + expect(elementRegistry.get('Flow_4').waypoints).to.eql([ + { + original: { + x: 800, + y: 750 + }, + x: 800, + y: 720 + }, + { + original: { + x: 800, + y: 520 + }, + x: 800, + y: 560 + } + ]); + } + )); + + }); + +}); \ No newline at end of file diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index b3bd02b4..d5d81d3b 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -355,7 +355,7 @@ describe('features/replace - bpmn replace', function() { }); - describe('should collapse pool, removing message flows', function() { + describe('should collapse pool, reconnecting message flows', function() { var diagramXML = require('./BpmnReplace.poolMessageFlows.bpmn'); @@ -382,8 +382,8 @@ describe('features/replace - bpmn replace', function() { expect(isExpanded(newShape)).to.be.false; // collapsed expect(newShape.children).to.be.empty; - expect(elementRegistry.get('MessageFlow_1')).not.to.exist; - expect(elementRegistry.get('MessageFlow_2')).not.to.exist; + expect(elementRegistry.get('MessageFlow_1')).to.exist; + expect(elementRegistry.get('MessageFlow_2')).to.exist; })); });