diff --git a/lib/features/modeling/ElementFactory.js b/lib/features/modeling/ElementFactory.js index 2c4168bf3b..8aa486d30d 100644 --- a/lib/features/modeling/ElementFactory.js +++ b/lib/features/modeling/ElementFactory.js @@ -207,6 +207,10 @@ ElementFactory.prototype.createElement = function(elementType, attrs) { attrs = applyAttribute(di, attrs, 'isExpanded'); } + if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ])) { + attrs = applyAttribute(di, attrs, 'isHorizontal'); + } + if (is(businessObject, 'bpmn:SubProcess')) { attrs.collapsed = !isExpanded(businessObject, di); } diff --git a/lib/features/modeling/cmd/AddLaneHandler.js b/lib/features/modeling/cmd/AddLaneHandler.js index 24bc4a45d1..a9ae5aac26 100644 --- a/lib/features/modeling/cmd/AddLaneHandler.js +++ b/lib/features/modeling/cmd/AddLaneHandler.js @@ -12,6 +12,10 @@ import { LANE_INDENTATION } from '../util/LaneUtil'; +import { + isHorizontal +} from '../../../util/DiUtil'; + /** * @typedef {import('diagram-js/lib/command/CommandHandler').default} CommandHandler * @@ -54,14 +58,47 @@ AddLaneHandler.prototype.preExecute = function(context) { var existingChildLanes = getChildLanes(laneParent); + var isHorizontalLane = isHorizontal(shape); + + // never mix up horizontal/vertical lanes + if (isHorizontalLane) { + if (location === 'left') { + location = 'top'; + } else + if (location === 'right') { + location = 'bottom'; + } + } else { + if (location === 'top') { + location = 'left'; + } else + if (location === 'bottom') { + location = 'right'; + } + } + // (0) add a lane if we currently got none and are adding to root if (!existingChildLanes.length) { - modeling.createShape({ type: 'bpmn:Lane' }, { + var siblingPosition = isHorizontalLane ? { x: shape.x + LANE_INDENTATION, y: shape.y, width: shape.width - LANE_INDENTATION, height: shape.height - }, laneParent); + } : { + x: shape.x, + y: shape.y + LANE_INDENTATION, + width: shape.width, + height: shape.height - LANE_INDENTATION + }; + + modeling.createShape( + { + type: 'bpmn:Lane', + isHorizontal: isHorizontalLane + }, + siblingPosition, + laneParent + ); } // (1) collect affected elements to create necessary space @@ -84,26 +121,72 @@ AddLaneHandler.prototype.preExecute = function(context) { }); }); - var offset = location === 'top' ? -120 : 120, - lanePosition = location === 'top' ? shape.y : shape.y + shape.height, - spacePos = lanePosition + (location === 'top' ? 10 : -10), - direction = location === 'top' ? 'n' : 's'; + var offset, + lanePosition, + spacePos, + direction, + axis; + + if (location === 'top') { + offset = -120; + lanePosition = shape.y; + spacePos = lanePosition + 10; + direction = 'n'; + axis = 'y'; + } else + if (location === 'left') { + offset = -120; + lanePosition = shape.x; + spacePos = lanePosition + 10; + direction = 'w'; + axis = 'x'; + } else + if (location === 'bottom') { + offset = 120; + lanePosition = shape.y + shape.height; + spacePos = lanePosition - 10; + direction = 's'; + axis = 'y'; + } else + if (location === 'right') { + offset = 120; + lanePosition = shape.x + shape.width; + spacePos = lanePosition - 10; + direction = 'e'; + axis = 'x'; + } - var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos); + var adjustments = spaceTool.calculateAdjustments(allAffected, axis, offset, spacePos); + + var delta = isHorizontalLane ? { x: 0, y: offset } : { x: offset, y: 0 }; spaceTool.makeSpace( adjustments.movingShapes, adjustments.resizingShapes, - { x: 0, y: offset }, + delta, direction, spacePos ); // (2) create new lane at open space - context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, { + var newLanePosition = isHorizontalLane ? { x: shape.x + (isRoot ? LANE_INDENTATION : 0), y: lanePosition - (location === 'top' ? 120 : 0), width: shape.width - (isRoot ? LANE_INDENTATION : 0), height: 120 - }, laneParent); + } : { + x: lanePosition - (location === 'left' ? 120 : 0), + y: shape.y + (isRoot ? LANE_INDENTATION : 0), + width: 120, + height: shape.height - (isRoot ? LANE_INDENTATION : 0) + }; + + context.newLane = modeling.createShape( + { + type: 'bpmn:Lane', + isHorizontal: isHorizontalLane + }, + newLanePosition, + laneParent + ); }; diff --git a/lib/model/Types.ts b/lib/model/Types.ts index b220c42104..7670d4fc84 100644 --- a/lib/model/Types.ts +++ b/lib/model/Types.ts @@ -17,6 +17,7 @@ export type BpmnAttributes = { cancelActivity: boolean; eventDefinitionType: string; isExpanded: boolean; + isHorizontal: boolean; isForCompensation: boolean; isInterrupting: boolean; processRef: ModdleElement; diff --git a/test/fixtures/bpmn/collaboration-vertical.bpmn b/test/fixtures/bpmn/collaboration-vertical.bpmn new file mode 100644 index 0000000000..f83dbdd16e --- /dev/null +++ b/test/fixtures/bpmn/collaboration-vertical.bpmn @@ -0,0 +1,101 @@ + + + + + + + + + + + Flow_0udpwgg + + + Flow_0udpwgg + Flow_1ds0g2d + + + + + Flow_1ds0g2d + + + + + + + Event_04gz91y + Activity_1yln18q + Event_0wiu8mt + + + + Flow_1wsapma + + + Flow_1wsapma + Flow_0jjji9q + + + + Flow_0jjji9q + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/ModelerSpec.js b/test/spec/ModelerSpec.js index 2cc32dd555..6e66d48c22 100644 --- a/test/spec/ModelerSpec.js +++ b/test/spec/ModelerSpec.js @@ -90,6 +90,15 @@ describe('Modeler', function() { }); + it('should import vertical collaboration', function() { + var xml = require('../fixtures/bpmn/collaboration-vertical.bpmn'); + return createModeler(xml).then(function(result) { + + expect(result.error).not.to.exist; + }); + }); + + it('should import ioSpecification', function() { var xml = require('./features/modeling/input-output/DataInputOutput.bpmn'); return createModeler(xml).then(function(result) { diff --git a/test/spec/features/modeling/ElementFactorySpec.js b/test/spec/features/modeling/ElementFactorySpec.js index 3f56baaf3d..0c910fb553 100644 --- a/test/spec/features/modeling/ElementFactorySpec.js +++ b/test/spec/features/modeling/ElementFactorySpec.js @@ -1,287 +1,337 @@ -import { - bootstrapModeler, - inject -} from 'test/TestHelper'; - -import coreModule from 'lib/core'; -import modelingModule from 'lib/features/modeling'; - -import { - getBusinessObject, - is -} from '../../../../lib/util/ModelUtil'; - -import { - assign -} from 'min-dash'; - - -describe('features - element factory', function() { - - var diagramXML = require('./ElementFactory.bpmn'); - - var testModules = [ modelingModule, coreModule ]; - - beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); - - - describe('basics', function() { - - it('should not mutate attrs', inject(function(elementFactory) { - - // given - var attrs = { - type: 'bpmn:SubProcess', - isExpanded: false - }; - - // when - var createAttrs = assign({}, attrs); - - elementFactory.createShape(createAttrs); - - // then - expect(createAttrs).to.eql(attrs); - })); - - - it('should not mutate attr', inject(function(elementFactory) { - - // given - var attrs = { - type: 'bpmn:SubProcess', - isExpanded: false, - di: { - 'bioc:stroke': 'red' - } - }; - - // when - var createAttrs = assign({}, attrs); - - elementFactory.createShape(createAttrs); - - // then - expect(createAttrs).to.eql(attrs); - })); - - }); - - - describe('create', function() { - - it('should create with message event definition', inject(function(elementFactory) { - - // when - var intermediateThrowEvent = elementFactory.createShape({ - type: 'bpmn:IntermediateThrowEvent', - eventDefinitionType: 'bpmn:MessageEventDefinition' - }); - - // then - expect(intermediateThrowEvent).to.exist; - expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true; - - var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), - eventDefinitions = intermediateThrowEventBo.eventDefinitions; - - expect(eventDefinitions).to.exist; - expect(eventDefinitions).to.have.length(1); - - var messageEventDefinition = eventDefinitions[ 0 ]; - - expect(is(messageEventDefinition, 'bpmn:MessageEventDefinition')).to.be.true; - })); - - - it('should create event with conditional event definition', inject(function(elementFactory) { - - // when - var intermediateCatchEvent = elementFactory.createShape({ - type: 'bpmn:IntermediateCatchEvent', - eventDefinitionType: 'bpmn:ConditionalEventDefinition' - }); - - // then - expect(intermediateCatchEvent).to.exist; - expect(is(intermediateCatchEvent, 'bpmn:IntermediateCatchEvent')).to.be.true; - - var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent), - eventDefinitions = intermediateThrowEventBo.eventDefinitions; - - expect(eventDefinitions).to.exist; - expect(eventDefinitions).to.have.length(1); - - var conditionalEventDefinition = eventDefinitions[ 0 ]; - - expect(is(conditionalEventDefinition, 'bpmn:ConditionalEventDefinition')).to.be.true; - expect(conditionalEventDefinition.condition).to.exist; - expect(is(conditionalEventDefinition.condition, 'bpmn:FormalExpression')).to.be.true; - })); - - - it('should create with link event definition', inject(function(elementFactory) { - - // when - var intermediateThrowEvent = elementFactory.createShape({ - type: 'bpmn:IntermediateThrowEvent', - eventDefinitionType: 'bpmn:LinkEventDefinition', - eventDefinitionAttrs: { - name: '' - } - }); - - // then - expect(intermediateThrowEvent).to.exist; - expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true; - - var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), - eventDefinitions = intermediateThrowEventBo.eventDefinitions; - - expect(eventDefinitions).to.exist; - expect(eventDefinitions).to.have.length(1); - - var eventDefinition = eventDefinitions[ 0 ]; - - expect(is(eventDefinition, 'bpmn:LinkEventDefinition')).to.be.true; - expect(eventDefinition.name).to.eql(''); - })); - - - it('should error when accessing via businessObject', inject(function(elementFactory) { - - // given - var shape = elementFactory.createShape({ - type: 'bpmn:Task', - }); - - // then - expect(shape.di).to.exist; - expect(function() { - shape.businessObject.di; - }).to.throw(/The di is available through the diagram element only./); - })); - - - it('should add collapsed attribute to subprocess', inject(function(elementFactory) { - - // when - var subprocess = elementFactory.createShape({ - type: 'bpmn:SubProcess', - isExpanded: false - }); - - // then - expect(subprocess.collapsed).to.be.true; - })); - - - it('should create subprocess as event subprocess', inject(function(elementFactory) { - - // when - var subprocess = elementFactory.createShape({ - type: 'bpmn:SubProcess', - triggeredByEvent: true - }); - - var businessObject = getBusinessObject(subprocess); - - // then - expect(businessObject.triggeredByEvent).to.be.true; - })); - - - it('should create boundary event as non-interrupting', inject(function(elementFactory) { - - // when - var event = elementFactory.createShape({ - type: 'bpmn:BoundaryEvent', - eventDefinitionType: 'bpmn:MessageEventDefinition', - cancelActivity: false - }); - - var businessObject = getBusinessObject(event); - - // then - expect(businessObject.cancelActivity).to.be.false; - })); - - - it('should create exclusive gateway with x marker', inject(function(elementFactory) { - - // when - var shape = elementFactory.createShape({ - type: 'bpmn:ExclusiveGateway', - isMarkerVisible: true - }); - - // then - expect(shape.di.isMarkerVisible).to.be.true; - })); - - - it('should create exclusive gateway without x marker', inject(function(elementFactory) { - - // when - var shape = elementFactory.createShape({ - type: 'bpmn:ExclusiveGateway', - isMarkerVisible: false - }); - - // then - expect(shape.di.isMarkerVisible).to.be.false; - })); - - - it('should create exclusive gateway with x marker by default', inject(function(elementFactory) { - - // when - var shape = elementFactory.createShape({ - type: 'bpmn:ExclusiveGateway' - }); - - // then - expect(shape.di.isMarkerVisible).to.be.true; - })); - - - describe('integration', function() { - - it('should create event definition with ID', inject(function(elementFactory) { - - // when - var intermediateThrowEvent = elementFactory.createShape({ - type: 'bpmn:IntermediateThrowEvent', - eventDefinitionType: 'bpmn:MessageEventDefinition' - }); - - // then - var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), - eventDefinitions = intermediateThrowEventBo.eventDefinitions, - messageEventDefinition = eventDefinitions[ 0 ]; - - expect(messageEventDefinition.id).to.exist; - })); - - - it('should NOT create formal expression with ID', inject(function(elementFactory) { - - // when - var intermediateCatchEvent = elementFactory.createShape({ - type: 'bpmn:IntermediateCatchEvent', - eventDefinitionType: 'bpmn:ConditionalEventDefinition' - }); - - // then - var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent), - eventDefinitions = intermediateThrowEventBo.eventDefinitions, - conditionalEventDefinition = eventDefinitions[ 0 ]; - - expect(conditionalEventDefinition.condition.id).not.to.exist; - })); - - }); - - }); - -}); +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; + +import { + getBusinessObject, + is +} from '../../../../lib/util/ModelUtil'; + +import { + assign +} from 'min-dash'; + + +describe('features - element factory', function() { + + var diagramXML = require('./ElementFactory.bpmn'); + + var testModules = [ modelingModule, coreModule ]; + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + + describe('basics', function() { + + it('should not mutate attrs', inject(function(elementFactory) { + + // given + var attrs = { + type: 'bpmn:SubProcess', + isExpanded: false + }; + + // when + var createAttrs = assign({}, attrs); + + elementFactory.createShape(createAttrs); + + // then + expect(createAttrs).to.eql(attrs); + })); + + + it('should not mutate attr', inject(function(elementFactory) { + + // given + var attrs = { + type: 'bpmn:SubProcess', + isExpanded: false, + di: { + 'bioc:stroke': 'red' + } + }; + + // when + var createAttrs = assign({}, attrs); + + elementFactory.createShape(createAttrs); + + // then + expect(createAttrs).to.eql(attrs); + })); + + }); + + + describe('create', function() { + + it('should create with message event definition', inject(function(elementFactory) { + + // when + var intermediateThrowEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateThrowEvent', + eventDefinitionType: 'bpmn:MessageEventDefinition' + }); + + // then + expect(intermediateThrowEvent).to.exist; + expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true; + + var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), + eventDefinitions = intermediateThrowEventBo.eventDefinitions; + + expect(eventDefinitions).to.exist; + expect(eventDefinitions).to.have.length(1); + + var messageEventDefinition = eventDefinitions[ 0 ]; + + expect(is(messageEventDefinition, 'bpmn:MessageEventDefinition')).to.be.true; + })); + + + it('should create event with conditional event definition', inject(function(elementFactory) { + + // when + var intermediateCatchEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateCatchEvent', + eventDefinitionType: 'bpmn:ConditionalEventDefinition' + }); + + // then + expect(intermediateCatchEvent).to.exist; + expect(is(intermediateCatchEvent, 'bpmn:IntermediateCatchEvent')).to.be.true; + + var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent), + eventDefinitions = intermediateThrowEventBo.eventDefinitions; + + expect(eventDefinitions).to.exist; + expect(eventDefinitions).to.have.length(1); + + var conditionalEventDefinition = eventDefinitions[ 0 ]; + + expect(is(conditionalEventDefinition, 'bpmn:ConditionalEventDefinition')).to.be.true; + expect(conditionalEventDefinition.condition).to.exist; + expect(is(conditionalEventDefinition.condition, 'bpmn:FormalExpression')).to.be.true; + })); + + + it('should create with link event definition', inject(function(elementFactory) { + + // when + var intermediateThrowEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateThrowEvent', + eventDefinitionType: 'bpmn:LinkEventDefinition', + eventDefinitionAttrs: { + name: '' + } + }); + + // then + expect(intermediateThrowEvent).to.exist; + expect(is(intermediateThrowEvent, 'bpmn:IntermediateThrowEvent')).to.be.true; + + var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), + eventDefinitions = intermediateThrowEventBo.eventDefinitions; + + expect(eventDefinitions).to.exist; + expect(eventDefinitions).to.have.length(1); + + var eventDefinition = eventDefinitions[ 0 ]; + + expect(is(eventDefinition, 'bpmn:LinkEventDefinition')).to.be.true; + expect(eventDefinition.name).to.eql(''); + })); + + + it('should error when accessing via businessObject', inject(function(elementFactory) { + + // given + var shape = elementFactory.createShape({ + type: 'bpmn:Task', + }); + + // then + expect(shape.di).to.exist; + expect(function() { + shape.businessObject.di; + }).to.throw(/The di is available through the diagram element only./); + })); + + + it('should add collapsed attribute to subprocess', inject(function(elementFactory) { + + // when + var subprocess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // then + expect(subprocess.collapsed).to.be.true; + })); + + + it('should create subprocess as event subprocess', inject(function(elementFactory) { + + // when + var subprocess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + triggeredByEvent: true + }); + + var businessObject = getBusinessObject(subprocess); + + // then + expect(businessObject.triggeredByEvent).to.be.true; + })); + + + it('should create boundary event as non-interrupting', inject(function(elementFactory) { + + // when + var event = elementFactory.createShape({ + type: 'bpmn:BoundaryEvent', + eventDefinitionType: 'bpmn:MessageEventDefinition', + cancelActivity: false + }); + + var businessObject = getBusinessObject(event); + + // then + expect(businessObject.cancelActivity).to.be.false; + })); + + + it('should create exclusive gateway with x marker', inject(function(elementFactory) { + + // when + var shape = elementFactory.createShape({ + type: 'bpmn:ExclusiveGateway', + isMarkerVisible: true + }); + + // then + expect(shape.di.isMarkerVisible).to.be.true; + })); + + + it('should create exclusive gateway without x marker', inject(function(elementFactory) { + + // when + var shape = elementFactory.createShape({ + type: 'bpmn:ExclusiveGateway', + isMarkerVisible: false + }); + + // then + expect(shape.di.isMarkerVisible).to.be.false; + })); + + + it('should create exclusive gateway with x marker by default', inject(function(elementFactory) { + + // when + var shape = elementFactory.createShape({ + type: 'bpmn:ExclusiveGateway' + }); + + // then + expect(shape.di.isMarkerVisible).to.be.true; + })); + + + it('should create horizontal participant', inject(function(elementFactory) { + + // when + var shape = elementFactory.createParticipantShape({ + isHorizontal: true + }); + + // then + expect(shape.di.isHorizontal).to.be.true; + })); + + + it('should create vertical participant', inject(function(elementFactory) { + + // when + var shape = elementFactory.createParticipantShape({ + isHorizontal: false + }); + + // then + expect(shape.di.isHorizontal).to.be.false; + })); + + + it('should create horizontal lane', inject(function(elementFactory) { + + // when + var shape = elementFactory.createShape({ + type: 'bpmn:Lane', + isHorizontal: true + }); + + // then + expect(shape.di.isHorizontal).to.be.true; + })); + + + it('should create vertical lane', inject(function(elementFactory) { + + // when + var shape = elementFactory.createShape({ + type: 'bpmn:Lane', + isHorizontal: false + }); + + // then + expect(shape.di.isHorizontal).to.be.false; + })); + + + describe('integration', function() { + + it('should create event definition with ID', inject(function(elementFactory) { + + // when + var intermediateThrowEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateThrowEvent', + eventDefinitionType: 'bpmn:MessageEventDefinition' + }); + + // then + var intermediateThrowEventBo = getBusinessObject(intermediateThrowEvent), + eventDefinitions = intermediateThrowEventBo.eventDefinitions, + messageEventDefinition = eventDefinitions[ 0 ]; + + expect(messageEventDefinition.id).to.exist; + })); + + + it('should NOT create formal expression with ID', inject(function(elementFactory) { + + // when + var intermediateCatchEvent = elementFactory.createShape({ + type: 'bpmn:IntermediateCatchEvent', + eventDefinitionType: 'bpmn:ConditionalEventDefinition' + }); + + // then + var intermediateThrowEventBo = getBusinessObject(intermediateCatchEvent), + eventDefinitions = intermediateThrowEventBo.eventDefinitions, + conditionalEventDefinition = eventDefinitions[ 0 ]; + + expect(conditionalEventDefinition.condition.id).not.to.exist; + })); + + }); + + }); + +}); diff --git a/test/spec/features/modeling/lanes/AddLaneSpec.js b/test/spec/features/modeling/lanes/AddLaneSpec.js index 033a73c158..d57c4c99b6 100644 --- a/test/spec/features/modeling/lanes/AddLaneSpec.js +++ b/test/spec/features/modeling/lanes/AddLaneSpec.js @@ -19,7 +19,8 @@ import { import { query as domQuery } from 'min-dom'; -var DEFAULT_LANE_HEIGHT = 120; +var DEFAULT_LANE_HEIGHT = 120, + DEFAULT_VERTICAL_LANE_WIDTH = 120; var testModules = [ coreModule, modelingModule ]; @@ -65,6 +66,9 @@ describe('features/modeling - add Lane', function() { height: belowLaneShape.height }); + expect(laneShape.di.isHorizontal).to.be.true; + expect(belowLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; })); @@ -92,6 +96,42 @@ describe('features/modeling - add Lane', function() { width: laneShape.width, height: aboveLaneShape.height }); + + expect(laneShape.di.isHorizontal).to.be.true; + expect(aboveLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; + })); + + + it('should add horizontal Lane after', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Lane_A'), + belowLaneShape = elementRegistry.get('Lane_B'); + + // when + var newLane = modeling.addLane(laneShape, 'right'); + + // then + expect(laneShape.di.isHorizontal).to.be.true; + expect(belowLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; + })); + + + it('should add horizontal Lane before', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Lane_B'), + aboveLaneShape = elementRegistry.get('Lane_A'); + + // when + var newLane = modeling.addLane(laneShape, 'left'); + + // then + expect(laneShape.di.isHorizontal).to.be.true; + expect(aboveLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; })); @@ -120,6 +160,10 @@ describe('features/modeling - add Lane', function() { width: participantBounds.width, height: participantBounds.height + newLane.height }); + + expect(participantShape.di.isHorizontal).to.be.true; + expect(laneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; })); @@ -153,6 +197,9 @@ describe('features/modeling - add Lane', function() { height: participantBounds.height + DEFAULT_LANE_HEIGHT }); + expect(participantShape.di.isHorizontal).to.be.true; + expect(lastLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; })); @@ -175,7 +222,7 @@ describe('features/modeling - add Lane', function() { height: DEFAULT_LANE_HEIGHT }); - // last lane kept position + // first lane kept position expect(firstLaneShape).to.have.bounds(firstLaneBounds); // participant got enlarged by { dy: + LANE_HEIGHT } at bottom @@ -186,6 +233,309 @@ describe('features/modeling - add Lane', function() { height: participantBounds.height + DEFAULT_LANE_HEIGHT }); + expect(participantShape.di.isHorizontal).to.be.true; + expect(firstLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; + })); + + }); + + + describe('nested vertical Lanes', function() { + + var diagramXML = require('./lanes.vertical.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + it('should add after Lane', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_A'), + rightLaneShape = elementRegistry.get('Vertical_Lane_B'); + + // when + var newLane = modeling.addLane(laneShape, 'right'); + + // then + expect(newLane).to.have.bounds({ + x: laneShape.x + laneShape.width, + y: laneShape.y, + height: laneShape.height, + width: DEFAULT_VERTICAL_LANE_WIDTH + }); + + // right lanes got moved by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } + expect(rightLaneShape).to.have.bounds({ + x: laneShape.x + laneShape.width + DEFAULT_VERTICAL_LANE_WIDTH, + y: laneShape.y, + width: rightLaneShape.width, + height: laneShape.height + }); + + expect(laneShape.di.isHorizontal).to.be.false; + expect(rightLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + + })); + + + it('should add before Lane', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_B'), + leftLaneShape = elementRegistry.get('Vertical_Lane_A'); + + // when + var newLane = modeling.addLane(laneShape, 'left'); + + // then + expect(newLane).to.have.bounds({ + x: laneShape.x - DEFAULT_VERTICAL_LANE_WIDTH, + y: laneShape.y, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: laneShape.height + }); + + // right lanes got moved by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } + expect(leftLaneShape).to.have.bounds({ + x: laneShape.x - leftLaneShape.width - DEFAULT_VERTICAL_LANE_WIDTH, + y: laneShape.y, + width: leftLaneShape.width, + height: laneShape.height + }); + + expect(laneShape.di.isHorizontal).to.be.false; + expect(leftLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + })); + + + it('should add vertical Lane after', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_A'), + rightLaneShape = elementRegistry.get('Vertical_Lane_B'); + + // when + var newLane = modeling.addLane(laneShape, 'bottom'); + + // then + expect(laneShape.di.isHorizontal).to.be.false; + expect(rightLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + + })); + + + it('should add vertical Lane before', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_B'), + leftLaneShape = elementRegistry.get('Vertical_Lane_A'); + + // when + var newLane = modeling.addLane(laneShape, 'top'); + + // then + expect(laneShape.di.isHorizontal).to.be.false; + expect(leftLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + })); + + + it('should add before nested Lane', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Nested_Vertical_Lane_A'), + participantShape = elementRegistry.get('Vertical_Participant_Lane'), + participantBounds = getBounds(participantShape); + + // when + var newLane = modeling.addLane(laneShape, 'left'); + + // then + expect(newLane).to.have.bounds({ + x: laneShape.x - DEFAULT_VERTICAL_LANE_WIDTH, + y: laneShape.y, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: laneShape.height + }); + + // participant got enlarged { left: + DEFAULT_VERTICAL_LANE_WIDTH } + expect(participantShape).to.have.bounds({ + x: participantBounds.x - newLane.width, + y: participantBounds.y, + width: participantBounds.width + newLane.width, + height: participantBounds.height + }); + + expect(laneShape.di.isHorizontal).to.be.false; + expect(participantShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + })); + + + it('should add after Participant', inject(function(elementRegistry, modeling) { + + // given + var participantShape = elementRegistry.get('Vertical_Participant_Lane'), + participantBounds = getBounds(participantShape), + lastLaneShape = elementRegistry.get('Vertical_Lane_B'), + lastLaneBounds = getBounds(lastLaneShape); + + // when + var newLane = modeling.addLane(participantShape, 'right'); + + // then + expect(newLane).to.have.bounds({ + x: participantBounds.x + participantBounds.width, + y: participantBounds.y + 30, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height - 30 + }); + + // last lane kept position + expect(lastLaneShape).to.have.bounds(lastLaneBounds); + + // participant got enlarged by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } to the right + expect(participantShape).to.have.bounds({ + x: participantBounds.x, + y: participantBounds.y, + width: participantBounds.width + DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height + }); + + expect(participantShape.di.isHorizontal).to.be.false; + expect(lastLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + + })); + + + it('should add before Participant', inject(function(elementRegistry, modeling) { + + // given + var participantShape = elementRegistry.get('Vertical_Participant_Lane'), + participantBounds = getBounds(participantShape), + firstLaneShape = elementRegistry.get('Vertical_Lane_A'), + firstLaneBounds = getBounds(firstLaneShape); + + // when + var newLane = modeling.addLane(participantShape, 'left'); + + // then + expect(newLane).to.have.bounds({ + x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH, + y: participantBounds.y + 30, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height - 30 + }); + + // first lane kept position + expect(firstLaneShape).to.have.bounds(firstLaneBounds); + + // participant got enlarged by { dx: + DEFAULT_VERTICAL_LANE_WIDTH } to the left + expect(participantShape).to.have.bounds({ + x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH, + y: participantBounds.y, + width: participantBounds.width + DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height + }); + + expect(participantShape.di.isHorizontal).to.be.false; + expect(firstLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + + })); + + }); + + + describe('without Participant', function() { + + var diagramXML = require('./lanes.only.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + it('should add horizontal Lane after', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Lane_A'), + belowLaneShape = elementRegistry.get('Lane_B'); + + // when + var newLane = modeling.addLane(laneShape, 'right'); + + // then + expect(laneShape.di.isHorizontal).to.be.true; + expect(belowLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; + })); + + + it('should add horizontal Lane before', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Lane_B'), + aboveLaneShape = elementRegistry.get('Lane_A'); + + // when + var newLane = modeling.addLane(laneShape, 'left'); + + // then + expect(laneShape.di.isHorizontal).to.be.true; + expect(aboveLaneShape.di.isHorizontal).to.be.true; + expect(newLane.di.isHorizontal).to.be.true; + })); + + }); + + + describe('vertical without Participant', function() { + + var diagramXML = require('./lanes.only.vertical.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + it('should add vertical Lane after', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_A'), + rightLaneShape = elementRegistry.get('Vertical_Lane_B'); + + // when + var newLane = modeling.addLane(laneShape, 'bottom'); + + // then + expect(laneShape.di.isHorizontal).to.be.false; + expect(rightLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; + + })); + + + it('should add vertical Lane before', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Vertical_Lane_B'), + leftLaneShape = elementRegistry.get('Vertical_Lane_A'); + + // when + var newLane = modeling.addLane(laneShape, 'top'); + + // then + expect(laneShape.di.isHorizontal).to.be.false; + expect(leftLaneShape.di.isHorizontal).to.be.false; + expect(newLane.di.isHorizontal).to.be.false; })); }); @@ -231,6 +581,9 @@ describe('features/modeling - add Lane', function() { width: participantBounds.width - 30, height: DEFAULT_LANE_HEIGHT }); + + expect(firstLane.di.isHorizontal).to.be.true; + expect(secondLane.di.isHorizontal).to.be.true; })); @@ -266,6 +619,93 @@ describe('features/modeling - add Lane', function() { height: DEFAULT_LANE_HEIGHT }); + expect(firstLane.di.isHorizontal).to.be.true; + expect(secondLane.di.isHorizontal).to.be.true; + })); + + }); + + + describe('vertical Participant without Lane', function() { + + var diagramXML = require('./participant-no-lane-vertical.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + it('should add after Participant', inject(function(elementRegistry, modeling) { + + // given + var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'), + participantBounds = getBounds(participantShape); + + // when + modeling.addLane(participantShape, 'right'); + + var childLanes = getChildLanes(participantShape); + + // then + expect(childLanes.length).to.eql(2); + + var firstLane = childLanes[0], + secondLane = childLanes[1]; + + // new lane was added at participant location + expect(firstLane).to.have.bounds({ + x: participantBounds.x, + y: participantBounds.y + 30, + width: participantBounds.width, + height: participantBounds.height - 30 + }); + + expect(secondLane).to.have.bounds({ + x: participantBounds.x + participantBounds.width, + y: participantBounds.y + 30, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height - 30 + }); + + expect(firstLane.di.isHorizontal).to.be.false; + expect(secondLane.di.isHorizontal).to.be.false; + })); + + + it('should add before Participant', inject(function(elementRegistry, modeling) { + + // given + var participantShape = elementRegistry.get('Vertical_Participant_No_Lane'), + participantBounds = getBounds(participantShape); + + // when + modeling.addLane(participantShape, 'left'); + + var childLanes = getChildLanes(participantShape); + + // then + expect(childLanes.length).to.eql(2); + + var firstLane = childLanes[0], + secondLane = childLanes[1]; + + // new lane was added at participant location + expect(firstLane).to.have.bounds({ + x: participantBounds.x, + y: participantBounds.y + 30, + width: participantBounds.width, + height: participantBounds.height - 30 + }); + + expect(secondLane).to.have.bounds({ + x: participantBounds.x - DEFAULT_VERTICAL_LANE_WIDTH, + y: participantBounds.y + 30, + width: DEFAULT_VERTICAL_LANE_WIDTH, + height: participantBounds.height - 30 + }); + + expect(firstLane.di.isHorizontal).to.be.false; + expect(secondLane.di.isHorizontal).to.be.false; })); }); @@ -280,7 +720,7 @@ describe('features/modeling - add Lane', function() { })); - it('should move flow nodes and sequence flows', inject(function(elementRegistry, modeling) { + it('should move up flow nodes and sequence flows', inject(function(elementRegistry, modeling) { // given var laneShape = elementRegistry.get('Nested_Lane_B'), @@ -312,6 +752,47 @@ describe('features/modeling - add Lane', function() { }); + describe('flow node handling - basics vertical', function() { + + var diagramXML = require('./lanes.vertical.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + it('should move left flow nodes and sequence flows', inject(function(elementRegistry, modeling) { + + // given + var laneShape = elementRegistry.get('Nested_Vertical_Lane_B'), + task_Boundary = elementRegistry.get('V_Task_Boundary'), + boundary = elementRegistry.get('V_Boundary'), + sequenceFlow = elementRegistry.get('Flow_V'), + sequenceFlow_From_Boundary = elementRegistry.get('Flow_From_V_Boundary'); + + // when + var newLane = modeling.addLane(laneShape, 'left'); + + // then + expect(task_Boundary).to.have.position({ x: 190 - newLane.width, y: 170 }); + expect(boundary).to.have.position({ x: 272 - newLane.width, y: 212 }); + + expect(sequenceFlow_From_Boundary).to.have.waypoints([ + { x: 308 - newLane.width, y: 230 }, + { x: 320 - newLane.width, y: 230 }, + { x: 320 - newLane.width, y: 370 }, + { x: 290 - newLane.width, y: 370 } + ]); + + expect(sequenceFlow).to.have.waypoints([ + { x: 240 - newLane.width, y: 250 }, + { x: 240 - newLane.width, y: 330 } + ]); + })); + + }); + + describe('flow node handling', function() { var diagramXML = require('./lanes-flow-nodes.bpmn'); @@ -449,6 +930,142 @@ describe('features/modeling - add Lane', function() { }); + describe('flow node handling - vertical', function() { + + var diagramXML = require('./lanes-flow-nodes-vertical.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + function addLaneLeft(laneId) { + + return getBpmnJS().invoke(function(elementRegistry, modeling) { + var existingLane = elementRegistry.get(laneId); + + expect(existingLane).to.exist; + + return modeling.addLane(existingLane, 'left'); + }); + } + + function addLaneRight(laneId) { + + return getBpmnJS().invoke(function(elementRegistry, modeling) { + var existingLane = elementRegistry.get(laneId); + + expect(existingLane).to.exist; + + return modeling.addLane(existingLane, 'right'); + }); + } + + it('should move flow nodes', inject(function(elementRegistry, modeling) { + + // given + var task_Boundary = elementRegistry.get('V_Task_Boundary'), + taskPosition = getPosition(task_Boundary), + boundary = elementRegistry.get('V_Boundary'), + boundaryPosition = getPosition(boundary); + + // when + addLaneLeft('Nested_Vertical_Lane_B'); + + // then + expect(task_Boundary).to.have.position({ x: taskPosition.x - 120, y: taskPosition.y }); + expect(boundary).to.have.position({ x: boundaryPosition.x - 120, y: boundaryPosition.y }); + })); + + + it('should move sequence flows', inject(function(elementRegistry, modeling) { + + // given + var sequenceFlow = elementRegistry.get('Flow_V'), + sequenceFlowWaypoints = sequenceFlow.waypoints, + sequenceFlow_From_Boundary = elementRegistry.get('Flow_From_V_Boundary'), + sequenceFlow_From_BoundaryWaypoints = sequenceFlow_From_Boundary.waypoints; + + // when + addLaneLeft('Nested_Vertical_Lane_B'); + + // then + expect(sequenceFlow_From_Boundary).to.have.waypoints( + moveWaypoints(sequenceFlow_From_BoundaryWaypoints, -120, 0) + ); + + expect(sequenceFlow).to.have.waypoints( + moveWaypoints(sequenceFlowWaypoints, -120, 0) + ); + })); + + + it('should move message flows when lane added above', inject(function(elementRegistry) { + + // given + var messageFlow = elementRegistry.get('MessageFlowLeft'), + messageFlowWaypoints = messageFlow.waypoints; + + // when + addLaneLeft('Nested_Vertical_Lane_B'); + + // then + expect(messageFlow).to.have.waypoints([ + movePosition(messageFlowWaypoints[0], -120, 0), + messageFlowWaypoints[1] + ]); + })); + + + it('should move message flows when lane added below', inject(function(elementRegistry) { + + // given + var messageFlow = elementRegistry.get('MessageFlowRight'), + messageFlowWaypoints = messageFlow.waypoints; + + // when + addLaneRight('Nested_Vertical_Lane_B'); + + // then + expect(messageFlow).to.have.waypoints([ + messageFlowWaypoints[0], + movePosition(messageFlowWaypoints[1], 120, 0) + ]); + })); + + + it('should move external labels', inject(function(elementRegistry, modeling) { + + // given + var event = elementRegistry.get('V_Event'), + label = event.label, + labelPosition = getPosition(label), + boundary = elementRegistry.get('V_Boundary'), + boundaryLabel = boundary.label, + boundaryLabelPosition = getPosition(boundaryLabel); + + // TODO(nikku): consolidate import + editing behavior => not consistent right now + + // when + // force move label to trigger label editing + update parent behavior + modeling.moveElements([ label ], { x: 0, y: 0 }); + + addLaneLeft('Nested_Vertical_Lane_B'); + + // then + expect(label).to.have.position({ + x: labelPosition.x - 120, + y: labelPosition.y + }); + + expect(boundaryLabel).to.have.position({ + x: boundaryLabelPosition.x - 120, + y: boundaryLabelPosition.y + }); + })); + + }); + describe('Internet Explorer', function() { diff --git a/test/spec/features/modeling/lanes/lanes-flow-nodes-vertical.bpmn b/test/spec/features/modeling/lanes/lanes-flow-nodes-vertical.bpmn new file mode 100644 index 0000000000..ac5c3d1ebb --- /dev/null +++ b/test/spec/features/modeling/lanes/lanes-flow-nodes-vertical.bpmn @@ -0,0 +1,111 @@ + + + + + + + + + + + + + V_Task_Boundary + V_Task + V_Event + V_Boundary + + + V_Task_Boundary + V_Task + V_Event + V_Boundary + + + + + + + Flow_V + + + Flow_From_V_Boundary + Flow_V + + + + Flow_From_V_Boundary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/lanes/lanes.only.bpmn b/test/spec/features/modeling/lanes/lanes.only.bpmn new file mode 100644 index 0000000000..895eb6f8c7 --- /dev/null +++ b/test/spec/features/modeling/lanes/lanes.only.bpmn @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/lanes/lanes.only.vertical.bpmn b/test/spec/features/modeling/lanes/lanes.only.vertical.bpmn new file mode 100644 index 0000000000..585dca34d5 --- /dev/null +++ b/test/spec/features/modeling/lanes/lanes.only.vertical.bpmn @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/lanes/participant-no-lane-vertical.bpmn b/test/spec/features/modeling/lanes/participant-no-lane-vertical.bpmn new file mode 100644 index 0000000000..c0d594e644 --- /dev/null +++ b/test/spec/features/modeling/lanes/participant-no-lane-vertical.bpmn @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +