diff --git a/lib/features/modeling/behavior/CompensateBoundaryEventBehaviour.js b/lib/features/modeling/behavior/CompensateBoundaryEventBehaviour.js new file mode 100644 index 0000000000..0d5097f03b --- /dev/null +++ b/lib/features/modeling/behavior/CompensateBoundaryEventBehaviour.js @@ -0,0 +1,78 @@ +import inherits from 'inherits-browser'; + +import { is } from '../../../util/ModelUtil'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +import { hasEventDefinition } from '../../../util/DiUtil'; + +/** + * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus + * @typedef {import('diagram-js/lib/features/modeling/Modeling').default} Modeling + */ + +export default function CompensateBoundaryEventBehaviour(eventBus, modeling) { + + CommandInterceptor.call(this, eventBus); + + function addIsForCompensationProperty(source, target) { + if (isCompensationBoundaryEvent(source)) { + if (is(target, 'bpmn:Activity') && !isForCompensation(target)) { + modeling.updateProperties(target, { isForCompensation: true }); + } + } + } + + function removeIsForCompensationProperty(source, target) { + if (isCompensationBoundaryEvent(source)) { + if (is(target, 'bpmn:Activity') && isForCompensation(target)) { + modeling.updateProperties(target, { isForCompensation: false }); + } + } + } + + this.preExecute('connection.create', function(context) { + var source = context.source, + target = context.target; + + addIsForCompensationProperty(source, target); + }, true); + + this.postExecute('connection.reconnect', function(context) { + var newSource = context.newSource, + newTarget = context.newTarget, + oldSource = context.oldSource, + oldTarget = context.oldTarget; + + // add `isForCompensation` to new target + addIsForCompensationProperty(newSource, newTarget); + + // remove `isForCompensation` from old target + removeIsForCompensationProperty(oldSource, oldTarget); + }, true); + + this.postExecute('connection.delete', function(context) { + var source = context.source, + target = context.target; + + removeIsForCompensationProperty(source, target); + }, true); + +} + +inherits(CompensateBoundaryEventBehaviour, CommandInterceptor); + +CompensateBoundaryEventBehaviour.$inject = [ + 'eventBus', + 'modeling' +]; + +// helpers ////////// + +function isForCompensation(element) { + return element && element.businessObject.isForCompensation; +} + +function isCompensationBoundaryEvent(element) { + return element && is(element, 'bpmn:BoundaryEvent') && + hasEventDefinition(element, 'bpmn:CompensateEventDefinition'); +} \ No newline at end of file diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 4e2f681a3f..50272f838c 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -3,6 +3,7 @@ import AppendBehavior from './AppendBehavior'; import AssociationBehavior from './AssociationBehavior'; import AttachEventBehavior from './AttachEventBehavior'; import BoundaryEventBehavior from './BoundaryEventBehavior'; +import CompensateBoundaryEventBehaviour from './CompensateBoundaryEventBehaviour'; import CreateBehavior from './CreateBehavior'; import CreateDataObjectBehavior from './CreateDataObjectBehavior'; import CreateParticipantBehavior from './CreateParticipantBehavior'; @@ -47,6 +48,7 @@ export default { 'associationBehavior', 'attachEventBehavior', 'boundaryEventBehavior', + 'compensateBoundaryEventBehaviour', 'createBehavior', 'createDataObjectBehavior', 'createParticipantBehavior', @@ -86,6 +88,7 @@ export default { associationBehavior: [ 'type', AssociationBehavior ], attachEventBehavior: [ 'type', AttachEventBehavior ], boundaryEventBehavior: [ 'type', BoundaryEventBehavior ], + compensateBoundaryEventBehaviour: [ 'type', CompensateBoundaryEventBehaviour ], createBehavior: [ 'type', CreateBehavior ], createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ], createParticipantBehavior: [ 'type', CreateParticipantBehavior ], diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js index e6f4331428..0bb1284224 100644 --- a/lib/features/rules/BpmnRules.js +++ b/lib/features/rules/BpmnRules.js @@ -550,15 +550,15 @@ function canConnect(source, target, connection) { return connectDataAssociation; } - if (isCompensationBoundary(source) && isForCompensation(target)) { - return { - type: 'bpmn:Association', - associationDirection: 'One' - }; - } - if (canConnectAssociation(source, target)) { + if (isCompensationBoundary(source) && is(target, 'bpmn:Activity')) { + return { + type: 'bpmn:Association', + associationDirection: 'One' + }; + } + return { type: 'bpmn:Association' }; @@ -1028,7 +1028,7 @@ function isOneTextAnnotation(source, target) { function canConnectAssociation(source, target) { // compensation boundary events are exception - if (isCompensationBoundary(source) && isForCompensation(target)) { + if (isCompensationBoundary(source) && is(target, 'bpmn:Activity') && !isEventSubProcess(target)) { return true; } diff --git a/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviour.bpmn b/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviour.bpmn new file mode 100644 index 0000000000..3d2b2bf7aa --- /dev/null +++ b/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviour.bpmn @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviourSpec.js b/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviourSpec.js new file mode 100644 index 0000000000..d5dc464dd1 --- /dev/null +++ b/test/spec/features/modeling/behavior/CompensateBoundaryEventBehaviourSpec.js @@ -0,0 +1,115 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import modelingModule from 'lib/features/modeling'; +import coreModule from 'lib/core'; + +import diagramXML from './CompensateBoundaryEventBehaviour.bpmn'; + + +describe('features/modeling/behavior - compensation boundary event', function() { + + var testModules = [ coreModule, modelingModule ]; + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + + describe('should add `isForCompensation`', function() { + + it('on append', inject(function(elementFactory, modeling, elementRegistry) { + + // given + var boundaryEventShape = elementRegistry.get('Attached_Event'); + var taskShape = elementFactory.createShape({ type: 'bpmn:Task' }); + + // when + var task = modeling.appendShape(boundaryEventShape, taskShape, { x: 100, y: 100 }); + + // then + expect(task.businessObject.isForCompensation).to.be.true; + })); + + + it('on connect', inject(function(modeling, elementRegistry) { + + // given + var boundaryEventShape = elementRegistry.get('Attached_Event'); + var taskShape = elementRegistry.get('Task'); + + // when + modeling.connect(boundaryEventShape, taskShape); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.true; + })); + + + it('on reconnect', inject(function(modeling, elementRegistry) { + + // given + var taskShape = elementRegistry.get('Task'); + var connection = elementRegistry.get('Association'); + + // when + modeling.reconnectEnd(connection, taskShape, { x: 100, y: 100 }); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.true; + })); + }); + + + describe('should remove `isForCompensation`', function() { + + it('on remove element', inject(function(elementRegistry, modeling) { + + // given + var taskShape = elementRegistry.get('Task_Compensation'); + var boundaryEventShape = elementRegistry.get('Attached_Event2'); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.true; + + // when + modeling.removeElements([ boundaryEventShape ]); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.false; + })); + + + it('on delete connection', inject(function(elementRegistry, modeling) { + + // given + var taskShape = elementRegistry.get('Task_Compensation'); + var connection = elementRegistry.get('Association'); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.true; + + // when + modeling.removeConnection(connection); + + // then + expect(taskShape.businessObject.isForCompensation).to.be.false; + })); + + + it('on reconnect', inject(function(modeling, elementRegistry) { + + // given + var oldShape = elementRegistry.get('Task_Compensation'); + var taskShape = elementRegistry.get('Task'); + var connection = elementRegistry.get('Association'); + + // when + modeling.reconnectEnd(connection, taskShape, { x: 100, y: 100 }); + + // then + expect(oldShape.businessObject.isForCompensation).to.be.false; + })); + + }); +}); diff --git a/test/spec/features/rules/BpmnRules.compensation.bpmn b/test/spec/features/rules/BpmnRules.compensation.bpmn index 3ca81df018..7e837bf966 100644 --- a/test/spec/features/rules/BpmnRules.compensation.bpmn +++ b/test/spec/features/rules/BpmnRules.compensation.bpmn @@ -17,16 +17,16 @@ - - + + + + + - - - - + @@ -61,6 +61,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/spec/features/rules/BpmnRulesSpec.js b/test/spec/features/rules/BpmnRulesSpec.js index 2cdf802dd7..b0cdab46d7 100644 --- a/test/spec/features/rules/BpmnRulesSpec.js +++ b/test/spec/features/rules/BpmnRulesSpec.js @@ -733,6 +733,28 @@ describe('features/modeling/rules - BpmnRules', function() { it('connect CompensationBoundary -> Task', inject(function() { expectCanConnect('CompensationBoundary', 'Task', { + sequenceFlow: false, + messageFlow: false, + association: true, + dataAssociation: false + }); + })); + + + it('connect CompensationBoundary -> SubProcess', inject(function() { + + expectCanConnect('CompensationBoundary', 'SubProcess_2', { + sequenceFlow: false, + messageFlow: false, + association: true, + dataAssociation: false + }); + })); + + + it('connect CompensationBoundary -> Event SubProcess', inject(function() { + + expectCanConnect('CompensationBoundary', 'SubProcess_1', { sequenceFlow: false, messageFlow: false, association: false,