fix(modeling/rules): correct boundary rules

* clean up boundary related rules
* split boundary tests into separate section
* add boundary message flow tests
* fix boundary message flow behavior

Closes #319
This commit is contained in:
Nico Rehwaldt 2015-07-30 10:48:17 +02:00 committed by pedesen
parent 02416de290
commit dac5bb397b
6 changed files with 321 additions and 207 deletions

View File

@ -94,8 +94,6 @@ BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
BpmnRules.prototype.canConnectBoundaryEvent = canConnectBoundaryEvent;
BpmnRules.prototype.canMove = canMove;
BpmnRules.prototype.canAttach = canAttach;
@ -145,7 +143,7 @@ function isSameOrganization(a, b) {
}
function isMessageFlowSource(element) {
return is(element, 'bpmn:InteractionNode') && ( is(element, 'bpmn:BoundaryEvent') ||
return is(element, 'bpmn:InteractionNode') && (
!is(element, 'bpmn:Event') || (
is(element, 'bpmn:ThrowEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
@ -154,7 +152,7 @@ function isMessageFlowSource(element) {
}
function isMessageFlowTarget(element) {
return is(element, 'bpmn:InteractionNode') && ( is(element, 'bpmn:BoundaryEvent') ||
return is(element, 'bpmn:InteractionNode') && (
!is(element, 'bpmn:Event') || (
is(element, 'bpmn:CatchEvent') &&
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
@ -212,7 +210,8 @@ function isSequenceFlowSource(element) {
}
function isSequenceFlowTarget(element) {
return is(element, 'bpmn:FlowNode') && !is(element, 'bpmn:StartEvent') &&
return is(element, 'bpmn:FlowNode') &&
!is(element, 'bpmn:StartEvent') &&
!is(element, 'bpmn:BoundaryEvent') &&
!(is(element, 'bpmn:IntermediateCatchEvent') &&
hasEventDefinition(element, 'bpmn:LinkEventDefinition'));
@ -257,10 +256,6 @@ function canConnect(source, target, connection) {
return false;
}
if (is(source, 'bpmn:BoundaryEvent')) {
return canConnectBoundaryEvent(source, target);
}
if (canConnectMessageFlow(source, target) ||
canConnectSequenceFlow(source, target)) {
return true;
@ -417,7 +412,7 @@ function canCreate(shape, target, source, position) {
return false;
}
return canDrop(shape, target) || canInsert(shape, target);
return canDrop(shape, target, position) || canInsert(shape, target, position);
}
function canResize(shape, newBounds) {
@ -465,7 +460,7 @@ function canConnectSequenceFlow(source, target) {
!(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
}
function canInsert(shape, flow) {
function canInsert(shape, flow, position) {
// return true if we can drop on the
// underlying flow parent
@ -476,35 +471,10 @@ function canInsert(shape, flow) {
is(flow, 'bpmn:SequenceFlow') ||
is(flow, 'bpmn:MessageFlow')
) && is(shape, 'bpmn:FlowNode') && !is(shape, 'bpmn:BoundaryEvent') &&
canDrop(shape, flow.parent);
canDrop(shape, flow.parent, position);
}
function canConnectBoundaryEvent(source, target) {
if (source.businessObject.$parent.id === target.id) {
return false;
}
if (source.businessObject.attachedToRef === target.id) {
return false;
}
if (canConnectMessageFlow(source, target)) {
return true;
}
if (is(target, 'bpmn:Activity')) {
return true;
}
return false;
}
/**
* Returns true if all elements have the same parent
*
* @param {Array<djs.model.Base>} elements
* @return {Boolean}
*/
function haveSameParent(elements) {
return size(groupBy(elements, function(e) { return e.parent && e.parent.id; })) === 1;
}

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:collaboration id="Collaboration">
<bpmn:textAnnotation id="TextAnnotation">
<bpmn:text>Boundary Events are awesome!</bpmn:text>
</bpmn:textAnnotation>
<bpmn:participant id="Participant" processRef="Process" />
<bpmn:participant id="OtherParticipant" processRef="OtherProcess" />
</bpmn:collaboration>
<bpmn:process id="Process" isExecutable="false">
<bpmn:subProcess id="SubProcess">
<bpmn:task id="Task_nested" />
<bpmn:boundaryEvent id="BoundaryEvent_nested" attachedToRef="Task_nested" />
<bpmn:endEvent id="EndEvent_nested" />
</bpmn:subProcess>
<bpmn:boundaryEvent id="BoundaryEvent_on_SubProcess" attachedToRef="SubProcess" />
<bpmn:task id="Task" />
<bpmn:boundaryEvent id="BoundaryEvent_on_Task" attachedToRef="Task" />
<bpmn:startEvent id="StartEvent_None" />
<bpmn:exclusiveGateway id="ExclusiveGateway" />
</bpmn:process>
<bpmn:process id="OtherProcess">
<bpmn:task id="Task_in_OtherProcess" name="3" />
<bpmn:boundaryEvent id="BoundaryEvent_in_OtherProcess" name="4" attachedToRef="Task_in_OtherProcess" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration">
<bpmndi:BPMNShape id="Participant_di" bpmnElement="Participant">
<dc:Bounds x="111" y="31" width="600" height="275" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="OtherParticipant_di" bpmnElement="OtherParticipant">
<dc:Bounds x="382" y="394" width="329" height="182" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_in_OtherProcess_di" bpmnElement="Task_in_OtherProcess">
<dc:Bounds x="450" y="426" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_in_OtherProcess_di" bpmnElement="BoundaryEvent_in_OtherProcess">
<dc:Bounds x="495" y="488" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="468" y="524" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="SubProcess_di" bpmnElement="SubProcess" isExpanded="true">
<dc:Bounds x="196" y="67" width="243" height="155" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_di" bpmnElement="BoundaryEvent_on_SubProcess">
<dc:Bounds x="237" y="204" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="210" y="240" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_nested_di" bpmnElement="Task_nested">
<dc:Bounds x="295" y="88" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_nested_di" bpmnElement="BoundaryEvent_nested">
<dc:Bounds x="340" y="150" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="313" y="186" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_di" bpmnElement="EndEvent_nested">
<dc:Bounds x="229" y="110" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="202" y="146" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ExclusiveGateway_di" bpmnElement="ExclusiveGateway" isMarkerVisible="true">
<dc:Bounds x="596" y="232" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="576" y="282" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_di" bpmnElement="TextAnnotation">
<dc:Bounds x="804" y="101" width="143" height="83" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_di" bpmnElement="Task">
<dc:Bounds x="557" y="95" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_on_Task_di" bpmnElement="BoundaryEvent_on_Task">
<dc:Bounds x="603" y="157" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="576" y="193" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="StartEvent_None_di" bpmnElement="StartEvent_None">
<dc:Bounds x="501" y="239" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="474" y="275" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -34,7 +34,6 @@
<bpmn2:endEvent id="EndEvent_Cancel">
<bpmn2:cancelEventDefinition />
</bpmn2:endEvent>
<bpmn2:boundaryEvent id="BoundaryEvent" attachedToRef="SubProcess" />
</bpmn2:process>
<bpmn2:process id="OtherProcess" isExecutable="false">
<bpmn2:task id="Task_in_OtherParticipant" />
@ -129,12 +128,6 @@
<dc:Bounds x="299" y="114" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_di" bpmnElement="BoundaryEvent">
<dc:Bounds x="499" y="259" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="472" y="295" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -18,8 +18,6 @@
<bpmn2:endEvent id="EndEvent_in_SubProcess" />
</bpmn2:subProcess>
<bpmn2:endEvent id="EndEvent_None" />
<bpmn2:boundaryEvent id="BoundaryEvent" name="ola" attachedToRef="SubProcess" />
<bpmn2:boundaryEvent id="BoundaryEvent_1" attachedToRef="Task" />
<bpmn2:textAnnotation id="TextAnnotation" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="_BPMNDiagram_2">
@ -69,18 +67,6 @@
<dc:Bounds x="534" y="209" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_di" bpmnElement="BoundaryEvent">
<dc:Bounds x="508" y="511" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="481" y="547" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_1_di" bpmnElement="BoundaryEvent_1">
<dc:Bounds x="154" y="203" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="127" y="239" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -1,6 +1,10 @@
'use strict';
var TestHelper = require('../../../../TestHelper');
var Helper = require('./Helper');
var expectCanConnect = Helper.expectCanConnect,
expectCanDrop = Helper.expectCanDrop,
expectCanMove = Helper.expectCanMove;
/* global bootstrapModeler, inject */
@ -8,75 +12,6 @@ var modelingModule = require('../../../../../lib/features/modeling'),
coreModule = require('../../../../../lib/core');
function expectCanConnect(source, target, rules) {
var results = {};
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
source = elementRegistry.get(source);
target = elementRegistry.get(target);
expect(source).to.exist;
expect(target).to.exist;
if ('sequenceFlow' in rules) {
results.sequenceFlow = bpmnRules.canConnectSequenceFlow(source, target);
}
if ('messageFlow' in rules) {
results.messageFlow = bpmnRules.canConnectMessageFlow(source, target);
}
if ('association' in rules) {
results.association = bpmnRules.canConnectAssociation(source, target);
}
});
expect(results).to.eql(rules);
}
function expectCanDrop(element, target, expectedResult) {
var result;
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
element = elementRegistry.get(element);
target = elementRegistry.get(target);
expect(element).to.exist;
expect(target).to.exist;
result = bpmnRules.canDrop(element, target);
});
expect(result).to.eql(expectedResult);
}
function expectCanExecute(elements, target, rules) {
var results = {};
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
target = elementRegistry.get(target);
if ('canAttach' in rules) {
results.canAttach = bpmnRules.canAttach(elements, target);
}
if ('canMove' in rules) {
results.canMove = bpmnRules.canMove(elements, target);
}
});
expect(results).to.eql(rules);
}
describe('features/modeling/rules - BpmnRules', function() {
var testModules = [ coreModule, modelingModule ];
@ -149,56 +84,6 @@ describe('features/modeling/rules - BpmnRules', function() {
}));
it('connect BoundaryEvent -> Task', inject(function() {
expectCanConnect('BoundaryEvent', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_1 -> SubProcess', inject(function() {
expectCanConnect('BoundaryEvent', 'SubProcess', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent -> BoundaryEvent_1', inject(function() {
expectCanConnect('BoundaryEvent', 'BoundaryEvent_1', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent -> StartEvent_None', inject(function() {
expectCanConnect('BoundaryEvent', 'BoundaryEvent_1', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect StartEvent_None -> BoundaryEvent', inject(function() {
expectCanConnect('StartEvent_None', 'BoundaryEvent', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('drop TextAnnotation -> Process', inject(function() {
expectCanDrop('TextAnnotation', 'Process', true);
@ -207,6 +92,125 @@ describe('features/modeling/rules - BpmnRules', function() {
});
describe('boundary events', function() {
var testXML = require('./BpmnRules.boundaryEvent.bpmn');
beforeEach(bootstrapModeler(testXML, { modules: testModules }));
it('connect BoundaryEvent_on_SubProcess -> Task', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'Task', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> ExclusiveGateway', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'ExclusiveGateway', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> SubProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'SubProcess', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> BoundaryEvent_on_Task', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'BoundaryEvent_on_Task', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> StartEvent_None', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'StartEvent_None', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect StartEvent_None -> BoundaryEvent_on_SubProcess', inject(function() {
expectCanConnect('StartEvent_None', 'BoundaryEvent_on_SubProcess', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_nested -> Task', inject(function() {
expectCanConnect('BoundaryEvent_nested', 'Task', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_nested -> EndEvent_nested', inject(function() {
expectCanConnect('BoundaryEvent_nested', 'EndEvent_nested', {
sequenceFlow: true,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> BoundaryEvent_in_OtherProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'BoundaryEvent_in_OtherProcess', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect BoundaryEvent_on_SubProcess -> Task_in_OtherProcess', inject(function() {
expectCanConnect('BoundaryEvent_on_SubProcess', 'Task_in_OtherProcess', {
sequenceFlow: false,
messageFlow: false,
association: true
});
}));
it('connect Task_in_OtherProcess -> BoundaryEvent_on_SubProcess', inject(function() {
expectCanConnect('Task_in_OtherProcess', 'BoundaryEvent_on_SubProcess', {
sequenceFlow: false,
messageFlow: true,
association: true
});
}));
});
describe('event based gateway', function() {
var testXML = require('./BpmnRules.eventBasedGateway.bpmn');
@ -516,16 +520,6 @@ describe('features/modeling/rules - BpmnRules', function() {
}));
it('connect BoundaryEvent -> Task_in_OtherParticipant', inject(function() {
expectCanConnect('BoundaryEvent', 'Task_in_OtherParticipant', {
sequenceFlow: false,
messageFlow: true,
association: true
});
}));
it('drop TextAnnotation_Global -> Participant', inject(function() {
expectCanDrop('TextAnnotation_Global', 'Participant', true);
@ -564,9 +558,9 @@ describe('features/modeling/rules - BpmnRules', function() {
var elements = [ boundaryEvent ];
// then
expectCanExecute(elements, 'Process_1', {
canAttach: false,
canMove: false
expectCanMove(elements, 'Process_1', {
attach: false,
move: false
});
}));
@ -580,9 +574,9 @@ describe('features/modeling/rules - BpmnRules', function() {
var elements = [ boundaryEvent ];
// then
expectCanExecute(elements, 'Task_2', {
canAttach: 'attach',
canMove: false
expectCanMove(elements, 'Task_2', {
attach: 'attach',
move: false
});
}));
@ -597,9 +591,9 @@ describe('features/modeling/rules - BpmnRules', function() {
var elements = [ label ];
// then
expectCanExecute(elements, 'SubProcess_1', {
canAttach: false,
canMove: true
expectCanMove(elements, 'SubProcess_1', {
attach: false,
move: true
});
}));
@ -615,9 +609,9 @@ describe('features/modeling/rules - BpmnRules', function() {
var elements = [ boundaryEvent, boundaryEvent2 ];
// then
expectCanExecute(elements, 'SubProcess_1', {
canAttach: false,
canMove: false
expectCanMove(elements, 'SubProcess_1', {
attach: false,
move: false
});
}));
@ -633,9 +627,9 @@ describe('features/modeling/rules - BpmnRules', function() {
var elements = [ subProcess, boundaryEvent, label ];
// then
expectCanExecute(elements, 'Process_1', {
canAttach: false,
canMove: false
expectCanMove(elements, 'Process_1', {
attach: false,
move: false
});
}));
@ -658,9 +652,9 @@ describe('features/modeling/rules - BpmnRules', function() {
});
// then
expectCanExecute([ eventShape ], 'Task_1', {
canAttach: 'attach',
canMove: false
expectCanMove([ eventShape ], 'Task_1', {
attach: 'attach',
move: false
});
}));

View File

@ -0,0 +1,78 @@
'use strict';
var TestHelper = require('../../../../TestHelper');
function expectCanConnect(source, target, rules) {
var results = {};
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
source = elementRegistry.get(source);
target = elementRegistry.get(target);
expect(source).to.exist;
expect(target).to.exist;
if ('sequenceFlow' in rules) {
results.sequenceFlow = bpmnRules.canConnectSequenceFlow(source, target);
}
if ('messageFlow' in rules) {
results.messageFlow = bpmnRules.canConnectMessageFlow(source, target);
}
if ('association' in rules) {
results.association = bpmnRules.canConnectAssociation(source, target);
}
});
expect(results).to.eql(rules);
}
module.exports.expectCanConnect = expectCanConnect;
function expectCanDrop(element, target, expectedResult) {
var result;
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
element = elementRegistry.get(element);
target = elementRegistry.get(target);
expect(element).to.exist;
expect(target).to.exist;
result = bpmnRules.canDrop(element, target);
});
expect(result).to.eql(expectedResult);
}
module.exports.expectCanDrop = expectCanDrop;
function expectCanMove(elements, target, rules) {
var results = {};
TestHelper.getBpmnJS().invoke(function(elementRegistry, bpmnRules) {
target = elementRegistry.get(target);
if ('attach' in rules) {
results.attach = bpmnRules.canAttach(elements, target);
}
if ('move' in rules) {
results.move = bpmnRules.canMove(elements, target);
}
});
expect(results).to.eql(rules);
}
module.exports.expectCanMove = expectCanMove;