chore(bpmn-snapping): seperate snapping into create/move and connect

* move create/move snapping to BpmnCreateMoveSnapping
* move connect snapping to BpmnConnectSnapping
* refactor tests

Related camunda/camunda-modeler#1290
This commit is contained in:
Philipp Fromme 2019-05-23 22:37:06 +02:00 committed by Nico Rehwaldt
parent a4e160c48b
commit a98c8e3bc8
16 changed files with 987 additions and 1468 deletions

View File

@ -0,0 +1,98 @@
import {
mid,
setSnapped
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { isCmd } from 'diagram-js/lib/features/keyboard/KeyboardUtil';
import { is } from '../../util/ModelUtil';
import { some } from 'min-dash';
var HIGHER_PRIORITY = 1250;
/**
* Snap during connect.
*
* @param {EventBus} eventBus
* @param {Rules} rules
*/
export default function BpmnConnectSnapping(eventBus, rules) {
eventBus.on([
'connect.hover',
'connect.move',
'connect.end',
], HIGHER_PRIORITY, function(event) {
var context = event.context,
source = context.source,
target = context.target;
if (event.originalEvent && isCmd(event.originalEvent)) {
return;
}
if (!context.initialSourcePosition) {
context.initialSourcePosition = context.sourcePosition;
}
var connectionAttrs = rules.allowed('connection.create', {
source: source,
target: target
});
if (target && isAnyType(connectionAttrs, [
'bpmn:Association',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:SequenceFlow'
])) {
// snap source
context.sourcePosition = mid(source);
// snap target
snapToPosition(event, mid(target));
} else if (isType(connectionAttrs, 'bpmn:MessageFlow')) {
if (is(source, 'bpmn:Event')) {
// snap source
context.sourcePosition = mid(source);
}
if (is(target, 'bpmn:Event')) {
// snap target
snapToPosition(event, mid(target));
}
} else {
// un-snap source
context.sourcePosition = context.initialSourcePosition;
}
});
}
BpmnConnectSnapping.$inject = [
'eventBus',
'rules'
];
// helpers //////////
function snapToPosition(event, position) {
setSnapped(event, 'x', position.x);
setSnapped(event, 'y', position.y);
}
function isType(attrs, type) {
return attrs && attrs.type === type;
}
function isAnyType(attrs, types) {
return some(types, function(type) {
return isType(attrs, type);
});
}

View File

@ -0,0 +1,137 @@
import inherits from 'inherits';
import CreateMoveSnapping from 'diagram-js/lib/features/snapping/CreateMoveSnapping';
import {
isSnapped,
setSnapped,
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { is } from '../../util/ModelUtil';
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
import { getBoundaryAttachment } from './BpmnSnappingUtil';
var HIGH_PRIORITY = 1500;
/**
* Snap during create and move.
*
* @param {BpmnRules} bpmnRules
* @param {EventBus} eventBus
* @param {Injector} injector
*/
export default function BpmnCreateMoveSnapping(bpmnRules, eventBus, injector) {
injector.invoke(CreateMoveSnapping, this);
// creating first participant
eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
function canAttach(shape, target, position) {
return bpmnRules.canAttach([ shape ], target, null, position) === 'attach';
}
// snap boundary events
eventBus.on([
'create.move',
'create.end',
'shape.move.move',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
target = context.target,
shape = context.shape;
if (target && canAttach(shape, target, event) && !isSnapped(event)) {
snapBoundaryEvent(event, target);
}
});
}
inherits(BpmnCreateMoveSnapping, CreateMoveSnapping);
BpmnCreateMoveSnapping.$inject = [
'bpmnRules',
'eventBus',
'injector'
];
BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
var shape = event.shape;
if (is(shape, 'bpmn:Participant')) {
// snap to borders with higher priority
snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
}
return snapContext;
};
BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
// add sequence flow parents as snap targets
if (is(target, 'bpmn:SequenceFlow')) {
snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
}
return snapPoints;
};
BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
.filter(function(snapTarget) {
// do not snap to lanes
return !is(snapTarget, 'bpmn:Lane');
});
};
// helpers //////////
function snapBoundaryEvent(event, target) {
var targetTRBL = asTRBL(target);
var direction = getBoundaryAttachment(event, target);
if (/top/.test(direction)) {
setSnapped(event, 'y', targetTRBL.top);
} else
if (/bottom/.test(direction)) {
setSnapped(event, 'y', targetTRBL.bottom);
}
if (/left/.test(direction)) {
setSnapped(event, 'x', targetTRBL.left);
} else
if (/right/.test(direction)) {
setSnapped(event, 'x', targetTRBL.right);
}
}
function setSnappedIfConstrained(event) {
var context = event.context,
createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
var top = createConstraints.top,
right = createConstraints.right,
bottom = createConstraints.bottom,
left = createConstraints.left;
if ((left && left >= event.x) || (right && right <= event.x)) {
setSnapped(event, 'x', event.x);
}
if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
setSnapped(event, 'y', event.y);
}
}

View File

@ -1,358 +0,0 @@
import inherits from 'inherits';
import {
forEach,
isNumber
} from 'min-dash';
import Snapping from 'diagram-js/lib/features/snapping/Snapping';
import { is } from '../../util/ModelUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import {
bottomRight,
isSnapped,
mid,
setSnapped,
topLeft
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
import { getBoundaryAttachment } from './BpmnSnappingUtil';
import { getLanesRoot } from '../modeling/util/LaneUtil';
var round = Math.round;
var HIGH_PRIORITY = 1500;
/**
* BPMN-specific snapping.
*
* @param {BpmnRules} bpmnRules
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Injector} injector
*/
export default function BpmnSnapping(bpmnRules, elementRegistry, eventBus, injector) {
injector.invoke(Snapping, this);
function canAttach(shape, target, position) {
return bpmnRules.canAttach([ shape ], target, null, position) === 'attach';
}
function canConnect(source, target) {
return bpmnRules.canConnect(source, target);
}
// creating first participant
eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
/**
* Snap boundary events to elements border
*/
eventBus.on([
'create.move',
'create.end',
'shape.move.move',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
target = context.target,
shape = context.shape;
if (target && !isSnapped(event) && canAttach(shape, target, event)) {
snapBoundaryEvent(event, shape, target);
}
});
/**
* Adjust parent for flowElements to the target participant
* when droping onto lanes.
*/
eventBus.on([
'shape.move.hover',
'shape.move.move',
'shape.move.end',
'create.hover',
'create.move',
'create.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
hover = event.hover;
if (is(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
event.hover = getLanesRoot(hover);
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
/**
* Snap sequence flows.
*/
eventBus.on([
'connect.move',
'connect.hover',
'connect.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
source = context.source,
target = context.target;
var connection = canConnect(source, target) || {};
if (!context.initialSourcePosition) {
context.initialSourcePosition = context.sourcePosition;
}
if (
target && (
connection.type === 'bpmn:Association' ||
connection.type === 'bpmn:DataOutputAssociation' ||
connection.type === 'bpmn:DataInputAssociation' ||
connection.type === 'bpmn:SequenceFlow'
)
) {
// snap source
context.sourcePosition = mid(source);
// snap target
snapToPosition(event, mid(target));
} else
if (connection.type === 'bpmn:MessageFlow') {
if (is(source, 'bpmn:Event')) {
// snap source
context.sourcePosition = mid(source);
}
if (is(target, 'bpmn:Event')) {
// snap target
snapToPosition(event, mid(target));
}
}
else {
// otherwise reset source snap
context.sourcePosition = context.initialSourcePosition;
}
});
}
inherits(BpmnSnapping, Snapping);
BpmnSnapping.$inject = [
'bpmnRules',
'elementRegistry',
'eventBus',
'injector'
];
BpmnSnapping.prototype.initSnap = function(event) {
var context = event.context,
shape = event.shape,
shapeMid,
shapeBounds,
shapeTopLeft,
shapeBottomRight,
snapContext;
snapContext = Snapping.prototype.initSnap.call(this, event);
if (is(shape, 'bpmn:Participant')) {
// snap to borders with higher priority
snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
}
if (shape) {
shapeMid = mid(shape, event);
shapeBounds = {
width: shape.width,
height: shape.height,
x: isNaN(shape.x) ? round(shapeMid.x - shape.width / 2) : shape.x,
y: isNaN(shape.y) ? round(shapeMid.y - shape.height / 2) : shape.y
};
shapeTopLeft = topLeft(shapeBounds);
shapeBottomRight = bottomRight(shapeBounds);
snapContext.setSnapOrigin('top-left', {
x: shapeTopLeft.x - event.x,
y: shapeTopLeft.y - event.y
});
snapContext.setSnapOrigin('bottom-right', {
x: shapeBottomRight.x - event.x,
y: shapeBottomRight.y - event.y
});
forEach(shape.outgoing, function(c) {
var docking = c.waypoints[0];
docking = docking.original || docking;
snapContext.setSnapOrigin(c.id + '-docking', {
x: docking.x - event.x,
y: docking.y - event.y
});
});
forEach(shape.incoming, function(c) {
var docking = c.waypoints[c.waypoints.length - 1];
docking = docking.original || docking;
snapContext.setSnapOrigin(c.id + '-docking', {
x: docking.x - event.x,
y: docking.y - event.y
});
});
}
var source = context.source;
if (source) {
snapContext.addDefaultSnap('mid', mid(source));
}
};
BpmnSnapping.prototype.addTargetSnaps = function(snapPoints, shape, target) {
// snap shape to itself
if (isNumber(shape.x) && isNumber(shape.y)) {
snapPoints.add('mid', mid(shape));
}
// use target parent as snap target
if (is(shape, 'bpmn:BoundaryEvent') && shape.type !== 'label') {
target = target.parent;
}
// add sequence flow parents as snap targets
if (is(target, 'bpmn:SequenceFlow')) {
this.addTargetSnaps(snapPoints, shape, target.parent);
}
var siblings = this.getSiblings(shape, target) || [];
forEach(siblings, function(sibling) {
// do not snap to lanes
if (is(sibling, 'bpmn:Lane')) {
return;
}
if (sibling.waypoints) {
forEach(sibling.waypoints.slice(1, -1), function(waypoint, i) {
var nextWaypoint = sibling.waypoints[i + 2],
previousWaypoint = sibling.waypoints[i];
if (!nextWaypoint || !previousWaypoint) {
throw new Error('waypoints must exist');
}
if (nextWaypoint.x === waypoint.x ||
nextWaypoint.y === waypoint.y ||
previousWaypoint.x === waypoint.x ||
previousWaypoint.y === waypoint.y) {
snapPoints.add('mid', waypoint);
}
});
return;
}
snapPoints.add('mid', mid(sibling));
if (is(sibling, 'bpmn:Participant')) {
snapPoints.add('top-left', topLeft(sibling));
snapPoints.add('bottom-right', bottomRight(sibling));
}
});
forEach(shape.incoming, function(c) {
if (siblings.indexOf(c.source) === -1) {
snapPoints.add('mid', mid(c.source));
}
var docking = c.waypoints[0];
snapPoints.add(c.id + '-docking', docking.original || docking);
});
forEach(shape.outgoing, function(c) {
if (siblings.indexOf(c.target) === -1) {
snapPoints.add('mid', mid(c.target));
}
var docking = c.waypoints[c.waypoints.length - 1];
snapPoints.add(c.id + '-docking', docking.original || docking);
});
};
// helpers //////////
function snapBoundaryEvent(event, shape, target) {
var targetTRBL = asTRBL(target);
var direction = getBoundaryAttachment(event, target);
if (/top/.test(direction)) {
setSnapped(event, 'y', targetTRBL.top);
} else
if (/bottom/.test(direction)) {
setSnapped(event, 'y', targetTRBL.bottom);
}
if (/left/.test(direction)) {
setSnapped(event, 'x', targetTRBL.left);
} else
if (/right/.test(direction)) {
setSnapped(event, 'x', targetTRBL.right);
}
}
function snapToPosition(event, position) {
setSnapped(event, 'x', position.x);
setSnapped(event, 'y', position.y);
}
function setSnappedIfConstrained(event) {
var context = event.context,
createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
var top = createConstraints.top,
right = createConstraints.right,
bottom = createConstraints.bottom,
left = createConstraints.left;
if ((left && left >= event.x) || (right && right <= event.x)) {
setSnapped(event, 'x', event.x);
}
if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
setSnapped(event, 'y', event.y);
}
}

View File

@ -1,6 +1,13 @@
import BpmnSnapping from './BpmnSnapping';
import BpmnConnectSnapping from './BpmnConnectSnapping';
import BpmnCreateMoveSnapping from './BpmnCreateMoveSnapping';
import SnappingModule from 'diagram-js/lib/features/snapping';
export default {
__init__: [ 'snapping' ],
snapping: [ 'type', BpmnSnapping ]
__depends__: [ SnappingModule ],
__init__: [
'connectSnapping',
'createMoveSnapping'
],
connectSnapping: [ 'type', BpmnConnectSnapping ],
createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
};

View File

@ -0,0 +1,54 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1xakeh4" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:collaboration id="Collaboration_1">
<bpmn:participant id="Participant_1" processRef="Process_1" />
<bpmn:participant id="Participant_2" processRef="Process_2" />
</bpmn:collaboration>
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" />
<bpmn:endEvent id="EndEvent_1" />
<bpmn:task id="Task_1" />
<bpmn:dataObjectReference id="DataObjectReference_1" dataObjectRef="DataObject_16xfc7e" />
<bpmn:dataObject id="DataObject_16xfc7e" />
</bpmn:process>
<bpmn:process id="Process_2" isExecutable="false">
<bpmn:task id="Task_2" />
<bpmn:intermediateCatchEvent id="IntermediateCatchEvent_1">
<bpmn:messageEventDefinition />
</bpmn:intermediateCatchEvent>
<bpmn:intermediateThrowEvent id="IntermediateThrowEvent_1">
<bpmn:messageEventDefinition />
</bpmn:intermediateThrowEvent>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1">
<bpmndi:BPMNShape id="Participant_03743bx_di" bpmnElement="Participant_1" isHorizontal="true">
<dc:Bounds x="32" y="61.5" width="600" height="383" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="82" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_1fetlei_di" bpmnElement="EndEvent_1">
<dc:Bounds x="182" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_0ed1rn5_di" bpmnElement="Task_1">
<dc:Bounds x="250" y="260" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_0pf2ezq_di" bpmnElement="DataObjectReference_1">
<dc:Bounds x="382" y="375" width="36" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1ooemal_di" bpmnElement="Participant_2" isHorizontal="true">
<dc:Bounds x="32" y="479" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_0iwlfts_di" bpmnElement="Task_2">
<dc:Bounds x="350" y="560" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="IntermediateCatchEvent_08covmw_di" bpmnElement="IntermediateCatchEvent_1">
<dc:Bounds x="182" y="582" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="IntermediateThrowEvent_1jsmhky_di" bpmnElement="IntermediateThrowEvent_1">
<dc:Bounds x="82" y="582" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,212 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import connectModule from 'diagram-js/lib/features/connect';
import coreModule from 'lib/core';
import globalConnectModule from 'diagram-js/lib/features/global-connect';
import modelingModule from 'lib/features/modeling';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents';
describe('features/snapping - BpmnConnectSnapping', function() {
var testModules = [
connectModule,
coreModule,
globalConnectModule,
modelingModule,
rulesModule,
snappingModule
];
var diagramXML = require('./BpmnConnectSnapping.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
describe('association', function() {
describe('connect', function() {
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
dataObjectReference = elementRegistry.get('DataObjectReference_1'),
dataObjectReferenceGfx = elementRegistry.getGraphics(dataObjectReference);
// when
connect.start(canvasEvent({ x: 300, y: 300 }), task);
dragging.hover({ element: dataObjectReference, gfx: dataObjectReferenceGfx });
dragging.move(canvasEvent({ x: 410, y: 410 }));
dragging.end();
// then
var waypoints = task.outgoing[0].waypoints;
expect(waypoints[1].original).to.eql({ x: 400, y: 400 });
}));
});
});
describe('sequence flow', function() {
describe('connect', function() {
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
endEvent = elementRegistry.get('EndEvent_1'),
endEventGfx = elementRegistry.getGraphics(endEvent);
// when
connect.start(canvasEvent({ x: 100, y: 100 }), startEvent);
dragging.hover({ element: endEvent, gfx: endEventGfx });
dragging.move(canvasEvent({ x: 210, y: 210 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints[3].original).to.eql({ x: 200, y: 200 });
}));
});
describe('global connect', function() {
it('should snap target', inject(
function(connect, dragging, elementRegistry, eventBus) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
endEvent = elementRegistry.get('EndEvent_1'),
endEventGfx = elementRegistry.getGraphics(endEvent);
// when
connect.start(null, startEvent, { x: 110, y: 110 });
dragging.hover({ element: endEvent, gfx: endEventGfx });
dragging.move(canvasEvent({ x: 210, y: 210 }));
dragging.end();
// then
var waypoints = startEvent.outgoing[0].waypoints;
expect(waypoints[0].original).to.eql({ x: 100, y: 100 });
expect(waypoints[3].original).to.eql({ x: 200, y: 200 });
}
));
});
});
describe('message flow', function() {
describe('connect', function() {
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
intermediateCatchEvent = elementRegistry.get('IntermediateCatchEvent_1'),
intermediateCatchEventGfx = elementRegistry.getGraphics(intermediateCatchEvent);
// when
connect.start(canvasEvent({ x: 300, y: 300 }), task);
dragging.hover({ element: intermediateCatchEvent, gfx: intermediateCatchEventGfx });
dragging.move(canvasEvent({ x: 210, y: 610 }));
dragging.end();
// then
var waypoints = task.outgoing[0].waypoints;
expect(waypoints[3].original).to.eql({ x: 200, y: 600 });
}));
});
describe('global connect', function() {
it('should snap source', inject(function(connect, dragging, elementRegistry) {
// given
var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'),
task = elementRegistry.get('Task_1'),
taskGfx = elementRegistry.getGraphics(task);
// when
connect.start(null, intermediateThrowEvent, { x: 110, y: 610 });
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({ x: 310, y: 310 }));
dragging.end();
// then
var waypoints = intermediateThrowEvent.outgoing[0].waypoints;
expect(waypoints[0].original).to.eql({ x: 100, y: 600 });
expect(waypoints[3].original).to.eql({ x: 310, y: 310 }); // NOT snapped
}));
it('should snap target', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_1'),
intermediateCatchEvent = elementRegistry.get('IntermediateCatchEvent_1'),
intermediateCatchEventGfx = elementRegistry.getGraphics(intermediateCatchEvent);
// when
connect.start(null, task, { x: 310, y: 310 });
dragging.hover({ element: intermediateCatchEvent, gfx: intermediateCatchEventGfx });
dragging.move(canvasEvent({ x: 210, y: 610 }));
dragging.end();
// then
var waypoints = task.outgoing[0].waypoints;
expect(waypoints[0].original).to.eql({ x: 310, y: 310 }); // NOT snapped
expect(waypoints[3].original).to.eql({ x: 200, y: 600 });
}));
});
});
});

View File

@ -0,0 +1,21 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_08wp20a" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:subProcess id="SubProcess_1" />
<bpmn:task id="Task_1" />
<bpmn:boundaryEvent id="BoundaryEvent_1" attachedToRef="SubProcess_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="SubProcess_0nmy4dr_di" bpmnElement="SubProcess_1" isExpanded="true">
<dc:Bounds x="100" y="100" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_04xg7vj_di" bpmnElement="Task_1">
<dc:Bounds x="100" y="400" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_0oi65v9_di" bpmnElement="BoundaryEvent_1">
<dc:Bounds x="282" y="282" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,31 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_0yz080z" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:collaboration id="Collaboration_1">
<bpmn:participant id="Participant_1" processRef="Process_1" />
</bpmn:collaboration>
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:laneSet id="LaneSet_1v4qu8c">
<bpmn:lane id="Lane_0asua49">
<bpmn:flowNodeRef>Task_1</bpmn:flowNodeRef>
</bpmn:lane>
<bpmn:lane id="Lane_0thsd04" />
</bpmn:laneSet>
<bpmn:task id="Task_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1">
<bpmndi:BPMNShape id="Participant_1c5vcmc_di" bpmnElement="Participant_1" isHorizontal="true">
<dc:Bounds x="100" y="100" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Lane_0asua49_di" bpmnElement="Lane_0asua49" isHorizontal="true">
<dc:Bounds x="130" y="100" width="570" height="125" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Lane_0thsd04_di" bpmnElement="Lane_0thsd04" isHorizontal="true">
<dc:Bounds x="130" y="225" width="570" height="125" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1i9ngbl_di" bpmnElement="Task_1">
<dc:Bounds x="150" y="125" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,13 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1tk7vec" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:task id="Task_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Task_1mjbupu_di" bpmnElement="Task_1">
<dc:Bounds x="100" y="100" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,30 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0o5cz3h" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:process id="Process_0kw81oc" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="EndEvent_1" />
<bpmn:task id="Task_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0kw81oc">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="82" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_03zjpp9_di" bpmnElement="EndEvent_1">
<dc:Bounds x="482" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1s3hj82_di" bpmnElement="SequenceFlow_1">
<di:waypoint x="118" y="100" />
<di:waypoint x="482" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Task_10kgr65_di" bpmnElement="Task_1">
<dc:Bounds x="200" y="200" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,381 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import TestContainer from 'mocha-test-container-support';
import coreModule from 'lib/core';
import createModule from 'diagram-js/lib/features/create';
import modelingModule from 'lib/features/modeling';
import moveModule from 'diagram-js/lib/features/move';
import rulesModule from 'lib/features/rules';
import snappingModule from 'lib/features/snapping';
import {
isSnapped,
mid
} from 'diagram-js/lib/features/snapping/SnapUtil';
import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents';
import { queryAll as domQueryAll } from 'min-dom';
import { attr as svgAttr } from 'tiny-svg';
describe('features/snapping - BpmnCreateMoveSnapping', function() {
var testModules = [
coreModule,
createModule,
modelingModule,
moveModule,
rulesModule,
snappingModule
];
describe('create participant', function() {
describe('process', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.process.bpmn');
it('should snap particpant if constrained', function(done) {
bootstrapModeler(diagramXML, {
container: TestContainer.get(this),
modules: testModules
})(function() {
// when
inject(function(canvas, create, dragging, elementFactory, eventBus) {
// given
dragging.setOptions({ manual: true });
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
create.start(canvasEvent({ x: 0, y: 0 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
eventBus.once('create.move', function(event) {
// then
// expect snapped to avoid snapping outside of constraints
expect(isSnapped(event)).to.be.true;
done();
});
// when
dragging.move(canvasEvent({ x: 1000, y: 1000 }));
})();
});
});
});
describe('collaboration', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.collaboration.bpmn');
it('should snap to participant border with higher priority', function(done) {
var container = TestContainer.get(this);
bootstrapModeler(diagramXML, {
container: container,
modules: testModules
})(function() {
// when
inject(function(create, dragging, elementFactory, elementRegistry, eventBus) {
// given
dragging.setOptions({ manual: true });
var participant = elementFactory.createParticipantShape(false),
collaboration = elementRegistry.get('Collaboration_1'),
collaborationGfx = elementRegistry.getGraphics(collaboration);
create.start(canvasEvent({ x: 0, y: 0 }), participant);
dragging.hover({ element: collaboration, gfx: collaborationGfx });
dragging.move(canvasEventTopLeft({ x: 0, y: 0 }, participant));
eventBus.once('create.move', function(event) {
// then
// expect snap line at left border of participant
expect(svgAttr(domQueryAll('.djs-snap-line', container)[1], 'd'))
.to.equal('M 100,-100000 L 100, +100000');
done();
});
// when
dragging.move(canvasEventTopLeft({ x: 95, y: 400 }, participant));
})();
});
});
});
});
describe('boundary events', function() {
describe('creating boundary event', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.process.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task, taskGfx, intermediateThrowEvent;
beforeEach(inject(function(create, dragging, elementRegistry, elementFactory) {
task = elementRegistry.get('Task_1');
taskGfx = elementRegistry.getGraphics(task);
intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({ element: task, gfx: taskGfx });
dragging.setOptions({ manual: false });
}));
it('should snap to top', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 150, y: 95 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 150,
y: 100 // 95 snapped to 100
});
}));
it('should snap to right', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 195, y: 140 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 200, // 195 snapped to 200
y: 140
});
}));
it('should snap to bottom', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 150, y: 175 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 150,
y: 180 // 175 snapped to 180
});
}));
it('should snap to left', inject(function(dragging) {
// when
dragging.move(canvasEvent({ x: 95, y: 140 }));
dragging.end();
// then
var boundaryEvent = getBoundaryEvent(task);
expect(mid(boundaryEvent)).to.eql({
x: 100, // 95 snapped to 100
y: 140
});
}));
});
describe('snapping to boundary events', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.boundary-events.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task;
beforeEach(inject(function(dragging, elementRegistry, move) {
task = elementRegistry.get('Task_1');
var process = elementRegistry.get('Process_1'),
processGfx = elementRegistry.getGraphics(process);
dragging.setOptions({ manual: true });
move.start(canvasEventTopLeft({ x: 100, y: 400 }, task), task, true);
dragging.hover({ element: process, gfx: processGfx });
dragging.move(canvasEventTopLeft({ x: 100, y: 400 }, task));
}));
it('should snap to boundary events', inject(function(dragging) {
// when
dragging.move(canvasEventTopLeft({ x: 245, y: 400 }, task));
dragging.end();
// then
expect(task).to.have.bounds({
x: 250, // 245 snapped to 250
y: 400,
width: 100,
height: 80
});
}));
});
});
describe('sequence flows', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.sequence-flows.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var sequenceFlow, sequenceFlowGfx, task;
beforeEach(inject(function(create, dragging, elementRegistry, elementFactory) {
sequenceFlow = elementRegistry.get('SequenceFlow_1');
sequenceFlowGfx = elementRegistry.getGraphics(sequenceFlow);
task = elementFactory.createShape({
type: 'bpmn:Task'
});
create.start(canvasEvent({ x: 0, y: 0 }), task);
dragging.hover({ element: sequenceFlow, gfx: sequenceFlowGfx });
dragging.setOptions({ manual: false });
}));
it('should add snap targets of sequence flow parent', inject(function(dragging) {
// when
dragging.move(canvasEventTopLeft({ x: 195, y: 60 }, task));
dragging.end();
// then
expect(task).to.have.bounds({
x: 200, // 195 snapped to 200
y: 60,
width: 100,
height: 80
});
}));
});
describe('lanes', function() {
var diagramXML = require('./BpmnCreateMoveSnapping.collaboration.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task;
beforeEach(inject(function(dragging, elementRegistry, move) {
task = elementRegistry.get('Task_1');
dragging.setOptions({ manual: false });
move.start(canvasEvent({ x: 200, y: 165 }), task);
}));
it('should should NOT snap to lanes', inject(function(dragging) {
// when
// lane mid is { x: 415, y: 162.5 }
dragging.move(canvasEvent({ x: 410, y: 160 }));
dragging.end();
// then
expect(task).to.have.bounds({
x: 360,
y: 120,
width: 100,
height: 80
});
}));
});
});
// helpers //////////
function canvasEventTopLeft(position, shape) {
return canvasEvent({
x: position.x + shape.width / 2,
y: position.y + shape.height / 2
});
}
function getBoundaryEvent(element) {
return element.attachers[0];
}

View File

@ -1,55 +0,0 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.1.1">
<bpmn:collaboration id="Collaboration_1">
<bpmn:participant id="Participant_1" processRef="Process_1" />
<bpmn:participant id="Participant_2" processRef="Process_1e043dv" />
</bpmn:collaboration>
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" />
<bpmn:task id="Task_1" />
<bpmn:dataObjectReference id="DataObjectReference_1" dataObjectRef="DataObject_1" />
<bpmn:dataObject id="DataObject_1" />
</bpmn:process>
<bpmn:process id="Process_1e043dv" isExecutable="false">
<bpmn:task id="Task_2" />
<bpmn:dataStoreReference id="DataStoreReference_1" />
<bpmn:intermediateThrowEvent id="IntermediateEvent" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1">
<bpmndi:BPMNShape id="Participant_1sh90bv_di" bpmnElement="Participant_1">
<dc:Bounds x="50" y="58" width="370" height="120" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="100" y="100" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="73" y="136" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_005jq1a_di" bpmnElement="Task_1">
<dc:Bounds x="300" y="78" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1lvgmm2_di" bpmnElement="Participant_2">
<dc:Bounds x="50" y="185" width="370" height="113" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_0ythg78_di" bpmnElement="Task_2">
<dc:Bounds x="240" y="200" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataStoreReference_1_di" bpmnElement="DataStoreReference_1">
<dc:Bounds x="101" y="215" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="81" y="269" width="90" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_1_di" bpmnElement="DataObjectReference_1">
<dc:Bounds x="196" y="93" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="214" y="147" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="IntermediateEvent_di" bpmnElement="IntermediateEvent">
<dc:Bounds x="175" y="222" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -1,38 +0,0 @@
<?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:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="EndEvent_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="156" y="103" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="242" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1_di" bpmnElement="SequenceFlow_1">
<di:waypoint x="192" y="121" />
<di:waypoint x="242" y="121" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
<dc:Bounds x="392" y="103" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_2_di" bpmnElement="SequenceFlow_2">
<di:waypoint x="342" y="121" />
<di:waypoint x="392" y="121" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -1,27 +0,0 @@
<?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:task id="Task_1" />
<bpmn:boundaryEvent id="BoundaryEvent_1" name="Label" attachedToRef="Task_1" />
<bpmn:startEvent id="StartEvent_1" name="Label" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="156" y="70" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_1_di" bpmnElement="BoundaryEvent_1">
<dc:Bounds x="218" y="132" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="190" y="180" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
<dc:Bounds x="320" y="92" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="292" y="135" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -1,41 +0,0 @@
<?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" exporter="Camunda Modeler" exporterVersion="1.9.0-dev">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:task id="Task_099t40a" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="StartEvent_1oc4unz_di" bpmnElement="StartEvent_1">
<dc:Bounds x="70" y="49" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="88" y="88" width="0" height="13" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1ndpsyi_di" bpmnElement="Task_1">
<dc:Bounds x="538" y="405" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1mpqh64_di" bpmnElement="SequenceFlow_1">
<di:waypoint xsi:type="dc:Point" x="106" y="67" />
<di:waypoint xsi:type="dc:Point" x="191" y="67" />
<di:waypoint xsi:type="dc:Point" x="191" y="134" />
<di:waypoint xsi:type="dc:Point" x="304" y="134" />
<di:waypoint xsi:type="dc:Point" x="304" y="325" />
<di:waypoint xsi:type="dc:Point" x="588" y="325" />
<di:waypoint xsi:type="dc:Point" x="588" y="405" />
<bpmndi:BPMNLabel>
<dc:Bounds x="247.5" y="112.5" width="0" height="13" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Task_099t40a_di" bpmnElement="Task_099t40a">
<dc:Bounds x="118" y="405" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -1,946 +0,0 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import TestContainer from 'mocha-test-container-support';
import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';
import coreModule from 'lib/core';
import snappingModule from 'lib/features/snapping';
import modelingModule from 'lib/features/modeling';
import createModule from 'diagram-js/lib/features/create';
import moveModule from 'diagram-js/lib/features/move';
import rulesModule from 'lib/features/rules';
import connectModule from 'diagram-js/lib/features/connect';
import { isSnapped } from 'diagram-js/lib/features/snapping/SnapUtil';
describe('features/snapping - BpmnSnapping', function() {
var testModules = [
coreModule,
snappingModule,
modelingModule,
createModule,
rulesModule,
moveModule,
connectModule
];
describe('on itself', function() {
var diagramXML = require('./BpmnSnapping.general.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task;
beforeEach(inject(function(dragging, elementRegistry) {
task = elementRegistry.get('Task_1');
dragging.setOptions({ manual: true });
}));
it('should snap to itself', inject(function(canvas, dragging, move) {
// given
var originalPosition = {
x: task.x,
y: task.y
};
var taskGfx = canvas.getGraphics(task);
// when
move.start(canvasEvent(originalPosition), task);
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({
x: originalPosition.x + 5,
y: originalPosition.y + 5
}));
dragging.end();
// then
expect(task.x).to.eql(originalPosition.x);
expect(task.y).to.eql(originalPosition.y);
}));
});
describe('on Boundary Events', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/process.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var task, intermediateThrowEvent;
beforeEach(inject(function(elementFactory, elementRegistry, dragging) {
task = elementRegistry.get('Task_1');
intermediateThrowEvent = elementFactory.createShape({
type: 'bpmn:IntermediateThrowEvent'
});
dragging.setOptions({ manual: true });
}));
afterEach(inject(function(dragging) {
dragging.setOptions({ manual: false });
}));
it('should snap on create to the bottom',
inject(function(canvas, create, dragging, elementRegistry) {
// given
var taskGfx = canvas.getGraphics(task);
// when
create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({ x: 382, y: 170 }));
dragging.end();
var boundaryEvent = elementRegistry.get(task.attachers[0].id);
// then
expect(boundaryEvent).to.have.bounds({ x: 364, y: 167, width: 36, height: 36 });
})
);
it('should snap on create to the left',
inject(function(canvas, create, dragging, elementRegistry) {
// given
var taskGfx = canvas.getGraphics(task);
// when
create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent);
dragging.hover({ element: task, gfx: taskGfx });
dragging.move(canvasEvent({ x: 382, y: 115 }));
dragging.end();
var boundaryEvent = elementRegistry.get(task.attachers[0].id);
// then
expect(boundaryEvent).to.have.bounds({ x: 364, y: 87, width: 36, height: 36 });
})
);
});
describe('on Sequence Flows', function() {
var diagramXML = require('./BpmnSnapping.sequenceFlow.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var sequenceFlow, task;
beforeEach(inject(function(elementFactory, elementRegistry, dragging) {
sequenceFlow = elementRegistry.get('SequenceFlow_1');
task = elementFactory.createShape({
type: 'bpmn:Task'
});
dragging.setOptions({ manual: true });
}));
afterEach(inject(function(dragging) {
dragging.setOptions({ manual: false });
}));
it('should snap vertically',
inject(function(canvas, create, dragging) {
// given
var sequenceFlowGfx = canvas.getGraphics(sequenceFlow);
// when
create.start(canvasEvent({ x: 0, y: 0 }), task);
dragging.hover({ element: sequenceFlow, gfx: sequenceFlowGfx });
dragging.move(canvasEvent({ x: 300, y: 245 }));
dragging.end();
// then
expect(task.x).to.eql(254);
})
);
it('should snap horizontally',
inject(function(canvas, create, dragging) {
// given
var sequenceFlowGfx = canvas.getGraphics(sequenceFlow);
// when
create.start(canvasEvent({ x: 0, y: 0 }), task);
dragging.hover({ element: sequenceFlow, gfx: sequenceFlowGfx });
dragging.move(canvasEvent({ x: 410, y: 320 }));
dragging.end();
// then
expect(task.y).to.eql(285);
})
);
it('should NOT snap shape to first waypoint',
inject(function(canvas, elementRegistry, move, dragging) {
// given
var shape = elementRegistry.get('StartEvent_1');
var originalPosition = {
x: shape.x,
y: shape.y
};
var rootElement = canvas.getRootElement();
// when
move.start(canvasEvent(originalPosition), shape);
dragging.hover({
element: rootElement,
gfx: canvas.getGraphics(rootElement)
});
dragging.move(canvasEvent({
x: originalPosition.x + 10,
y: originalPosition.y
}));
dragging.move(canvasEvent({
x: originalPosition.x + 20,
y: originalPosition.y - 5
}));
dragging.end();
// then
// no horizontal snap
expect(shape.x).to.eql(originalPosition.x + 20);
// vertical snap though!
expect(shape.y).to.eql(originalPosition.y);
})
);
it('should NOT snap shape to last waypoint',
inject(function(canvas, elementRegistry, move, dragging) {
// given
var shape = elementRegistry.get('Task_1');
var originalPosition = {
x: shape.x,
y: shape.y
};
var rootElement = canvas.getRootElement();
// when
move.start(canvasEvent(originalPosition), shape);
dragging.hover({
element: rootElement,
gfx: canvas.getGraphics(rootElement)
});
dragging.move(canvasEvent({
x: originalPosition.x,
y: originalPosition.y - 10
}));
dragging.move(canvasEvent({
x: originalPosition.x + 5,
y: originalPosition.y - 20
}));
dragging.end();
// then
// no vertical snap
expect(shape.y).to.eql(originalPosition.y - 20);
// horizontal snap though!
expect(shape.x).to.eql(originalPosition.x);
})
);
});
describe('on Participant create', function() {
describe('in non-empty process', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/process.bpmn');
it('should set snapped if outside of constraints', function(done) {
bootstrapModeler(diagramXML, {
container: TestContainer.get(this),
modules: testModules
})(function() {
// when
inject(function(canvas, create, dragging, elementFactory, eventBus) {
// given
dragging.setOptions({ manual: true });
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
create.start(canvasEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
eventBus.once('create.move', function(event) {
// then
// expect snapped to avoid snapping outside of constraints
expect(isSnapped(event)).to.be.true;
done();
});
// when
dragging.move(canvasEvent({ x: 0, y: 0 }));
})();
});
});
});
describe('in empty process', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/process-empty.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should not snap', inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(canvasEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 400, y: 400 }));
dragging.end(canvasEvent({ x: 400, y: 400 }));
// then
expect(participantShape).to.have.bounds({
x: 100, y: 275, width: 600, height: 250
});
}));
});
describe('in collaboration', function() {
var diagramXML = require('../../../fixtures/bpmn/collaboration/collaboration-participant.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));
it('should not snap to diagram contents', inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(canvasEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 400, y: 400 }));
dragging.end(canvasEvent({ x: 400, y: 400 }));
// then
expect(participantShape).to.have.bounds({
x: 100, y: 275, width: 600, height: 250
});
}));
it('should snap to participant border', inject(function(canvas, create, dragging, elementFactory) {
// given
var participantShape = elementFactory.createParticipantShape(false),
rootElement = canvas.getRootElement(),
rootGfx = canvas.getGraphics(rootElement);
// when
create.start(canvasEvent({ x: 50, y: 50 }), participantShape);
dragging.hover({ element: rootElement, gfx: rootGfx });
dragging.move(canvasEvent({ x: 390, y: 400 }));
dragging.end(canvasEvent({ x: 390, y: 400 }));
// then
expect(participantShape).to.have.bounds({
x: 84, y: 275, width: 600, height: 250
});
}));
});
});
describe('labels', function() {
var diagramXML = require('./BpmnSnapping.labels.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should snap to start events', inject(function(canvas, elementRegistry, move, dragging) {
var label = elementRegistry.get('StartEvent_1_label'),
rootElement = canvas.getRootElement();
var originalPosition = { x: label.x, y: label.y };
move.start(canvasEvent({ x: label.x + 3, y: label.y + 3 }), label);
dragging.hover({
element: rootElement,
gfx: elementRegistry.getGraphics(rootElement)
});
dragging.move(canvasEvent({ x: label.x + 4, y: label.y + 40 }));
dragging.move(canvasEvent({ x: label.x + 4, y: label.y + 40 }));
dragging.end();
expect(label.x).to.be.within(originalPosition.x, originalPosition.x + 1);
}));
it('should snap to boundary events', inject(function(canvas, elementRegistry, move, dragging) {
var label = elementRegistry.get('BoundaryEvent_1_label'),
rootElement = canvas.getRootElement();
var originalPosition = { x: label.x, y: label.y };
move.start(canvasEvent({ x: label.x + 2, y: label.y + 2 }), label);
dragging.hover({
element: rootElement,
gfx: elementRegistry.getGraphics(rootElement)
});
dragging.move(canvasEvent({ x: label.x + 4, y: label.y + 40 }));
dragging.move(canvasEvent({ x: label.x + 4, y: label.y + 40 }));
dragging.end();
expect(label.x).to.be.within(originalPosition.x, originalPosition.x + 1);
}));
it('should snap to siblings', inject(function(canvas, elementRegistry, move, dragging) {
var label = elementRegistry.get('BoundaryEvent_1_label'),
task = elementRegistry.get('Task_1'),
rootElement = canvas.getRootElement();
move.start(canvasEvent({ x: label.x + 2, y: label.y + 2 }), label);
dragging.hover({
element: rootElement,
gfx: elementRegistry.getGraphics(rootElement)
});
dragging.move(canvasEvent({ x: label.x-23, y: label.y+40 }));
dragging.move(canvasEvent({ x: label.x-23, y: label.y+40 }));
dragging.end();
var labelCenterX = label.x + Math.ceil(label.width / 2),
taskCenterX = task.x + Math.ceil(task.width / 2);
expect(labelCenterX).to.equal(taskCenterX);
}));
});
describe('on connect', function() {
var diagramXML = require('./BpmnSnapping.connect.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('sequence flow', function() {
it('should snap on global connect', inject(function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
var mid = {
x: startEvent.x + startEvent.width / 2,
y: startEvent.y + startEvent.height / 2
};
// when
connect.start(canvasEvent({ x: mid.x + 10, y: mid.y + 10 }), startEvent);
dragging.hover({
element: task,
gfx: elementRegistry.getGraphics(task)
});
dragging.move(canvasEvent({
x: task.x + task.width / 2,
y: task.y + task.height / 2
}));
dragging.end();
// then
var expected = [
{
original:
{
x: startEvent.x + startEvent.width / 2,
y: startEvent.y + startEvent.height / 2
},
x: startEvent.x + startEvent.width,
y: startEvent.y + startEvent.height / 2
},
{
original:
{
x: task.x + task.width / 2,
y: task.y + task.height / 2
},
x: task.x,
y: task.y + task.height / 2
}
];
expect(startEvent.outgoing[0].waypoints).to.eql(expected);
}));
it('should snap on connect', inject(function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
task = elementRegistry.get('Task_1');
var mid = { x: task.x + task.width / 2, y: task.y + task.height / 2 };
// when
connect.start(canvasEvent({ x: 0, y: 0 }), startEvent);
dragging.hover({
element: task,
gfx: elementRegistry.getGraphics(task)
});
dragging.move(canvasEvent({ x: mid.x + 10, y: mid.y + 10 }));
dragging.end();
// then
var expected = [
{
original:
{
x: startEvent.x + startEvent.width / 2,
y: startEvent.y + startEvent.height / 2
},
x: startEvent.x + startEvent.width,
y: startEvent.y + startEvent.height / 2
},
{
original:
{
x: task.x + task.width / 2,
y: task.y + task.height / 2
},
x: task.x,
y: task.y + task.height / 2
}
];
expect(startEvent.outgoing[0].waypoints).to.eql(expected);
}));
});
it('should snap data output association', inject(function(connect, dragging, elementRegistry) {
// given
var startEvent = elementRegistry.get('StartEvent_1'),
dataObjectReference = elementRegistry.get('DataObjectReference_1');
var mid = { x: dataObjectReference.x + dataObjectReference.width / 2, y: dataObjectReference.y + dataObjectReference.height / 2 };
// when
connect.start(canvasEvent({ x: 0, y: 0 }), startEvent);
dragging.hover({
element: dataObjectReference,
gfx: elementRegistry.getGraphics(dataObjectReference)
});
dragging.move(canvasEvent({ x: mid.x + 10, y: mid.y + 10 }));
dragging.end();
// then
var expected = [
{
original:
{
x: startEvent.x + startEvent.width / 2,
y: startEvent.y + startEvent.height / 2
},
x: startEvent.x + startEvent.width,
y: startEvent.y + startEvent.height / 2
},
{
original:
{
x: dataObjectReference.x + dataObjectReference.width / 2,
y: dataObjectReference.y + dataObjectReference.height / 2
},
x: dataObjectReference.x,
y: dataObjectReference.y + dataObjectReference.height / 2
}
];
expect(startEvent.outgoing[0].waypoints).to.eql(expected);
}));
it('should snap data input association', inject(function(connect, dragging, elementRegistry) {
// given
var dataStoreReference = elementRegistry.get('DataStoreReference_1'),
task = elementRegistry.get('Task_2');
var mid = { x: task.x + task.width / 2, y: task.y + task.height / 2 };
// when
connect.start(canvasEvent({ x: 0, y: 0 }), dataStoreReference);
dragging.hover({
element: task,
gfx: elementRegistry.getGraphics(task)
});
dragging.move(canvasEvent({ x: mid.x + 10, y: mid.y + 10 }));
dragging.end();
// then
var expected = [
{
original:
{
x: dataStoreReference.x + dataStoreReference.width / 2,
y: dataStoreReference.y + dataStoreReference.height / 2
},
x: dataStoreReference.x + dataStoreReference.width,
y: dataStoreReference.y + dataStoreReference.height / 2
},
{
original:
{
x: task.x + task.width / 2,
y: task.y + task.height / 2
},
x: task.x,
y: task.y + task.height / 2
}
];
expect(dataStoreReference.outgoing[0].waypoints).to.eql(expected);
}));
describe('message flow', function() {
it('should NOT snap Task -> Task on global connect', inject(function(connect, dragging, elementRegistry) {
// given
var task1 = elementRegistry.get('Task_1'),
task2 = elementRegistry.get('Task_2');
var task1Mid = { x: task1.x + task1.width / 2, y: task1.y + task1.height / 2 },
task2Mid = { x: task2.x + task2.width / 2, y: task2.y + task2.height / 2 };
// when
connect.start(null, task1, { x: 320, y: task1Mid.y + 20 });
dragging.hover({
element: task2,
gfx: elementRegistry.getGraphics(task2)
});
dragging.move(canvasEvent({
x: 320,
y: task2Mid.y - 20
}));
dragging.end();
// then
var expected = [
{
original:
{
x: 320,
y: task1Mid.y + 20
},
x: 320,
y: task1.y + task1.height
},
{
original:
{
x: 320,
y: task2Mid.y - 20
},
x: 320,
y: task2.y
}
];
expect(task1.outgoing[0].waypoints).to.eql(expected);
}));
it('should NOT snap Task -> Task on connect', inject(function(connect, dragging, elementRegistry) {
// given
var task1 = elementRegistry.get('Task_1'),
task2 = elementRegistry.get('Task_2');
var task1Mid = { x: task1.x + task1.width / 2, y: task1.y + task1.height / 2 },
task2Mid = { x: task2.x + task2.width / 2, y: task2.y + task2.height / 2 };
// when
connect.start(canvasEvent({ x: 0, y: 0 }), task1);
dragging.hover({
element: task2,
gfx: elementRegistry.getGraphics(task2)
});
dragging.move(canvasEvent({
x: task2Mid.x + 20,
y: task2Mid.y - 20
}));
dragging.end();
// then
expect(task1.outgoing[0].waypoints.length).to.equal(4);
expect(task1.outgoing[0].waypoints[0]).to.eql({
original:
{
x: task1Mid.x,
y: task1Mid.y
},
x: task1Mid.x,
y: task1.y + task1.height
});
expect(task1.outgoing[0].waypoints[3]).to.eql({
original:
{
x: task2Mid.x + 20,
y: task2Mid.y - 20
},
x: task2Mid.x + 20,
y: task2.y
});
}));
it('should snap Task -> Event on connect', inject(function(connect, dragging, elementRegistry) {
// given
var task = elementRegistry.get('Task_2'),
event = elementRegistry.get('StartEvent_1');
var taskMid = { x: task.x + task.width / 2, y: task.y + task.height / 2 },
eventMid = { x: event.x + event.width / 2, y: event.y + event.height / 2 };
// when
connect.start(canvasEvent({ x: 0, y: 0 }), task);
dragging.hover({
element: event,
gfx: elementRegistry.getGraphics(event)
});
dragging.move(canvasEvent({
x: eventMid.x + 10,
y: eventMid.y - 10
}));
dragging.end();
// then
var connection = task.outgoing[0];
expect(connection.waypoints.length).to.equal(4);
expect(connection.waypoints[0]).to.eql({
original:
{
x: taskMid.x,
y: taskMid.y
},
x: taskMid.x,
y: task.y
});
expect(connection.waypoints[3]).to.eql({
original:
{
x: eventMid.x,
y: eventMid.y
},
x: eventMid.x,
y: event.y + event.height
});
}));
it('should snap IntermediateEvent -> Task on global connect', inject(function(connect, dragging, elementRegistry) {
// given
var event = elementRegistry.get('IntermediateEvent'),
task = elementRegistry.get('Task_1');
var eventMid = { x: event.x + event.width / 2, y: event.y + event.height / 2 },
taskMid = { x: task.x + task.width / 2, y: task.y + task.height / 2 };
// when
connect.start(null, event, { x: eventMid.x - 10, y: eventMid.y + 10 });
dragging.hover({
element: task,
gfx: elementRegistry.getGraphics(task)
});
dragging.move(canvasEvent({
x: taskMid.x + 10,
y: taskMid.y - 10
}));
dragging.end();
// then
var connection = event.outgoing[0];
expect(connection.waypoints.length).to.equal(4);
expect(connection.waypoints[0]).to.eql({
original:
{
x: eventMid.x,
y: eventMid.y
},
x: eventMid.x,
y: event.y
});
expect(connection.waypoints[3]).to.eql({
original:
{
x: taskMid.x + 10,
y: taskMid.y - 10
},
x: taskMid.x + 10,
y: task.y + task.height
});
}));
});
});
});