feat(modeling/layout): improve sequence flow layouting behind Gateways

Closes bpmn-io/bpmn-js#227
This commit is contained in:
Nico Rehwaldt 2015-07-29 15:11:54 +02:00 committed by pedesen
parent 359e0e01f4
commit 8e4f480868
9 changed files with 745 additions and 43 deletions

View File

@ -5,9 +5,13 @@ var inherits = require('inherits');
var assign = require('lodash/object/assign');
var BaseLayouter = require('diagram-js/lib/layout/BaseLayouter'),
LayoutUtil = require('diagram-js/lib/layout/LayoutUtil'),
ManhattanLayout = require('diagram-js/lib/layout/ManhattanLayout');
var LayoutUtil = require('diagram-js/lib/layout/LayoutUtil');
var getMid = LayoutUtil.getMid,
getOrientation = LayoutUtil.getOrientation;
var is = require('../../util/ModelUtil').is;
@ -18,48 +22,108 @@ inherits(BpmnLayouter, BaseLayouter);
module.exports = BpmnLayouter;
function getAttachment(waypoints, idx, shape) {
var point = waypoints && waypoints[idx];
return point ? (point.original || point) : LayoutUtil.getMidPoint(shape);
}
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
BpmnLayouter.prototype.layoutConnection = function(connection, layoutHints) {
var source = connection.source,
target = connection.target,
waypoints = connection.waypoints,
start,
end;
var layoutManhattan,
var manhattanOptions,
updatedWaypoints;
start = getAttachment(waypoints, 0, source);
end = getAttachment(waypoints, waypoints && waypoints.length - 1, target);
start = getConnectionDocking(waypoints, 0, source);
end = getConnectionDocking(waypoints, waypoints && waypoints.length - 1, target);
// TODO (nre): support vertical modeling
// and invert preferredLayouts accordingly
// manhattan layout sequence / message flows
if (is(connection, 'bpmn:MessageFlow')) {
layoutManhattan = {
preferStraight: true,
preferVertical: true
manhattanOptions = {
preferredLayouts: [ 'straight', 'v:v' ]
};
}
} else
// layout all connection between flow elements h:h,
//
// except for
//
// (1) outgoing of BoundaryEvents -> layout h:v or v:h based on attach orientation
// (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
//
if (is(connection, 'bpmn:SequenceFlow')) {
layoutManhattan = {};
// make sure boundary event connections do
// not look ugly =:>
if (is(source, 'bpmn:BoundaryEvent')) {
var orientation = getAttachOrientation(source);
if (/left|right/.test(orientation)) {
manhattanOptions = {
preferredLayouts: [ 'h:v' ]
};
} else
if (/top|bottom/.test(orientation)) {
manhattanOptions = {
preferredLayouts: [ 'v:h' ]
};
}
} else
if (is(source, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'v:h' ]
};
} else
if (is(target, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'h:v' ]
};
}
// apply horizontal love <3
else {
manhattanOptions = {
preferredLayouts: [ 'h:h' ]
};
}
}
if (layoutManhattan) {
if (manhattanOptions) {
layoutManhattan = assign(layoutManhattan, hints);
manhattanOptions = assign(manhattanOptions, layoutHints);
updatedWaypoints =
ManhattanLayout.repairConnection(
source, target, start, end,
source, target,
start, end,
waypoints,
layoutManhattan);
manhattanOptions);
}
return updatedWaypoints || [ start, end ];
};
function getAttachOrientation(attachedElement) {
var hostElement = attachedElement.host,
padding = -10;
return getOrientation(getMid(attachedElement), hostElement, padding);
}
function getConnectionDocking(waypoints, idx, shape) {
var point = waypoints && waypoints[idx];
return point ? (point.original || point) : getMid(shape);
}

View File

@ -41,22 +41,16 @@ describe('features/modeling - move shape', function() {
expect(startEvent.di.bounds.x).to.equal(oldPosition.x);
expect(startEvent.di.bounds.y).to.equal(oldPosition.y + 50);
// expect flow layout
expect(sequenceFlowElement.waypoints).to.eql([
{ original: { x: 388, y: 310 }, x: 388, y: 310 },
{ x: 404, y: 310 },
{ x: 404, y: 260 },
{ original: { x: 420, y: 260 }, x: 420, y: 260 }
]);
var newWaypoints = sequenceFlowElement.waypoints;
var diWaypoints = bpmnFactory.createDiWaypoints([
{x: 388, y: 310 },
{x: 404, y: 310 },
{x: 404, y: 260 },
{x: 420, y: 260 }
]);
var expectedDiWaypoints = bpmnFactory.createDiWaypoints(newWaypoints.map(function(p) {
return { x: p.x, y: p.y };
}));
expect(sequenceFlow.di.waypoint).to.eql(diWaypoints);
// see LayoutSpec for actual connection layouting tests
// expect di waypoints update
expect(sequenceFlow.di.waypoint).to.eql(expectedDiWaypoints);
}));

View File

@ -0,0 +1,65 @@
var TestHelper = require('../../../../TestHelper');
function connect(source, target, attrs) {
var elementRegistry = TestHelper.getBpmnJS().get('elementRegistry'),
modeling = TestHelper.getBpmnJS().get('modeling');
var sourceElement = typeof source === 'string' ? elementRegistry.get(source) : source;
var targetElement = typeof target === 'string' ? elementRegistry.get(target) : target;
// assume
expect(sourceElement).to.exist;
expect(targetElement).to.exist;
return modeling.connect(sourceElement, targetElement, attrs);
}
function reconnectEnd(connection, target, docking) {
var elementRegistry = TestHelper.getBpmnJS().get('elementRegistry'),
modeling = TestHelper.getBpmnJS().get('modeling');
var connectionElement = typeof connection === 'string' ? elementRegistry.get(connection) : connection;
var targetElement = typeof target === 'string' ? elementRegistry.get(target) : target;
// assume
expect(connectionElement).to.exist;
expect(targetElement).to.exist;
return modeling.reconnectEnd(connectionElement, targetElement, docking);
}
function element(id) {
return TestHelper.getBpmnJS().get('elementRegistry').get(id);
}
function move(shape, delta) {
var elementRegistry = TestHelper.getBpmnJS().get('elementRegistry'),
modeling = TestHelper.getBpmnJS().get('modeling');
var shapeElement = typeof shape === 'string' ? elementRegistry.get(shape) : shape;
// assume
expect(shapeElement).to.exist;
modeling.moveShapes([shapeElement], delta);
return shapeElement;
}
// API
module.exports.connect = connect;
module.exports.reconnectEnd = reconnectEnd;
module.exports.element = element;
module.exports.move = move;
// debugging
module.exports.inspect = function(element) {
console.log(JSON.stringify(element));
};

View File

@ -1,17 +1,17 @@
'use strict';
var TestHelper = require('../../../TestHelper');
var TestHelper = require('../../../../TestHelper');
/* global bootstrapModeler, inject */
var modelingModule = require('../../../../lib/features/modeling'),
coreModule = require('../../../../lib/core');
var modelingModule = require('../../../../../lib/features/modeling'),
coreModule = require('../../../../../lib/core');
describe('features/modeling - layout connection', function() {
var diagramXML = require('../../../fixtures/bpmn/sequence-flows.bpmn');
var diagramXML = require('../../../../fixtures/bpmn/sequence-flows.bpmn');
var testModules = [ coreModule, modelingModule ];

View File

@ -1,17 +1,17 @@
'use strict';
var TestHelper = require('../../../TestHelper');
var TestHelper = require('../../../../TestHelper');
/* global bootstrapModeler, inject */
var modelingModule = require('../../../../lib/features/modeling'),
coreModule = require('../../../../lib/core');
var modelingModule = require('../../../../../lib/features/modeling'),
coreModule = require('../../../../../lib/core');
describe('features/modeling - layout message flows', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration-message-flows.bpmn');
var diagramXML = require('../../../../fixtures/bpmn/collaboration-message-flows.bpmn');
var testModules = [ coreModule, modelingModule ];

View File

@ -0,0 +1,65 @@
<?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:process id="Process_1" isExecutable="false">
<bpmn:subProcess id="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_D" name="D" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_C" name="C" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_B" name="B" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_A" name="A" attachedToRef="SubProcess" />
<bpmn:task id="Task_1" name="1" />
<bpmn:task id="Task_2" name="2" />
<bpmn:task id="Task_3" name="3" />
<bpmn:task id="Task_4" name="4" />
<bpmn:task id="Task_5" name="5" />
<bpmn:task id="Task_6" name="6" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="SubProcess_di" bpmnElement="SubProcess" isExpanded="true">
<dc:Bounds x="505" y="258" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_D_di" bpmnElement="BoundaryEvent_D">
<dc:Bounds x="797" y="440" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="744" y="424" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_C_di" bpmnElement="BoundaryEvent_C">
<dc:Bounds x="837" y="275" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="775" y="284" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_B_di" bpmnElement="BoundaryEvent_B">
<dc:Bounds x="568" y="240" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="540" y="280" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_A_di" bpmnElement="BoundaryEvent_A">
<dc:Bounds x="487" y="399" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="495" y="407" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="287" y="354" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
<dc:Bounds x="362" y="503" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_3_di" bpmnElement="Task_3">
<dc:Bounds x="378" y="122" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_4_di" bpmnElement="Task_4">
<dc:Bounds x="536" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_5_di" bpmnElement="Task_5">
<dc:Bounds x="966" y="175" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_6_di" bpmnElement="Task_6">
<dc:Bounds x="991" y="443" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,70 @@
<?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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" />
<bpmn:exclusiveGateway id="ExclusiveGateway_1" />
<bpmn:task id="Task_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:task>
<bpmn:intermediateThrowEvent id="IntermediateThrowEvent_1" />
<bpmn:serviceTask id="ServiceTask_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:serviceTask>
<bpmn:parallelGateway id="ParallelGateway_1" />
<bpmn:endEvent id="EndEvent_1" />
<bpmn:businessRuleTask id="BusinessRuleTask_1" />
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="Task_1" targetRef="ServiceTask_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="284" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="125" y="320" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ExclusiveGateway_1_di" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds x="653" y="277" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="633" y="327" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="282" y="180" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="IntermediateThrowEvent_1_di" bpmnElement="IntermediateThrowEvent_1">
<dc:Bounds x="478" y="284" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="451" y="320" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ServiceTask_1_di" bpmnElement="ServiceTask_1">
<dc:Bounds x="628" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ParallelGateway_1_di" bpmnElement="ParallelGateway_1" isMarkerVisible="true">
<dc:Bounds x="980" y="277" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="960" y="327" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
<dc:Bounds x="478" y="99" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="451" y="135" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BusinessRuleTask_1_di" bpmnElement="BusinessRuleTask_1">
<dc:Bounds x="790" y="180" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1_di" bpmnElement="SequenceFlow_1">
<di:waypoint xsi:type="dc:Point" x="382" y="241" />
<di:waypoint xsi:type="dc:Point" x="559" y="241" />
<di:waypoint xsi:type="dc:Point" x="559" y="138" />
<di:waypoint xsi:type="dc:Point" x="628" y="138" />
<bpmndi:BPMNLabel>
<dc:Bounds x="460" y="158.5" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,326 @@
'use strict';
var Helper = require('./Helper');
var connect = Helper.connect,
element = Helper.element,
move = Helper.move,
reconnectEnd = Helper.reconnectEnd;
var Modeler = require('../../../../../lib/Modeler');
/* global bootstrapModeler, inject */
var appendElement = require('../../../../util/ModelingUtil').appendElement,
moveElements = require('../../../../util/ModelingUtil').moveElements,
getElement = require('../../../../util/ModelingUtil').getElement;
var modelingModule = require('../../../../../lib/features/modeling'),
coreModule = require('../../../../../lib/core');
describe('features/modeling - layout', function() {
describe.skip('overall experience, flow elements', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.flowElements.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: Modeler.prototype._modules }));
it('should feel awesome', inject(function() { }));
});
describe.skip('overall experience, boundary events', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEvents.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: Modeler.prototype._modules }));
it('should feel awesome', inject(function() { }));
});
describe('flow elements', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.flowElements.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('gateway layout', function() {
it('should layout v:h after Gateway', inject(function() {
// when
var connection = connect('ExclusiveGateway_1', 'BusinessRuleTask_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":678,"y":302},"x":678,"y":277},
{"x":678,"y":220},
{"original":{"x":840,"y":220},"x":790,"y":220}
]);
}));
it('should layout h:v before Gateway', inject(function() {
// when
var connection = connect('BusinessRuleTask_1', 'ParallelGateway_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":840,"y":220},"x":890,"y":220},
{"x":1005,"y":220},
{"original":{"x":1005,"y":302},"x":1005,"y":277}
]);
}));
});
describe('other elements layout', function() {
it('should layout h:h after StartEvent', inject(function() {
// when
var connection = connect('StartEvent_1', 'Task_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":170,"y":302},"x":188,"y":302},
{"x":235,"y":302},
{"x":235,"y":220},
{"original":{"x":332,"y":220},"x":282,"y":220}
]);
}));
it('should layout h:h after Task', inject(function() {
// when
var connection = connect('ServiceTask_1', 'BusinessRuleTask_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":678,"y":117},"x":728,"y":117},
{"x":759,"y":117},
{"x":759,"y":220},
{"original":{"x":840,"y":220},"x":790,"y":220}
]);
}));
it('should layout h:h after IntermediateEvent', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'ServiceTask_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":496,"y":302},"x":514,"y":302},
{"x":571,"y":302},
{"x":571,"y":117},
{"original":{"x":678,"y":117},"x":628,"y":117}
]);
}));
it('should layout h:h after IntermediateEvent (right to left)', inject(function() {
// when
var connection = connect('IntermediateThrowEvent_1', 'Task_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":496,"y":302},"x":478,"y":302},
{"x":430,"y":302},
{"x":430,"y":220},
{"original":{"x":332,"y":220},"x":382,"y":220}
]);
}));
});
describe('relayout', function() {
it('should not repair after reconnect end', inject(function() {
// given
var newDocking = { x: 660, y: 280 };
var connection = element('SequenceFlow_1');
// when
reconnectEnd(connection, 'ExclusiveGateway_1', newDocking);
// then
expect(connection.waypoints).to.eql([
{"original":{"x":382,"y":241},"x":382,"y":241},
{"x":559,"y":241},
{"x":559,"y":138},
{"original":{"x":660,"y":280},"x":660,"y":280}
]);
}));
it('should repair after target move', inject(function() {
// given
var delta = { x: -30, y: 20 };
var connection = element('SequenceFlow_1');
// when
move('ServiceTask_1', delta);
// then
expect(connection.waypoints).to.eql([{"original":{"x":382,"y":241},"x":382,"y":241},
{"x":559,"y":241},
{"x":559,"y":158},
{"original":{"x":598,"y":158},"x":598,"y":158}
]);
}));
it('should repair after source move', inject(function() {
// given
var delta = { x: -30, y: 20 };
var connection = element('SequenceFlow_1');
// when
move('Task_1', delta);
// then
expect(connection.waypoints).to.eql([
{"original":{"x":352,"y":261},"x":352,"y":261},
{"x":559,"y":261},
{"x":559,"y":138},
{"original":{"x":628,"y":138},"x":628,"y":138}
]);
}));
});
});
describe('boundary events', function() {
var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEvents.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout h:h connecting BoundaryEvent -> left Task', inject(function() {
// when
var connection = connect('BoundaryEvent_A', 'Task_1');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":505,"y":417},"x":487,"y":417},
{"x":437,"y":417},
{"x":437,"y":394},
{"original":{"x":337,"y":394},"x":387,"y":394}
]);
}));
it('should layout h:v connecting BoundaryEvent -> bottom-left Task', inject(function() {
// when
var connection = connect('BoundaryEvent_A', 'Task_2');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":505,"y":417},"x":487,"y":417},
{"x":412,"y":417},
{"original":{"x":412,"y":543},"x":412,"y":503}
]);
}));
it('should layout h:v connecting BoundaryEvent -> top-right Task', inject(function() {
// when
var connection = connect('BoundaryEvent_A', 'Task_5');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":505,"y":417},"x":523,"y":417},
{"x":1016,"y":417},
{"original":{"x":1016,"y":215},"x":1016,"y":255}
]);
}));
it('should layout v:v connecting BoundaryEvent -> top Task', inject(function() {
// when
var connection = connect('BoundaryEvent_B', 'Task_4');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":586,"y":258},"x":586,"y":240},
{"original":{"x":586,"y":121},"x":586,"y":161}
]);
}));
it('should layout v:h connecting BoundaryEvent -> top-left Task', inject(function() {
// when
var connection = connect('BoundaryEvent_B', 'Task_3');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":586,"y":258},"x":586,"y":258},
{"x":586,"y":162},
{"original":{"x":428,"y":162},"x":478,"y":162}
]);
}));
it('should layout h:v connecting BoundaryEvent -> bottom-right Task', inject(function() {
// when
var connection = connect('BoundaryEvent_C', 'Task_6');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":855,"y":293},"x":873,"y":293},
{"x":1041,"y":293},
{"original":{"x":1041,"y":483},"x":1041,"y":443}
]);
}));
it('should layout v:h connecting BoundaryEvent -> bottom-left Task', inject(function() {
// when
var connection = connect('BoundaryEvent_D', 'Task_2');
// then
expect(connection.waypoints).to.eql([
{"original":{"x":815,"y":458},"x":815,"y":476},
{"x":815,"y":543},
{"original":{"x":412,"y":543},"x":462,"y":543}
]);
}));
});
});

118
test/util/ModelingUtil.js Normal file
View File

@ -0,0 +1,118 @@
'use strict';
var TestHelper = require('../helper');
var getCenter = require('diagram-js/lib/layout/LayoutUtil').getCenter;
var isArray = require('lodash/lang/isArray'),
map = require('lodash/collection/map'),
pick = require('lodash/object/pick'),
assign = require('lodash/object/assign');
function invoke(fn) {
return TestHelper.getBpmnJS().invoke(fn);
}
function normalizeDelta(delta) {
return assign({ x: 0, y: 0 }, delta);
}
module.exports.getRelativeCenter = function(element, delta) {
var normalizedDelta = normalizeDelta(delta);
var center = getCenter(element);
return {
x: center.x + normalizedDelta.x,
y: center.y + normalizedDelta.y
};
};
function getElement(id) {
// assume
expect(id).to.exist;
return invoke(function(elementRegistry) {
return elementRegistry.get(id.id || id);
});
}
module.exports.getElement = getElement;
module.exports.appendShape = function(source, shape, distance, target, connection, connectionParent) {
source = getElement(source);
target = target && getElement(target);
connectionParent = connectionParent && getElement(connectionParent);
return invoke(function(rules, modeling) {
var allowed = rules.allowed('shape.append', {
source: source,
shape: shape,
target: target
});
// assume append to be allowed
expect(allowed).to.eql(true);
});
};
module.exports.moveShapes = function(elements, distance, target, isAttach, hints) {
var actualElements = elements.map(function(e) {
var actualElement = getElement(e);
// assume the elements we want to move exist
expect(actualElement).to.exist;
return actualElement;
});
invoke(function(elementRegistry, modeling, rules) {
var delta = normalizeDelta(distance);
var allowed = rules.allowed('shapes.move', {
shapes: actualElements,
delta: delta,
target: target
});
// assume we can actually move
expect(allowed).to.eql(typeof isAttach === 'boolean' ? 'attach' : true);
// perform actual move
modeling.moveShapes(actualElements, delta, target, isAttach, hints);
});
};
module.exports.getBounds = function() {
var args = Array.prototype.slice.call(arguments);
if (isArray(args[0])) {
args = args[0];
}
return map(args, function(e) {
e = getElement(e);
if (e.waypoints) {
return null;
}
return pick(e, [ 'x', 'y', 'width', 'height' ]);
});
};