From 2e27d7430642439e30806941d0df43018ca729eb Mon Sep 17 00:00:00 2001 From: Philipp Fromme Date: Fri, 11 Oct 2019 16:39:29 +0200 Subject: [PATCH] feat(modeling): copy and paste boundary events * allow copying boundary events without host * remove CreateBoundaryEventBehavior in favor of AttachEventBehavior Closes #1154 Closes #1202 Closes #1204 Closes #1205 --- .../modeling/behavior/AttachEventBehavior.js | 95 +++-- .../behavior/CreateBoundaryEventBehavior.js | 54 --- .../modeling/behavior/DetachEventBehavior.js | 103 +++--- .../behavior/ReplaceElementBehaviour.js | 57 ++- lib/features/modeling/behavior/index.js | 3 - lib/features/rules/BpmnRules.js | 6 +- .../snapping/BpmnCreateMoveSnapping.js | 47 +-- .../features/copy-paste/BpmnCopyPasteSpec.js | 81 ++++- .../behavior/AttachEventBehavior.bpmn | 68 ++++ .../behavior/AttachEventBehaviorSpec.js | 273 +++++++++----- .../CreateBoundaryEventBehaviorSpec.js | 56 --- .../behavior/DetachEventBehavior.bpmn | 60 ++++ .../behavior/DetachEventBehaviorSpec.js | 340 ++++++++++++------ test/spec/features/rules/BpmnRulesSpec.js | 2 +- .../snapping/BpmnCreateMoveSnappingSpec.js | 228 +++++++++--- 15 files changed, 971 insertions(+), 502 deletions(-) delete mode 100644 lib/features/modeling/behavior/CreateBoundaryEventBehavior.js create mode 100644 test/spec/features/modeling/behavior/AttachEventBehavior.bpmn delete mode 100644 test/spec/features/modeling/behavior/CreateBoundaryEventBehaviorSpec.js create mode 100644 test/spec/features/modeling/behavior/DetachEventBehavior.bpmn diff --git a/lib/features/modeling/behavior/AttachEventBehavior.js b/lib/features/modeling/behavior/AttachEventBehavior.js index 985453e2b6..22107901f4 100644 --- a/lib/features/modeling/behavior/AttachEventBehavior.js +++ b/lib/features/modeling/behavior/AttachEventBehavior.js @@ -2,68 +2,97 @@ import inherits from 'inherits'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; -import { isAny } from '../util/ModelingUtil'; import { getBusinessObject } from '../../../util/ModelUtil'; +import { isAny } from '../util/ModelingUtil'; + +import { isLabel } from '../../../util/LabelUtil'; + +var LOW_PRIORITY = 500; + /** - * BPMN specific attach event behavior + * Replace intermediate event with boundary event when creating or moving results in attached event. */ -export default function AttachEventBehavior(eventBus, bpmnReplace) { +export default function AttachEventBehavior(bpmnReplace, injector) { + injector.invoke(CommandInterceptor, this); - CommandInterceptor.call(this, eventBus); + this._bpmnReplace = bpmnReplace; - /** - * replace intermediate event with boundary event when - * attaching it to a shape - */ + var self = this; - this.preExecute('elements.move', function(context) { - var shapes = context.shapes, - host = context.newHost, - shape, - eventDefinition, - boundaryEvent, - newShape; + this.postExecuted('elements.create', LOW_PRIORITY, function(context) { + var elements = context.elements; - if (shapes.length !== 1) { + elements = elements.filter(function(shape) { + var host = shape.host; + + return shouldReplace(shape, host); + }); + + if (elements.length !== 1) { return; } - shape = shapes[0]; + elements.map(function(element) { + return elements.indexOf(element); + }).forEach(function(index) { + var host = elements[ index ]; - if (host && isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ])) { + context.elements[ index ] = self.replaceShape(elements[ index ], host); + }); + }, true); - eventDefinition = getEventDefinition(shape); - boundaryEvent = { - type: 'bpmn:BoundaryEvent', - host: host - }; + this.preExecute('elements.move', LOW_PRIORITY, function(context) { + var shapes = context.shapes, + host = context.newHost; - if (eventDefinition) { - boundaryEvent.eventDefinitionType = eventDefinition.$type; - } + if (shapes.length !== 1) { + return; + } - newShape = bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false }); + var shape = shapes[0]; - context.shapes = [ newShape ]; + if (shouldReplace(shape, host)) { + context.shapes = [ self.replaceShape(shape, host) ]; } }, true); } AttachEventBehavior.$inject = [ - 'eventBus', - 'bpmnReplace' + 'bpmnReplace', + 'injector' ]; inherits(AttachEventBehavior, CommandInterceptor); +AttachEventBehavior.prototype.replaceShape = function(shape, host) { + var eventDefinition = getEventDefinition(shape); + + var boundaryEvent = { + type: 'bpmn:BoundaryEvent', + host: host + }; + if (eventDefinition) { + boundaryEvent.eventDefinitionType = eventDefinition.$type; + } + + return this._bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false }); +}; + + +// helpers ////////// -// helper ///// function getEventDefinition(element) { - var bo = getBusinessObject(element); + var businessObject = getBusinessObject(element), + eventDefinitions = businessObject.eventDefinitions; + + return eventDefinitions && eventDefinitions[0]; +} - return bo && bo.eventDefinitions && bo.eventDefinitions[0]; +function shouldReplace(shape, host) { + return !isLabel(shape) && + isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ]) && !!host; } diff --git a/lib/features/modeling/behavior/CreateBoundaryEventBehavior.js b/lib/features/modeling/behavior/CreateBoundaryEventBehavior.js deleted file mode 100644 index a4685049fb..0000000000 --- a/lib/features/modeling/behavior/CreateBoundaryEventBehavior.js +++ /dev/null @@ -1,54 +0,0 @@ -import inherits from 'inherits'; - -import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; - -import { is } from '../../../util/ModelUtil'; - - -/** - * BPMN specific create boundary event behavior - */ -export default function CreateBoundaryEventBehavior( - eventBus, modeling, elementFactory, - bpmnFactory) { - - CommandInterceptor.call(this, eventBus); - - /** - * replace intermediate event with boundary event when - * attaching it to a shape - */ - - this.preExecute('shape.create', function(context) { - var shape = context.shape, - host = context.host, - businessObject, - boundaryEvent; - - var attrs = { - cancelActivity: true - }; - - if (host && is(shape, 'bpmn:IntermediateThrowEvent')) { - attrs.attachedToRef = host.businessObject; - - businessObject = bpmnFactory.create('bpmn:BoundaryEvent', attrs); - - boundaryEvent = { - type: 'bpmn:BoundaryEvent', - businessObject: businessObject - }; - - context.shape = elementFactory.createShape(boundaryEvent); - } - }, true); -} - -CreateBoundaryEventBehavior.$inject = [ - 'eventBus', - 'modeling', - 'elementFactory', - 'bpmnFactory' -]; - -inherits(CreateBoundaryEventBehavior, CommandInterceptor); diff --git a/lib/features/modeling/behavior/DetachEventBehavior.js b/lib/features/modeling/behavior/DetachEventBehavior.js index e8d359eae3..ae64a63615 100644 --- a/lib/features/modeling/behavior/DetachEventBehavior.js +++ b/lib/features/modeling/behavior/DetachEventBehavior.js @@ -9,67 +9,86 @@ import { import { isLabel } from '../../../util/LabelUtil'; +var LOW_PRIORITY = 500; + /** - * BPMN specific detach event behavior + * Replace boundary event with intermediate event when creating or moving results in detached event. */ -export default function DetachEventBehavior(eventBus, bpmnReplace) { +export default function DetachEventBehavior(bpmnReplace, injector) { + injector.invoke(CommandInterceptor, this); + + this._bpmnReplace = bpmnReplace; - CommandInterceptor.call(this, eventBus); + var self = this; - /** - * replace boundary event with intermediate event when - * detaching from a shape - */ + this.postExecuted('elements.create', LOW_PRIORITY, function(context) { + var elements = context.elements; - this.preExecute('elements.move', function(context) { + elements.filter(function(shape) { + var host = shape.host; + + return shouldReplace(shape, host); + }).map(function(shape) { + return elements.indexOf(shape); + }).forEach(function(index) { + context.elements[ index ] = self.replaceShape(elements[ index ]); + }); + }, true); + + this.preExecute('elements.move', LOW_PRIORITY, function(context) { var shapes = context.shapes, - host = context.newHost, - shape, - eventDefinition, - intermediateEvent, - newShape; - - if (shapes.length !== 1) { - return; - } - - shape = shapes[0]; - - if (!isLabel(shape) && !host && is(shape, 'bpmn:BoundaryEvent')) { - - eventDefinition = getEventDefinition(shape); - - if (eventDefinition) { - intermediateEvent = { - type: 'bpmn:IntermediateCatchEvent', - eventDefinitionType: eventDefinition.$type - }; - } else { - intermediateEvent = { - type: 'bpmn:IntermediateThrowEvent' - }; - } + newHost = context.newHost; - newShape = bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false }); + shapes.forEach(function(shape, index) { + var host = shape.host; - context.shapes = [ newShape ]; - } + if (shouldReplace(shape, includes(shapes, host) ? host : newHost)) { + shapes[ index ] = self.replaceShape(shape); + } + }); }, true); } DetachEventBehavior.$inject = [ - 'eventBus', - 'bpmnReplace' + 'bpmnReplace', + 'injector' ]; inherits(DetachEventBehavior, CommandInterceptor); +DetachEventBehavior.prototype.replaceShape = function(shape) { + var eventDefinition = getEventDefinition(shape), + intermediateEvent; + if (eventDefinition) { + intermediateEvent = { + type: 'bpmn:IntermediateCatchEvent', + eventDefinitionType: eventDefinition.$type + }; + } else { + intermediateEvent = { + type: 'bpmn:IntermediateThrowEvent' + }; + } + + return this._bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false }); +}; + + +// helpers ////////// -// helper ///// function getEventDefinition(element) { - var bo = getBusinessObject(element); + var businessObject = getBusinessObject(element), + eventDefinitions = businessObject.eventDefinitions; - return bo && bo.eventDefinitions && bo.eventDefinitions[0]; + return eventDefinitions && eventDefinitions[0]; } + +function shouldReplace(shape, host) { + return !isLabel(shape) && is(shape, 'bpmn:BoundaryEvent') && !host; +} + +function includes(array, item) { + return array.indexOf(item) !== -1; +} \ No newline at end of file diff --git a/lib/features/modeling/behavior/ReplaceElementBehaviour.js b/lib/features/modeling/behavior/ReplaceElementBehaviour.js index 01ed6fbef2..d0648d8356 100644 --- a/lib/features/modeling/behavior/ReplaceElementBehaviour.js +++ b/lib/features/modeling/behavior/ReplaceElementBehaviour.js @@ -1,35 +1,31 @@ import inherits from 'inherits'; -import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; - -import { - forEach -} from 'min-dash'; +import { forEach } from 'min-dash'; -import { - isEventSubProcess -} from '../../../util/DiUtil'; +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; -import { is } from '../../../util/ModelUtil'; +import { isEventSubProcess } from '../../../util/DiUtil'; /** - * Defines the behaviour of what happens to the elements inside a container - * that morphs into another BPMN element + * BPMN-specific replace behavior. */ export default function ReplaceElementBehaviour( - eventBus, bpmnReplace, bpmnRules, - elementRegistry, selection, modeling) { - - CommandInterceptor.call(this, eventBus); + bpmnReplace, + bpmnRules, + elementRegistry, + injector, + modeling, + selection +) { + injector.invoke(CommandInterceptor, this); this._bpmnReplace = bpmnReplace; this._elementRegistry = elementRegistry; this._selection = selection; - this._modeling = modeling; + // replace elements on move this.postExecuted([ 'elements.move' ], 500, function(event) { - var context = event.context, target = context.newParent, newHost = context.newHost, @@ -43,7 +39,7 @@ export default function ReplaceElementBehaviour( } }); - // Change target to host when the moving element is a `bpmn:BoundaryEvent` + // set target to host if attaching if (elements.length === 1 && newHost) { target = newHost; } @@ -55,9 +51,8 @@ export default function ReplaceElementBehaviour( } }, this); - // update attachments if the host is replaced + // update attachments on host replace this.postExecute([ 'shape.replace' ], 1500, function(e) { - var context = e.context, oldShape = context.oldShape, newShape = context.newShape, @@ -72,6 +67,7 @@ export default function ReplaceElementBehaviour( }, this); + // keep ID on shape replace this.postExecuted([ 'shape.replace' ], 1500, function(e) { var context = e.context, oldShape = context.oldShape, @@ -84,32 +80,21 @@ export default function ReplaceElementBehaviour( inherits(ReplaceElementBehaviour, CommandInterceptor); - -ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements, newHost) { +ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) { var elementRegistry = this._elementRegistry, bpmnReplace = this._bpmnReplace, - selection = this._selection, - modeling = this._modeling; + selection = this._selection; forEach(newElements, function(replacement) { - var newElement = { type: replacement.newElementType }; var oldElement = elementRegistry.get(replacement.oldElementId); - if (newHost && is(oldElement, 'bpmn:BoundaryEvent')) { - modeling.updateAttachment(oldElement, null); - } - var idx = elements.indexOf(oldElement); elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false }); - - if (newHost && is(elements[idx], 'bpmn:BoundaryEvent')) { - modeling.updateAttachment(elements[idx], newHost); - } }); if (newElements) { @@ -118,10 +103,10 @@ ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElemen }; ReplaceElementBehaviour.$inject = [ - 'eventBus', 'bpmnReplace', 'bpmnRules', 'elementRegistry', - 'selection', - 'modeling' + 'injector', + 'modeling', + 'selection' ]; diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 5f62e78eb7..2d3b86f842 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -4,7 +4,6 @@ import AttachEventBehavior from './AttachEventBehavior'; import BoundaryEventBehavior from './BoundaryEventBehavior'; import CreateBehavior from './CreateBehavior'; import FixHoverBehavior from './FixHoverBehavior'; -import CreateBoundaryEventBehavior from './CreateBoundaryEventBehavior'; import CreateDataObjectBehavior from './CreateDataObjectBehavior'; import CreateParticipantBehavior from './CreateParticipantBehavior'; import DataInputAssociationBehavior from './DataInputAssociationBehavior'; @@ -38,7 +37,6 @@ export default { 'boundaryEventBehavior', 'createBehavior', 'fixHoverBehavior', - 'createBoundaryEventBehavior', 'createDataObjectBehavior', 'createParticipantBehavior', 'dataStoreBehavior', @@ -70,7 +68,6 @@ export default { boundaryEventBehavior: [ 'type', BoundaryEventBehavior ], createBehavior: [ 'type', CreateBehavior ], fixHoverBehavior: [ 'type', FixHoverBehavior ], - createBoundaryEventBehavior: [ 'type', CreateBoundaryEventBehavior ], createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ], createParticipantBehavior: [ 'type', CreateParticipantBehavior ], dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ], diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js index fbfc1abe92..3256da62ab 100644 --- a/lib/features/rules/BpmnRules.js +++ b/lib/features/rules/BpmnRules.js @@ -908,10 +908,6 @@ function canCopy(elements, element) { return false; } - if (is(element, 'bpmn:BoundaryEvent') && !includes(elements, element.host)) { - return false; - } - return true; } @@ -930,4 +926,4 @@ function areOutgoingEventBasedGatewayConnections(connections) { function getRootElement(element) { return getParent(element, 'bpmn:Process') || getParent(element, 'bpmn:Collaboration'); -} \ No newline at end of file +} diff --git a/lib/features/snapping/BpmnCreateMoveSnapping.js b/lib/features/snapping/BpmnCreateMoveSnapping.js index 392048aeae..186c593b65 100644 --- a/lib/features/snapping/BpmnCreateMoveSnapping.js +++ b/lib/features/snapping/BpmnCreateMoveSnapping.js @@ -9,9 +9,7 @@ import { bottomRight } from 'diagram-js/lib/features/snapping/SnapUtil'; -import { - isExpanded -} from '../../util/DiUtil'; +import { isExpanded } from '../../util/DiUtil'; import { is } from '../../util/ModelUtil'; @@ -30,20 +28,15 @@ 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) { +export default function BpmnCreateMoveSnapping(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', @@ -52,10 +45,12 @@ export default function BpmnCreateMoveSnapping(bpmnRules, eventBus, injector) { 'shape.move.end' ], HIGH_PRIORITY, function(event) { var context = event.context, - target = context.target, - shape = context.shape; + canExecute = context.canExecute, + target = context.target; - if (target && canAttach(shape, target, event) && !isSnapped(event)) { + var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach); + + if (canAttach && !isSnapped(event)) { snapBoundaryEvent(event, target); } }); @@ -64,7 +59,6 @@ export default function BpmnCreateMoveSnapping(bpmnRules, eventBus, injector) { inherits(BpmnCreateMoveSnapping, CreateMoveSnapping); BpmnCreateMoveSnapping.$inject = [ - 'bpmnRules', 'eventBus', 'injector' ]; @@ -171,18 +165,27 @@ function snapBoundaryEvent(event, target) { var direction = getBoundaryAttachment(event, target); + var context = event.context, + shape = context.shape; + + var offset; + + if (shape.parent) { + offset = { x: 0, y: 0 }; + } else { + offset = getMid(shape); + } + if (/top/.test(direction)) { - setSnapped(event, 'y', targetTRBL.top); - } else - if (/bottom/.test(direction)) { - setSnapped(event, 'y', targetTRBL.bottom); + setSnapped(event, 'y', targetTRBL.top - offset.y); + } else if (/bottom/.test(direction)) { + setSnapped(event, 'y', targetTRBL.bottom - offset.y); } if (/left/.test(direction)) { - setSnapped(event, 'x', targetTRBL.left); - } else - if (/right/.test(direction)) { - setSnapped(event, 'x', targetTRBL.right); + setSnapped(event, 'x', targetTRBL.left - offset.x); + } else if (/right/.test(direction)) { + setSnapped(event, 'x', targetTRBL.right - offset.x); } } @@ -225,4 +228,4 @@ function setSnappedIfConstrained(event) { function includes(array, value) { return array.indexOf(value) !== -1; -} \ No newline at end of file +} diff --git a/test/spec/features/copy-paste/BpmnCopyPasteSpec.js b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js index 4a3253e92f..5615f41b73 100644 --- a/test/spec/features/copy-paste/BpmnCopyPasteSpec.js +++ b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js @@ -71,6 +71,83 @@ describe('features/copy-paste', function() { })); + describe('should copy boundary events without host', function() { + + it('should copy/paste', inject(function(elementRegistry, canvas, copyPaste) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + rootElement = canvas.getRootElement(); + + // when + copyPaste.copy(boundaryEvent); + + var copiedElements = copyPaste.paste({ + element: rootElement, + point: { + x: 1000, + y: 1000 + } + }); + + // then + expect(rootElement.children).to.have.length(2); + + expect(copiedElements).to.have.length(1); + + expect(copiedElements[0].type).to.eql('bpmn:IntermediateCatchEvent'); + + expect(copiedElements[0].attachedToRef).to.be.undefined; + + expect(copiedElements[0].host).to.be.undefined; + + expect(copiedElements[0].id).not.to.eql(boundaryEvent.id); + })); + + + it('should copy/paste and reattach', inject(function(elementRegistry, canvas, copyPaste) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + task = elementRegistry.get('Task_1'), + rootElement = canvas.getRootElement(); + + // when + copyPaste.copy(boundaryEvent); + + var copiedElement = copyPaste.paste({ + element: rootElement, + point: { + x: 1000, + y: 1000 + } + })[0]; + + copyPaste.copy(copiedElement); + + var attachedBoundaryEvent = copyPaste.paste({ + element: task, + point: { + x: task.x, + y: task.y + }, + hints: { + attach: 'attach' + } + })[0]; + + // then + expect(attachedBoundaryEvent.businessObject.attachedToRef).to.eql(task.businessObject); + + expect(attachedBoundaryEvent.host).to.be.eql(task); + + expect(attachedBoundaryEvent.type).to.eql('bpmn:BoundaryEvent'); + + })); + + }); + + it('should NOT override type property of descriptor', inject(function(elementRegistry) { // given @@ -309,7 +386,7 @@ describe('features/copy-paste', function() { describe('rules', function() { - it('should NOT allow copying boundary event without host', inject(function(elementRegistry) { + it('should allow copying boundary event without host', inject(function(elementRegistry) { var boundaryEvent1 = elementRegistry.get('BoundaryEvent_1'), boundaryEvent2 = elementRegistry.get('BoundaryEvent_2'); @@ -317,7 +394,7 @@ describe('features/copy-paste', function() { // when var tree = copy([ boundaryEvent1, boundaryEvent2 ]); - expect(keys(tree)).to.have.length(0); + expect(keys(tree)).to.have.length(1); })); }); diff --git a/test/spec/features/modeling/behavior/AttachEventBehavior.bpmn b/test/spec/features/modeling/behavior/AttachEventBehavior.bpmn new file mode 100644 index 0000000000..a82dfade1e --- /dev/null +++ b/test/spec/features/modeling/behavior/AttachEventBehavior.bpmn @@ -0,0 +1,68 @@ + + + + + bar + SequenceFlow_1 + SequenceFlow_2 + + + SequenceFlow_2 + + + + SequenceFlow_1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/AttachEventBehaviorSpec.js b/test/spec/features/modeling/behavior/AttachEventBehaviorSpec.js index 4f04430293..66b76733c4 100644 --- a/test/spec/features/modeling/behavior/AttachEventBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/AttachEventBehaviorSpec.js @@ -5,171 +5,276 @@ import { inject } from 'test/TestHelper'; -import modelingModule from 'lib/features/modeling'; import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; + +import { getBusinessObject } from '../../../../../lib/util/ModelUtil'; describe('features/modeling/behavior - attach events', function() { - var testModules = [ coreModule, modelingModule ]; + var testModules = [ + coreModule, + modelingModule + ]; - var processDiagramXML = require('test/spec/features/rules/BpmnRules.attaching.bpmn'); + var attachEventBehaviorXML = require('./AttachEventBehavior.bpmn'); - beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + beforeEach(bootstrapModeler(attachEventBehaviorXML, { modules: testModules })); describe('basics', function() { - it('should execute on attach', inject(function(elementRegistry, modeling) { + describe('create', function() { - // given - var eventId = 'IntermediateThrowEvent', - intermediateThrowEvent = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'), - boundaryEvent; + it('should replace', inject(function(elementFactory, elementRegistry, modeling) { - var elements = [ intermediateThrowEvent ]; + // given + var task = elementRegistry.get('Task_1'), + taskBo = getBusinessObject(task), + intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' }); - // when - modeling.moveElements(elements, { x: 0, y: -90 }, subProcess, { attach: true }); + // when + var boundaryEvent = modeling.createElements( + [ intermediateEvent ], { x: 300, y: 140 }, task, { attach: true } + )[0]; - // then - boundaryEvent = elementRegistry.get(eventId); + // then + var boundaryEventBo = getBusinessObject(boundaryEvent); - expect(intermediateThrowEvent.parent).to.not.exist; - expect(boundaryEvent).to.exist; - expect(boundaryEvent.type).to.equal('bpmn:BoundaryEvent'); - expect(boundaryEvent.businessObject.attachedToRef).to.equal(subProcess.businessObject); - })); + expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent'); + expect(boundaryEventBo.attachedToRef).to.equal(taskBo); + })); - it('should NOT execute on drop', inject(function(elementRegistry, modeling) { + it('should NOT replace', inject(function(elementFactory, elementRegistry, modeling) { - // given - var eventId = 'IntermediateThrowEvent', - intermediateThrowEvent = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'); + // given + var process = elementRegistry.get('Process_1'), + intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' }); - var elements = [ intermediateThrowEvent ]; + // when + intermediateEvent = modeling.createElements([ intermediateEvent ], { x: 300, y: 240 }, process)[0]; - // when - modeling.moveElements(elements, { x: 0, y: -150 }, subProcess); + // then + var intermediateEventBo = getBusinessObject(intermediateEvent); - // then - expect(intermediateThrowEvent.parent).to.eql(subProcess); - expect(intermediateThrowEvent.type).to.equal('bpmn:IntermediateThrowEvent'); - })); - }); + expect(intermediateEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + expect(intermediateEventBo.attachedToRef).not.to.exist; + })); - describe('event definition', function() { + it('should copy properties', inject( + function(bpmnFactory, elementFactory, elementRegistry, modeling) { - it('should copy event definitions', inject(function(elementRegistry, modeling) { + // given + var task = elementRegistry.get('Task_1'); - // given - var attachableEvents = [ - 'IntermediateThrowEvent', - 'MessageCatchEvent', - 'TimerCatchEvent', - 'SignalCatchEvent', - 'ConditionalCatchEvent' - ]; + var intermediateThrowEventBo = bpmnFactory.create('bpmn:IntermediateThrowEvent', { + name: 'foo' + }); + + var documentation = bpmnFactory.create('bpmn:Documentation', { + text: 'bar' + }); + + intermediateThrowEventBo.documentation = [ documentation ]; + + documentation.$parent = intermediateThrowEventBo; + + var intermediateThrowEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateThrowEvent', + businessObject: intermediateThrowEventBo + }); + + // when + var boundaryEvent = modeling.createElements( + [ intermediateThrowEvent ], { x: 300, y: 140 }, task, { attach: true } + )[0]; + + // then + var boundaryEventBo = getBusinessObject(boundaryEvent); + + expect(boundaryEventBo.name).to.equal('foo'); + expect(boundaryEventBo.documentation).to.have.lengthOf(1); + expect(boundaryEventBo.documentation[0].text).to.equal('bar'); + } + )); - attachableEvents.forEach(function(eventId) { + }); - var event = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'), - eventDefinitions = event.businessObject.eventDefinitions, - boundaryEvent, bo; - var elements = [ event ]; + describe('move', function() { + + it('should replace', inject(function(elementRegistry, modeling) { + + // given + var task = elementRegistry.get('Task_1'), + taskBo = getBusinessObject(task), + intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'); // when - modeling.moveElements(elements, { x: 0, y: -90 }, subProcess, { attach: true }); + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true }); // then - boundaryEvent = elementRegistry.get(eventId); - bo = boundaryEvent.businessObject; + var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'), + boundaryEventBo = getBusinessObject(boundaryEvent); + + expect(boundaryEvent).to.exist; + expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent'); + expect(boundaryEventBo.attachedToRef).to.equal(taskBo); + })); + + + it('should NOT replace', inject(function(elementRegistry, modeling) { + + // given + var process = elementRegistry.get('Process_1'), + intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'), + intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); + + // when + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 100 }, process); + + // then + expect(intermediateThrowEvent).to.exist; + expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + expect(intermediateThrowEventBo.attachedToRef).not.to.exist; + })); + + + describe('properties', function() { + + it('should copy properties', inject(function(elementRegistry, modeling) { + + // given + var task = elementRegistry.get('Task_1'), + intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'); + + // when + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true }); + + // then + var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'), + boundaryEventBo = getBusinessObject(boundaryEvent); + + expect(boundaryEventBo.name).to.equal('foo'); + expect(boundaryEventBo.documentation).to.have.lengthOf(1); + expect(boundaryEventBo.documentation[0].text).to.equal('bar'); + })); + + + describe('event definitions', function() { + + var ids = [ + 'ConditionalCatchEvent', + 'IntermediateThrowEvent_1', + 'MessageCatchEvent', + 'SignalCatchEvent', + 'TimerCatchEvent', + ]; + + function getDelta(element, task) { + return { + x: task.x + task.width / 2 - element.x - element.width / 2, + y: task.y + task.height - element.y - element.height / 2 + }; + } + + ids.forEach(function(id) { + + it('should copy event definition', inject(function(elementRegistry, modeling) { + + // given + var element = elementRegistry.get(id), + elementBo = getBusinessObject(element), + eventDefinitions = elementBo.eventDefinitions, + task = elementRegistry.get('Task_1'); + + // when + modeling.moveElements([ element ], getDelta(element, task), task, { attach: true }); + + // then + var boundaryEvent = elementRegistry.get(id), + boundaryEventBo = getBusinessObject(boundaryEvent); + + expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent'); + expect(boundaryEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); + })); + + }); + + }); - expect(boundaryEvent.type).to.equal('bpmn:BoundaryEvent'); - expect(bo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); }); - })); + + }); + }); describe('connections', function() { - var eventId = 'IntermediateThrowEventWithConnections'; - it('should remove incoming connection', inject(function(elementRegistry, modeling) { // given - var event = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'), - gateway = elementRegistry.get('Gateway_1'), - boundaryEvent; - - var elements = [ event ]; + var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'), + startEvent = elementRegistry.get('StartEvent_1'), + task = elementRegistry.get('Task_1'); // when - modeling.moveElements(elements, { x: 0, y: -90 }, subProcess, { attach: true }); + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true }); // then - boundaryEvent = elementRegistry.get(eventId); + var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'); expect(boundaryEvent.incoming).to.have.lengthOf(0); - expect(gateway.outgoing).to.have.lengthOf(0); + expect(startEvent.outgoing).to.have.lengthOf(0); })); - it('should keep outgoing connection', inject(function(elementRegistry, modeling) { + it('should NOT remove outgoing connection', inject(function(elementRegistry, modeling) { // given - var event = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'), - task = elementRegistry.get('Task_1'), - boundaryEvent; - - var elements = [ event ]; + var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'), + task = elementRegistry.get('Task_1'); // when - modeling.moveElements(elements, { x: 0, y: -90 }, subProcess, { attach: true }); + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true }); // then - boundaryEvent = elementRegistry.get(eventId); + var boundaryEvent = elementRegistry.get('IntermediateThrowEvent_1'); expect(boundaryEvent.outgoing).to.have.lengthOf(1); expect(task.incoming).to.have.lengthOf(1); })); - it('should lay out connection once', inject(function(eventBus, elementRegistry, modeling) { + it('should lay out connection once', inject(function(elementRegistry, eventBus, modeling) { // given - var layoutSpy = sinon.spy(), - event = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'); + var intermediateThrowEvent = elementRegistry.get('IntermediateThrowEvent_1'), + task = elementRegistry.get('Task_1'); - eventBus.on('commandStack.connection.layout.execute', layoutSpy); + var layoutSpy = sinon.spy(); - var elements = [ event ]; + eventBus.on('commandStack.connection.layout.execute', layoutSpy); // when - modeling.moveElements(elements, { x: 0, y: -90 }, subProcess, { attach: true }); + modeling.moveElements([ intermediateThrowEvent ], { x: 100, y: 40 }, task, { attach: true }); // then expect(layoutSpy).to.be.calledOnce; })); + }); }); -// helper ////// +// helpers ////////// function skipId(key, value) { - if (key === 'id') { return; } diff --git a/test/spec/features/modeling/behavior/CreateBoundaryEventBehaviorSpec.js b/test/spec/features/modeling/behavior/CreateBoundaryEventBehaviorSpec.js deleted file mode 100644 index 16ce881818..0000000000 --- a/test/spec/features/modeling/behavior/CreateBoundaryEventBehaviorSpec.js +++ /dev/null @@ -1,56 +0,0 @@ -import { - bootstrapModeler, - inject -} from 'test/TestHelper'; - -import modelingModule from 'lib/features/modeling'; -import coreModule from 'lib/core'; - - -describe('features/modeling/behavior - create boundary events', function() { - - var testModules = [ coreModule, modelingModule ]; - - - var processDiagramXML = require('../../../../fixtures/bpmn/collaboration/process-empty.bpmn'); - - beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); - - - it('should execute on attach', inject(function(canvas, elementFactory, modeling) { - - // given - var rootElement = canvas.getRootElement(), - task = elementFactory.createShape({ type: 'bpmn:Task' }), - intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' }); - - modeling.createShape(task, { x: 100, y: 100 }, rootElement); - - // when - var newEvent = modeling.createShape(intermediateEvent, { x: 50 + 15, y: 100 }, task, { attach: true }); - - // then - expect(newEvent.type).to.equal('bpmn:BoundaryEvent'); - expect(newEvent.businessObject.attachedToRef).to.equal(task.businessObject); - })); - - - it('should NOT execute on drop', inject(function(canvas, elementFactory, modeling) { - - // given - var rootElement = canvas.getRootElement(), - subProcess = elementFactory.createShape({ type: 'bpmn:SubProcess', isExpanded: true }), - intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent' }); - - - modeling.createShape(subProcess, { x: 300, y: 200 }, rootElement); - - // when - var newEvent = modeling.createShape(intermediateEvent, { x: 300, y: 200 }, subProcess); - - // then - expect(newEvent).to.exist; - expect(newEvent.type).to.equal('bpmn:IntermediateThrowEvent'); - })); - -}); diff --git a/test/spec/features/modeling/behavior/DetachEventBehavior.bpmn b/test/spec/features/modeling/behavior/DetachEventBehavior.bpmn new file mode 100644 index 0000000000..23331e84a2 --- /dev/null +++ b/test/spec/features/modeling/behavior/DetachEventBehavior.bpmn @@ -0,0 +1,60 @@ + + + + + + + bar + SequenceFlow_2 + + + SequenceFlow_2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/DetachEventBehaviorSpec.js b/test/spec/features/modeling/behavior/DetachEventBehaviorSpec.js index d6fc2d473f..2458546ffd 100644 --- a/test/spec/features/modeling/behavior/DetachEventBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/DetachEventBehaviorSpec.js @@ -5,197 +5,317 @@ import { inject } from 'test/TestHelper'; -import modelingModule from 'lib/features/modeling'; import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; + +import { getBusinessObject } from '../../../../../lib/util/ModelUtil'; describe('features/modeling/behavior - detach events', function() { - var testModules = [ coreModule, modelingModule ]; + var testModules = [ + coreModule, + modelingModule + ]; - var processDiagramXML = require('test/spec/features/rules/BpmnRules.detaching.bpmn'); + var detachEventBehaviorXML = require('./DetachEventBehavior.bpmn'); - beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + beforeEach(bootstrapModeler(detachEventBehaviorXML, { modules: testModules })); describe('basics', function() { - it('should execute on detach', inject(function(canvas, elementRegistry, modeling) { + describe('create', function() { - // given - var eventId = 'BoundaryEvent', - boundaryEvent = elementRegistry.get(eventId), - root = canvas.getRootElement(), - intermediateThrowEvent; + it('should replace', inject(function(elementFactory, elementRegistry, modeling) { - var elements = [ boundaryEvent ]; + // given + var process = elementRegistry.get('Process_1'); - // when - modeling.moveElements(elements, { x: 0, y: 100 }, root); + var boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent' }); - // then - intermediateThrowEvent = elementRegistry.get(eventId); + // when + var intermediateThrowEvent = modeling.createElements( + boundaryEvent, { x: 200, y: 100 }, process + )[0]; - expect(boundaryEvent.parent).to.not.exist; - expect(intermediateThrowEvent).to.exist; - expect(intermediateThrowEvent.type).to.equal('bpmn:IntermediateThrowEvent'); - expect(intermediateThrowEvent.businessObject.attachedToRef).to.not.exist; - expect(intermediateThrowEvent.parent).to.equal(root); - })); + // then + var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); + expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + })); - it('should NOT execute on move to another host', inject(function(elementRegistry, modeling) { - // given - var eventId = 'BoundaryEvent', - boundaryEvent = elementRegistry.get(eventId), - subProcess = elementRegistry.get('SubProcess_1'); + it('should NOT replace', inject(function(elementFactory, elementRegistry, modeling) { - var elements = [ boundaryEvent ]; + // given + var task = elementRegistry.get('Task_1'), + taskBo = getBusinessObject(task); - // when - modeling.moveElements(elements, { x: -20, y: 0 }, subProcess, { attach: true }); + var boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent' }), + boundaryEventBo = getBusinessObject(boundaryEvent); - // then - expect(boundaryEvent.host).to.eql(subProcess); - expect(boundaryEvent.type).to.equal('bpmn:BoundaryEvent'); - expect(boundaryEvent.businessObject.attachedToRef).to.equal(subProcess.businessObject); - })); - }); + // when + boundaryEvent = modeling.createElements( + boundaryEvent, { x: 100, y: 60 }, task, { attach: true } + )[0]; + // then + expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent'); + expect(boundaryEventBo.attachedToRef).to.equal(taskBo); + })); - describe('event definition', function() { - it('should leave event definitions empty if not present', - inject(function(canvas, elementRegistry, modeling) { + it('should copy properties', inject( + function(bpmnFactory, elementFactory, elementRegistry, modeling) { - // given - var boundaryEvent = elementRegistry.get('BoundaryEvent'), - root = canvas.getRootElement(), - eventDefinitions = boundaryEvent.businessObject.eventDefinitions, - intermediateEvent, bo; + // given + var process = elementRegistry.get('Process_1'); + + var boundaryEventBo = bpmnFactory.create('bpmn:BoundaryEvent', { + name: 'foo' + }); + + var documentation = bpmnFactory.create('bpmn:Documentation', { + text: 'bar' + }); + + boundaryEventBo.documentation = [ documentation ]; - var elements = [ boundaryEvent ]; + documentation.$parent = boundaryEventBo; + + var boundaryEvent = elementFactory.createShape({ + type: 'bpmn:BoundaryEvent', + businessObject: boundaryEventBo + }); + + // when + var intermediateThrowEvent = modeling.createElements( + boundaryEvent, { x: 200, y: 100 }, process + )[0]; + + // then + var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); + + expect(intermediateThrowEventBo.name).to.equal('foo'); + expect(intermediateThrowEventBo.documentation).to.have.lengthOf(1); + expect(intermediateThrowEventBo.documentation[0].text).to.equal('bar'); + } + )); + + }); + + + describe('move', function() { + + it('should replace', inject(function(elementRegistry, modeling) { + + // given + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); // when - modeling.moveElements(elements, { x: 0, y: 90 }, root); + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); // then - intermediateEvent = elementRegistry.get('BoundaryEvent'); - bo = intermediateEvent.businessObject; + var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'), + intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); - expect(intermediateEvent.type).to.equal('bpmn:IntermediateThrowEvent'); - expect(bo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); - }) - ); + expect(intermediateThrowEvent).to.exist; + expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + expect(intermediateThrowEventBo.attachedToRef).not.to.exist; + })); - it('should copy event definitions', inject(function(canvas, elementRegistry, modeling) { + it('should NOT replace', inject(function(elementRegistry, modeling) { - // given - var detachableEvents = [ - 'BoundaryMessageEvent', - 'BoundaryTimerEvent', - 'BoundarySignalEvent', - 'BoundaryConditionalEvent' - ]; + // given + var task = elementRegistry.get('Task_1'), + taskBo = getBusinessObject(task), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); + + // when + modeling.moveElements([ boundaryEvent ], { x: 0, y: -80 }, task, { attach: true }); + + // then + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); + + var boundaryEventBo = getBusinessObject(boundaryEvent); - detachableEvents.forEach(function(eventId) { + expect(boundaryEventBo.$type).to.equal('bpmn:BoundaryEvent'); + expect(boundaryEventBo.attachedToRef).to.equal(taskBo); + })); - var boundaryEvent = elementRegistry.get(eventId), - root = canvas.getRootElement(), - eventDefinitions = boundaryEvent.businessObject.eventDefinitions, - intermediateEvent, bo; - var elements = [ boundaryEvent ]; + it('should replace multiple', inject(function(canvas, elementRegistry, modeling) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + boundaryConditionalEvent = elementRegistry.get('BoundaryConditionalEvent'), + root = canvas.getRootElement(); // when - modeling.moveElements(elements, { x: 0, y: 90 }, root); + modeling.moveElements([ boundaryEvent, boundaryConditionalEvent ], { x: 0, y: 200 }, root); // then - intermediateEvent = elementRegistry.get(eventId); - bo = intermediateEvent.businessObject; + var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'), + intermediateCatchEvent = elementRegistry.get('BoundaryConditionalEvent'), + intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), + intermediateCatchEventBo = getBusinessObject(intermediateCatchEvent); + + expect(intermediateCatchEventBo.$type).to.equal('bpmn:IntermediateCatchEvent'); + expect(intermediateCatchEventBo.attachedToRef).not.to.exist; + + expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + expect(intermediateThrowEventBo.attachedToRef).not.to.exist; + })); + + + describe('properties', function() { + + it('should copy properties', inject(function(elementRegistry, modeling) { + + // given + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); + + // when + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); + + // then + var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'), + intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); + + expect(intermediateThrowEventBo.name).to.equal('foo'); + expect(intermediateThrowEventBo.documentation).to.have.lengthOf(1); + expect(intermediateThrowEventBo.documentation[0].text).to.equal('bar'); + })); + + + describe('event definitions', function() { + + var ids = [ + 'BoundaryConditionalEvent', + 'BoundaryMessageEvent', + 'BoundarySignalEvent', + 'BoundaryTimerEvent' + ]; + + ids.forEach(function(id) { + + it('should copy event definition', inject(function(elementRegistry, modeling) { + + // given + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get(id), + boundaryEventBo = getBusinessObject(boundaryEvent), + eventDefinitions = boundaryEventBo.eventDefinitions; + + // when + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); + + // then + var intermediateCatchEvent = elementRegistry.get(id), + intermediateCatchEventBo = getBusinessObject(intermediateCatchEvent); + + expect(intermediateCatchEventBo.$type).to.equal('bpmn:IntermediateCatchEvent'); + expect(intermediateCatchEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); + })); + + }); + + + it('should NOT create event definition', inject(function(elementRegistry, modeling) { + + // given + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + boundaryEventBo = getBusinessObject(boundaryEvent), + eventDefinitions = boundaryEventBo.eventDefinitions; + + // when + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); + + // then + var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'), + intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent); + + expect(intermediateThrowEventBo.$type).to.equal('bpmn:IntermediateThrowEvent'); + expect(intermediateThrowEventBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); + })); + + }); - expect(intermediateEvent.type).to.equal('bpmn:IntermediateCatchEvent'); - expect(bo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId); }); - })); - }); + }); - describe('connections', function() { + }); - var eventId = 'BoundaryEventWithConnections'; - it('should keep outgoing connection', inject(function(canvas, elementRegistry, modeling) { + describe('connections', function() { - var event = elementRegistry.get(eventId), - root = canvas.getRootElement(), - task = elementRegistry.get('Task_1'), - intermediateEvent; + it('should NOT remove outgoing connection', inject(function(elementRegistry, modeling) { - var elements = [ event ]; + // given + var process = elementRegistry.get('Process_1'), + endEvent = elementRegistry.get('EndEvent_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); // when - modeling.moveElements(elements, { x: 0, y: 100 }, root); + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); // then - intermediateEvent = elementRegistry.get(eventId); + var intermediateThrowEvent = elementRegistry.get('BoundaryEvent_1'); - expect(intermediateEvent.outgoing).to.have.lengthOf(1); - expect(task.incoming).to.have.lengthOf(1); + expect(intermediateThrowEvent.outgoing).to.have.lengthOf(1); + expect(endEvent.incoming).to.have.lengthOf(1); })); - it('should lay out connection once', - inject(function(eventBus, canvas, elementRegistry, modeling) { + it('should lay out connection once', inject(function(eventBus, elementRegistry, modeling) { - // given - var layoutSpy = sinon.spy(), - event = elementRegistry.get(eventId), - root = canvas.getRootElement(); + // given + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'); - eventBus.on('commandStack.connection.layout.execute', layoutSpy); + var layoutSpy = sinon.spy(); - var elements = [ event ]; + eventBus.on('commandStack.connection.layout.execute', layoutSpy); - // when - modeling.moveElements(elements, { x: 0, y: 100 }, root); + // when + modeling.moveElements([ boundaryEvent ], { x: 0, y: 100 }, process); + + // then + expect(layoutSpy).to.be.calledOnce; + })); - // then - expect(layoutSpy).to.be.calledOnce; - }) - ); }); describe('labels', function() { - var eventId = 'BoundaryEventWithLabel'; - - it('should ignore label movement', inject(function(canvas, elementRegistry, modeling) { - - var event = elementRegistry.get(eventId), - root = canvas.getRootElement(), - initialElements = elementRegistry.getAll().slice(); + it('should NOT replace', inject(function(elementRegistry, modeling) { - var elements = [ event.label ]; + var process = elementRegistry.get('Process_1'), + boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + label = boundaryEvent.label; // when - modeling.moveElements(elements, { x: 0, y: 300 }, root); + modeling.moveElements([ label ], { x: 0, y: 100 }, process); // then - expect(elementRegistry.getAll()).to.eql(initialElements); + expect(elementRegistry.get('BoundaryEvent_1')).to.equal(boundaryEvent); })); + }); }); -// helper ////// +// helpers ////////// function skipId(key, value) { - if (key === 'id') { return; } diff --git a/test/spec/features/rules/BpmnRulesSpec.js b/test/spec/features/rules/BpmnRulesSpec.js index 40cb6473ed..55941133fa 100644 --- a/test/spec/features/rules/BpmnRulesSpec.js +++ b/test/spec/features/rules/BpmnRulesSpec.js @@ -183,7 +183,7 @@ describe('features/modeling/rules - BpmnRules', function() { boundaryEvent = elementFactory.createShape({ type: 'bpmn:BoundaryEvent', host: task }); // then - expectCanCopy(boundaryEvent, [], false); + expectCanCopy(boundaryEvent, [ boundaryEvent ], true); })); }); diff --git a/test/spec/features/snapping/BpmnCreateMoveSnappingSpec.js b/test/spec/features/snapping/BpmnCreateMoveSnappingSpec.js index fae59f1733..fe8bcf027d 100644 --- a/test/spec/features/snapping/BpmnCreateMoveSnappingSpec.js +++ b/test/spec/features/snapping/BpmnCreateMoveSnappingSpec.js @@ -20,6 +20,11 @@ import { import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents'; +import { + DEFAULT_LABEL_SIZE, + getExternalLabelMid +} from 'lib/util/LabelUtil'; + import { queryAll as domQueryAll } from 'min-dom'; import { attr as svgAttr } from 'tiny-svg'; @@ -155,86 +160,201 @@ describe('features/snapping - BpmnCreateMoveSnapping', function() { 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' - }); + describe('without label', function() { - create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent); + beforeEach(inject(function(create, dragging, elementRegistry, elementFactory) { + task = elementRegistry.get('Task_1'); - dragging.hover({ element: task, gfx: taskGfx }); - })); + taskGfx = elementRegistry.getGraphics(task); + intermediateThrowEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateThrowEvent' + }); - it('should snap to top', inject(function(dragging) { + create.start(canvasEvent({ x: 0, y: 0 }), intermediateThrowEvent); - // when - dragging.move(canvasEvent({ x: 150, y: 95 })); + dragging.hover({ element: task, gfx: taskGfx }); + })); - dragging.end(); - // then - var boundaryEvent = getBoundaryEvent(task); + it('should snap to top', inject(function(dragging) { - expect(mid(boundaryEvent)).to.eql({ - x: 150, - y: 100 // 95 snapped to 100 - }); - })); + // when + dragging.move(canvasEvent({ x: 150, y: 95 })); + dragging.end(); - it('should snap to right', inject(function(dragging) { + // then + var boundaryEvent = getBoundaryEvent(task); - // when - dragging.move(canvasEvent({ x: 195, y: 140 })); + expect(mid(boundaryEvent)).to.eql({ + x: 150, + y: 100 // 95 snapped to 100 + }); + })); - dragging.end(); - // then - var boundaryEvent = getBoundaryEvent(task); + it('should snap to right', inject(function(dragging) { - expect(mid(boundaryEvent)).to.eql({ - x: 200, // 195 snapped to 200 - y: 140 - }); - })); + // when + dragging.move(canvasEvent({ x: 195, y: 140 })); + dragging.end(); - it('should snap to bottom', inject(function(dragging) { + // then + var boundaryEvent = getBoundaryEvent(task); - // when - dragging.move(canvasEvent({ x: 150, y: 175 })); + expect(mid(boundaryEvent)).to.eql({ + x: 200, // 195 snapped to 200 + y: 140 + }); + })); - dragging.end(); - // then - var boundaryEvent = getBoundaryEvent(task); + it('should snap to bottom', inject(function(dragging) { - expect(mid(boundaryEvent)).to.eql({ - x: 150, - y: 180 // 175 snapped to 180 - }); - })); + // when + dragging.move(canvasEvent({ x: 150, y: 175 })); + dragging.end(); - it('should snap to left', inject(function(dragging) { + // then + var boundaryEvent = getBoundaryEvent(task); - // when - dragging.move(canvasEvent({ x: 95, y: 140 })); + expect(mid(boundaryEvent)).to.eql({ + x: 150, + y: 180 // 175 snapped to 180 + }); + })); - dragging.end(); - // then - var boundaryEvent = getBoundaryEvent(task); + it('should snap to left', inject(function(dragging) { - expect(mid(boundaryEvent)).to.eql({ - x: 100, // 95 snapped to 100 - y: 140 - }); - })); + // 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('with label', function() { + + beforeEach(inject(function( + bpmnFactory, + create, + dragging, + elementFactory, + elementRegistry, + textRenderer + ) { + task = elementRegistry.get('Task_1'); + + taskGfx = elementRegistry.getGraphics(task); + + intermediateThrowEvent = elementFactory.createShape({ + businessObject: bpmnFactory.create('bpmn:IntermediateThrowEvent', { + name: 'Foo' + }), + type: 'bpmn:IntermediateThrowEvent', + x: 0, + y: 0 + }); + + var externalLabelMid = getExternalLabelMid(intermediateThrowEvent); + + var externalLabelBounds = textRenderer.getExternalLabelBounds(DEFAULT_LABEL_SIZE, 'Foo'); + + var label = elementFactory.createLabel({ + labelTarget: intermediateThrowEvent, + x: externalLabelMid.x - externalLabelBounds.width / 2, + y: externalLabelMid.y - externalLabelBounds.height / 2, + width: externalLabelBounds.width, + height: externalLabelBounds.height + }); + + create.start(canvasEvent({ x: 0, y: 0 }), [ intermediateThrowEvent, label ]); + + dragging.hover({ element: task, gfx: taskGfx }); + })); + + + it('should snap to top-left', inject(function(dragging) { + + // when + dragging.move(canvasEvent({ x: 90, y: 95 })); + + dragging.end(); + + // then + var boundaryEvent = getBoundaryEvent(task); + + expect(mid(boundaryEvent)).to.eql({ + x: 100, // 90 snapped to 100 + y: 100 // 95 snapped to 100 + }); + })); + + + it('should snap to top-right', inject(function(dragging) { + + // when + dragging.move(canvasEvent({ x: 210, y: 95 })); + + dragging.end(); + + // then + var boundaryEvent = getBoundaryEvent(task); + + expect(mid(boundaryEvent)).to.eql({ + x: 200, // 210 snapped to 200 + y: 100 // 95 snapped to 100 + }); + })); + + + it('should snap to bottom-left', inject(function(dragging) { + + // when + dragging.move(canvasEvent({ x: 90, y: 190 })); + + dragging.end(); + + // then + var boundaryEvent = getBoundaryEvent(task); + + expect(mid(boundaryEvent)).to.eql({ + x: 100, // 90 snapped to 100 + y: 180 // 190 snapped to 180 + }); + })); + + + it('should snap to bottom-right', inject(function(dragging) { + + // when + dragging.move(canvasEvent({ x: 210, y: 190 })); + + dragging.end(); + + // then + var boundaryEvent = getBoundaryEvent(task); + + expect(mid(boundaryEvent)).to.eql({ + x: 200, // 210 snapped to 200 + y: 180 // 190 snapped to 180 + }); + })); + }); }); @@ -562,4 +682,4 @@ function canvasEventTopLeft(position, shape) { function getBoundaryEvent(element) { return element.attachers[0]; -} \ No newline at end of file +}