From 0a03e598668228aae52f02ad7a76ce72a822d45e Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Tue, 31 Mar 2015 15:02:04 +0200 Subject: [PATCH] feat(modeling): add participant modeling behavior This commit adds the ability to model participants from the palette. * Empty diagrams can be used as a start for participant _AND_ process diagram * Process diagrams can be converted to collaboration diagrams by dropping a participant onto them Closes #128 --- lib/features/modeling/BpmnFactory.js | 8 + lib/features/modeling/BpmnUpdater.js | 36 +++- lib/features/modeling/ElementFactory.js | 21 ++ lib/features/modeling/Modeling.js | 43 +++- .../modeling/behavior/CreateBehavior.js | 104 +++++++++ .../modeling/behavior/DropBehavior.js | 21 +- .../modeling/behavior/RemoveBehavior.js | 48 +++++ lib/features/modeling/behavior/index.js | 4 +- .../modeling/cmd/UpdateCanvasRootHandler.js | 73 +++++++ lib/features/modeling/rules/ModelingRules.js | 43 +++- lib/features/palette/PaletteProvider.js | 22 +- .../collaboration-empty-participant.bpmn | 14 ++ .../collaboration-message-flows.bpmn | 90 ++++++++ .../collaboration-participant.bpmn | 60 ++++++ .../bpmn/collaboration/process-empty.bpmn | 7 + test/fixtures/bpmn/collaboration/process.bpmn | 48 +++++ .../modeling/CreateParticipantSpec.js | 200 ++++++++++++++++++ .../modeling/DeleteParticipantSpec.js | 97 +++++++++ .../features/palette/PaletteProviderSpec.js | 2 +- 19 files changed, 909 insertions(+), 32 deletions(-) create mode 100644 lib/features/modeling/behavior/CreateBehavior.js create mode 100644 lib/features/modeling/behavior/RemoveBehavior.js create mode 100644 lib/features/modeling/cmd/UpdateCanvasRootHandler.js create mode 100644 test/fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn create mode 100644 test/fixtures/bpmn/collaboration/collaboration-message-flows.bpmn create mode 100644 test/fixtures/bpmn/collaboration/collaboration-participant.bpmn create mode 100644 test/fixtures/bpmn/collaboration/process-empty.bpmn create mode 100644 test/fixtures/bpmn/collaboration/process.bpmn create mode 100644 test/spec/features/modeling/CreateParticipantSpec.js create mode 100644 test/spec/features/modeling/DeleteParticipantSpec.js diff --git a/lib/features/modeling/BpmnFactory.js b/lib/features/modeling/BpmnFactory.js index d74103bf..888b6941 100644 --- a/lib/features/modeling/BpmnFactory.js +++ b/lib/features/modeling/BpmnFactory.js @@ -16,6 +16,9 @@ BpmnFactory.prototype._needsId = function(element) { return element.$instanceOf('bpmn:RootElement') || element.$instanceOf('bpmn:FlowElement') || element.$instanceOf('bpmn:Artifact') || + element.$instanceOf('bpmn:Participant') || + element.$instanceOf('bpmn:Process') || + element.$instanceOf('bpmn:Collaboration') || element.$instanceOf('bpmndi:BPMNShape') || element.$instanceOf('bpmndi:BPMNEdge') || element.$instanceOf('bpmndi:BPMNDiagram') || @@ -81,5 +84,10 @@ BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) { }, attrs)); }; +BpmnFactory.prototype.createDiPlane = function(semantic) { + return this.create('bpmndi:BPMNPlane', { + bpmnElement: semantic + }); +}; module.exports = BpmnFactory; diff --git a/lib/features/modeling/BpmnUpdater.js b/lib/features/modeling/BpmnUpdater.js index b969d823..575dc65e 100644 --- a/lib/features/modeling/BpmnUpdater.js +++ b/lib/features/modeling/BpmnUpdater.js @@ -69,7 +69,6 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) { 'connection.move', 'connection.delete' ], updateParent); - // update bounds function updateBounds(e) { self.updateBounds(e.context.shape); @@ -186,6 +185,13 @@ BpmnUpdater.prototype.updateDiParent = function(di, parentDi) { } }; +function getDefinitions(element) { + while (element && !element.$instanceOf('bpmn:Definitions')) { + element = element.$parent; + } + + return element; +} BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent) { @@ -197,10 +203,6 @@ BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent) if (businessObject.$instanceOf('bpmn:FlowElement')) { - if (businessObject.$parent === newParent) { - return; - } - if (newParent && newParent.$instanceOf('bpmn:Participant')) { newParent = newParent.processRef; } @@ -226,6 +228,30 @@ BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent) containment = 'artifacts'; } + if (businessObject.$instanceOf('bpmn:Participant')) { + containment = 'participants'; + + // make sure the participants process is properly attached / detached + // from the XML document + + var process = businessObject.processRef, + definitions; + + if (process) { + definitions = getDefinitions(businessObject.$parent || newParent); + + if (businessObject.$parent) { + Collections.remove(definitions.get('rootElements'), process); + process.$parent = null; + } + + if (newParent) { + Collections.add(definitions.get('rootElements'), process); + process.$parent = definitions; + } + } + } + if (!containment) { throw new Error('no parent for ', businessObject, newParent); } diff --git a/lib/features/modeling/ElementFactory.js b/lib/features/modeling/ElementFactory.js index 0a7b643f..8387acc5 100644 --- a/lib/features/modeling/ElementFactory.js +++ b/lib/features/modeling/ElementFactory.js @@ -49,6 +49,11 @@ ElementFactory.prototype.create = function(elementType, attrs) { } if (!businessObject.di) { + if (elementType === 'root') { + businessObject.di = this._bpmnFactory.createDiPlane(businessObject, [], { + id: businessObject.id + '_di' + }); + } else if (elementType === 'connection') { businessObject.di = this._bpmnFactory.createDiEdge(businessObject, [], { id: businessObject.id + '_di' @@ -111,5 +116,21 @@ ElementFactory.prototype._getDefaultSize = function(semantic) { return { width: 36, height: 36 }; } + if (semantic.$instanceOf('bpmn:Participant')) { + return { width: 600, height: 300 }; + } + return { width: 100, height: 80 }; }; + + +ElementFactory.prototype.createParticipantShape = function(collapsed) { + + var participantShape = this.createShape({ type: 'bpmn:Participant' }); + + if (!collapsed) { + participantShape.businessObject.processRef = this._bpmnFactory.create('bpmn:Process'); + } + + return participantShape; +}; \ No newline at end of file diff --git a/lib/features/modeling/Modeling.js b/lib/features/modeling/Modeling.js index 9a0d2d9e..b2e3583a 100644 --- a/lib/features/modeling/Modeling.js +++ b/lib/features/modeling/Modeling.js @@ -4,7 +4,8 @@ var inherits = require('inherits'); var BaseModeling = require('diagram-js/lib/features/modeling/Modeling'); -var UpdatePropertiesHandler = require('./cmd/UpdatePropertiesHandler'); +var UpdatePropertiesHandler = require('./cmd/UpdatePropertiesHandler'), + UpdateCanvasRootHandler = require('./cmd/UpdateCanvasRootHandler'); /** @@ -29,6 +30,7 @@ Modeling.prototype.getHandlers = function() { var handlers = BaseModeling.prototype.getHandlers.call(this); handlers['element.updateProperties'] = UpdatePropertiesHandler; + handlers['canvas.updateRoot'] = UpdateCanvasRootHandler; return handlers; }; @@ -72,4 +74,43 @@ Modeling.prototype.updateProperties = function(element, properties) { element: element, properties: properties }); +}; + + +/** + * Transform the current diagram into a collaboration. + * + * @return {djs.model.Root} the new root element + */ +Modeling.prototype.makeCollaboration = function() { + + var collaborationElement = this._create('root', { + type: 'bpmn:Collaboration' + }); + + var context = { + newRoot: collaborationElement + }; + + this._commandStack.execute('canvas.updateRoot', context); + + return collaborationElement; +}; + +/** + * Transform the current diagram into a process. + * + * @return {djs.model.Root} the new root element + */ +Modeling.prototype.makeProcess = function() { + + var processElement = this._create('root', { + type: 'bpmn:Process' + }); + + var context = { + newRoot: processElement + }; + + this._commandStack.execute('canvas.updateRoot', context); }; \ No newline at end of file diff --git a/lib/features/modeling/behavior/CreateBehavior.js b/lib/features/modeling/behavior/CreateBehavior.js new file mode 100644 index 00000000..bace4a8f --- /dev/null +++ b/lib/features/modeling/behavior/CreateBehavior.js @@ -0,0 +1,104 @@ +'use strict'; + +var inherits = require('inherits'); + +var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); + +var getBoundingBox = require('diagram-js/lib/util/Elements').getBBox, + assign = require('lodash/object/assign'); + +/** + * BPMN specific create behavior + */ +function CreateBehavior(eventBus, modeling) { + + CommandInterceptor.call(this, eventBus); + + + /** + * morph process into collaboration before adding + * participant onto collaboration + */ + + this.preExecute('shape.create', function(context) { + + var parent = context.parent, + shape = context.shape, + position = context.position; + + if (parent.businessObject.$instanceOf('bpmn:Process') && + shape.businessObject.$instanceOf('bpmn:Participant')) { + + // update shape size + position of new participant to + // encapsulate all existing children + if (parent.children.length) { + var bbox = getBoundingBox(parent.children); + + assign(shape, { + width: bbox.width + 60, + height: bbox.height + 40 + }); + + position = { + x: bbox.x - 40 + shape.width / 2, + y: bbox.y - 20 + shape.height / 2 + }; + } + + // this is going to detach the process root + // and set the returned collaboration element + // as the new root element + var collaborationElement = modeling.makeCollaboration(); + + // monkey patch the create context + // so that the participant is being dropped + // onto the new collaboration root instead + context.position = position; + context.parent = collaborationElement; + + context.processRoot = parent; + } + }, true); + + this.execute('shape.create', function(context) { + + var processRoot = context.processRoot, + shape = context.shape; + + if (processRoot) { + context.oldProcessRef = shape.businessObject.processRef; + + // assign the participant processRef + shape.businessObject.processRef = processRoot.businessObject; + } + }, true); + + this.revert('shape.create', function(context) { + var processRoot = context.processRoot, + shape = context.shape; + + if (processRoot) { + // assign the participant processRef + shape.businessObject.processRef = context.oldProcessRef; + } + }, true); + + this.postExecute('shape.create', function(context) { + + var processRoot = context.processRoot, + shape = context.shape; + + if (processRoot) { + // process root is already detached at this point + var processChildren = processRoot.children.slice(); + modeling.moveShapes(processChildren, { x: 0, y: 0 }, shape); + } + }, true); + +} + +CreateBehavior.$inject = [ 'eventBus', 'modeling' ]; + +inherits(CreateBehavior, CommandInterceptor); + +module.exports = CreateBehavior; \ No newline at end of file diff --git a/lib/features/modeling/behavior/DropBehavior.js b/lib/features/modeling/behavior/DropBehavior.js index e8c4dc67..469299e7 100644 --- a/lib/features/modeling/behavior/DropBehavior.js +++ b/lib/features/modeling/behavior/DropBehavior.js @@ -1,18 +1,20 @@ 'use strict'; -var forEach = require('lodash/collection/forEach'); +var forEach = require('lodash/collection/forEach'), + inherits = require('inherits'); +var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); function DropBehavior(eventBus, modeling) { - // sequence flow handling + CommandInterceptor.call(this, eventBus); - eventBus.on([ - 'commandStack.shapes.move.postExecute' - ], function(e) { + // remove sequence flows that should not be allowed + // after a move operation - var context = e.context, - closure = context.closure, + this.postExecute('shapes.move', function(context) { + + var closure = context.closure, allConnections = closure.allConnections; forEach(allConnections, function(c) { @@ -22,10 +24,11 @@ function DropBehavior(eventBus, modeling) { modeling.removeConnection(c); } }); - }); - + }, true); } +inherits(DropBehavior, CommandInterceptor); + DropBehavior.$inject = [ 'eventBus', 'modeling' ]; module.exports = DropBehavior; \ No newline at end of file diff --git a/lib/features/modeling/behavior/RemoveBehavior.js b/lib/features/modeling/behavior/RemoveBehavior.js new file mode 100644 index 00000000..3c07fe53 --- /dev/null +++ b/lib/features/modeling/behavior/RemoveBehavior.js @@ -0,0 +1,48 @@ +'use strict'; + +var inherits = require('inherits'); + +var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); + +/** + * BPMN specific remove behavior + */ +function RemoveBehavior(eventBus, modeling) { + + CommandInterceptor.call(this, eventBus); + + + /** + * morph collaboration diagram into process diagram + * after the last participant has been removed + */ + + this.preExecute('shape.delete', function(context) { + + var shape = context.shape, + parent = shape.parent; + + // activate the behavior if the shape to be removed + // is a participant + if (shape.businessObject.$instanceOf('bpmn:Participant')) { + context.collaborationRoot = parent; + } + }, true); + + this.postExecute('shape.delete', function(context) { + + var collaborationRoot = context.collaborationRoot; + + if (collaborationRoot && collaborationRoot.businessObject.participants.length < 2) { + // replace empty collaboration with process diagram + modeling.makeProcess(); + } + }, true); + +} + +RemoveBehavior.$inject = [ 'eventBus', 'modeling' ]; + +inherits(RemoveBehavior, CommandInterceptor); + +module.exports = RemoveBehavior; \ No newline at end of file diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 68d9d2c2..3d4de43b 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -1,5 +1,7 @@ module.exports = { - __init__: [ 'appendBehavior', 'dropBehavior' ], + __init__: [ 'appendBehavior', 'dropBehavior', 'createBehavior', 'removeBehavior' ], appendBehavior: [ 'type', require('./AppendBehavior') ], + createBehavior: [ 'type', require('./CreateBehavior') ], + removeBehavior: [ 'type', require('./RemoveBehavior') ], dropBehavior: [ 'type', require('./DropBehavior') ] }; \ No newline at end of file diff --git a/lib/features/modeling/cmd/UpdateCanvasRootHandler.js b/lib/features/modeling/cmd/UpdateCanvasRootHandler.js new file mode 100644 index 00000000..0bcda9ff --- /dev/null +++ b/lib/features/modeling/cmd/UpdateCanvasRootHandler.js @@ -0,0 +1,73 @@ +'use strict'; + +var Collections = require('diagram-js/lib/util/Collections'); + + +function UpdateCanvasRootHandler(canvas, modeling) { + this._canvas = canvas; + this._modeling = modeling; +} + +UpdateCanvasRootHandler.$inject = [ 'canvas', 'modeling' ]; + +module.exports = UpdateCanvasRootHandler; + + +UpdateCanvasRootHandler.prototype.execute = function(context) { + + var canvas = this._canvas; + + var newRoot = context.newRoot, + newRootBusinessObject = newRoot.businessObject, + oldRoot = canvas.getRootElement(), + oldRootBusinessObject = oldRoot.businessObject, + bpmnDefinitions = oldRootBusinessObject.$parent, + diPlane = oldRootBusinessObject.di; + + // (1) replace process old <> new root + canvas.setRootElement(newRoot, true); + + // (2) update root elements + Collections.add(bpmnDefinitions.rootElements, newRootBusinessObject); + newRootBusinessObject.$parent = bpmnDefinitions; + + Collections.remove(bpmnDefinitions.rootElements, oldRootBusinessObject); + oldRootBusinessObject.$parent = null; + + // (3) wire di + oldRootBusinessObject.di = null; + + diPlane.bpmnElement = newRootBusinessObject; + newRootBusinessObject.di = diPlane; + + context.oldRoot = oldRoot; +}; + + +UpdateCanvasRootHandler.prototype.revert = function(context) { + + var canvas = this._canvas; + + var newRoot = context.newRoot, + newRootBusinessObject = newRoot.businessObject, + oldRoot = context.oldRoot, + oldRootBusinessObject = oldRoot.businessObject, + bpmnDefinitions = newRootBusinessObject.$parent, + diPlane = newRootBusinessObject.di; + + // (1) replace process old <> new root + canvas.setRootElement(oldRoot, true); + + // (2) update root elements + Collections.remove(bpmnDefinitions.rootElements, newRootBusinessObject); + newRootBusinessObject.$parent = null; + + Collections.add(bpmnDefinitions.rootElements, oldRootBusinessObject); + oldRootBusinessObject.$parent = bpmnDefinitions; + + // (3) wire di + newRootBusinessObject.di = null; + + diPlane.bpmnElement = oldRootBusinessObject; + oldRootBusinessObject.di = diPlane; +}; \ No newline at end of file diff --git a/lib/features/modeling/rules/ModelingRules.js b/lib/features/modeling/rules/ModelingRules.js index 41bf55a8..958b8d72 100644 --- a/lib/features/modeling/rules/ModelingRules.js +++ b/lib/features/modeling/rules/ModelingRules.js @@ -18,7 +18,6 @@ ModelingRules.$inject = [ 'eventBus' ]; module.exports = ModelingRules; - ModelingRules.prototype.init = function() { function nonExistantOrLabel(element) { @@ -173,15 +172,15 @@ ModelingRules.prototype.init = function() { newBounds = context.newBounds, bo = shape.businessObject; - if (!bo.$instanceOf('bpmn:SubProcess') || !bo.di.isExpanded) { - return false; + if (bo.$instanceOf('bpmn:SubProcess') && bo.di.isExpanded) { + return !newBounds || (newBounds.width >= 100 && newBounds.height >= 80); } - if (newBounds) { - if (newBounds.width < 100 || newBounds.height < 80) { - return false; - } + if (bo.$instanceOf('bpmn:Participant')) { + return !newBounds || (newBounds.width >= 100 && newBounds.height >= 80); } + + return false; }); /** @@ -191,6 +190,14 @@ ModelingRules.prototype.init = function() { */ function canDrop(businessObject, targetBusinessObject, targetDi) { + // allow to create new participants on + // on existing collaboration and process diagrams + if (businessObject.$instanceOf('bpmn:Participant') && + (targetBusinessObject.$instanceOf('bpmn:Process') || + targetBusinessObject.$instanceOf('bpmn:Collaboration'))) { + return true; + } + if (businessObject.$instanceOf('bpmn:FlowElement') && targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer')) { @@ -202,15 +209,29 @@ ModelingRules.prototype.init = function() { return true; } - if (businessObject.$instanceOf('bpmn:TextAnnotation') && - targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer')) { + if (targetBusinessObject.$instanceOf('bpmn:Collaboration') && + !businessObject.$instanceOf('bpmn:Participant') && + !targetBusinessObject.participants.length) { + // TODO: transform collaboration into process + return false; + } + + if (businessObject.$instanceOf('bpmn:FlowElement') && + targetBusinessObject.$instanceOf('bpmn:Participant') && + targetBusinessObject.processRef) { + return true; + } + + if (businessObject.$instanceOf('bpmn:TextAnnotation') && + (targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer') || + targetBusinessObject.$instanceOf('bpmn:Participant'))) { return true; } if (businessObject.$instanceOf('bpmn:Association') && - targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer')) { - + (targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer') || + targetBusinessObject.$instanceOf('bpmn:Participant'))) { return true; } diff --git a/lib/features/palette/PaletteProvider.js b/lib/features/palette/PaletteProvider.js index 0625bb6f..95750fb0 100644 --- a/lib/features/palette/PaletteProvider.js +++ b/lib/features/palette/PaletteProvider.js @@ -20,6 +20,11 @@ PaletteProvider.$inject = [ 'palette', 'create', 'elementFactory' ]; PaletteProvider.prototype.getPaletteEntries = function(element) { + var actions = {}, + create = this._create, + elementFactory = this._elementFactory; + + function createAction(type, group, className, title, options) { function createListener(event) { @@ -43,9 +48,9 @@ PaletteProvider.prototype.getPaletteEntries = function(element) { }; } - var actions = {}, - create = this._create, - elementFactory = this._elementFactory; + function createParticipant(event, collapsed) { + create.start(event, elementFactory.createParticipantShape(collapsed)); + } assign(actions, { @@ -71,7 +76,16 @@ PaletteProvider.prototype.getPaletteEntries = function(element) { 'create.subprocess-expanded': createAction( 'bpmn:SubProcess', 'activity', 'icon-subprocess-expanded', 'Sub Process (expanded)', { isExpanded: true } - ) + ), + 'create.participant-expanded': { + group: 'collaboration', + className: 'icon-participant', + title: 'Create a participant', + action: { + dragstart: createParticipant, + click: createParticipant + } + } }); return actions; diff --git a/test/fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn b/test/fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn new file mode 100644 index 00000000..f0fc4348 --- /dev/null +++ b/test/fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/collaboration/collaboration-message-flows.bpmn b/test/fixtures/bpmn/collaboration/collaboration-message-flows.bpmn new file mode 100644 index 00000000..ed430f52 --- /dev/null +++ b/test/fixtures/bpmn/collaboration/collaboration-message-flows.bpmn @@ -0,0 +1,90 @@ + + + + + + + + + + + + SequenceFlow_1 + SequenceFlow_2 + + + + SequenceFlow_1 + + + + SequenceFlow_2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/collaboration/collaboration-participant.bpmn b/test/fixtures/bpmn/collaboration/collaboration-participant.bpmn new file mode 100644 index 00000000..2aebf233 --- /dev/null +++ b/test/fixtures/bpmn/collaboration/collaboration-participant.bpmn @@ -0,0 +1,60 @@ + + + + + + + + SequenceFlow_1 + SequenceFlow_2 + + + + SequenceFlow_1 + + + + SequenceFlow_2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/collaboration/process-empty.bpmn b/test/fixtures/bpmn/collaboration/process-empty.bpmn new file mode 100644 index 00000000..b281085c --- /dev/null +++ b/test/fixtures/bpmn/collaboration/process-empty.bpmn @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/collaboration/process.bpmn b/test/fixtures/bpmn/collaboration/process.bpmn new file mode 100644 index 00000000..1ef9119b --- /dev/null +++ b/test/fixtures/bpmn/collaboration/process.bpmn @@ -0,0 +1,48 @@ + + + + + SequenceFlow_1 + SequenceFlow_2 + + + + SequenceFlow_1 + + + + SequenceFlow_2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/spec/features/modeling/CreateParticipantSpec.js b/test/spec/features/modeling/CreateParticipantSpec.js new file mode 100644 index 00000000..f7695323 --- /dev/null +++ b/test/spec/features/modeling/CreateParticipantSpec.js @@ -0,0 +1,200 @@ +'use strict'; + +var TestHelper = require('../../../TestHelper'); + +/* global bootstrapModeler, inject */ + + +var modelingModule = require('../../../../lib/features/modeling'), + coreModule = require('../../../../lib/core'); + + +describe('features/modeling - create participant', function() { + + var testModules = [ coreModule, modelingModule ]; + + + describe('on process diagram', function() { + + describe('should transform diagram into collaboration', function() { + + var processDiagramXML = require('../../../fixtures/bpmn/collaboration/process-empty.bpmn'); + + beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + + + it('execute', inject(function(modeling, elementFactory, canvas) { + + // given + var processShape = canvas.getRootElement(), + process = processShape.businessObject, + participantShape = elementFactory.createParticipantShape(true), + participant = participantShape.businessObject, + diRoot = process.di.$parent; + + // when + modeling.createShape(participantShape, { x: 350, y: 200 }, processShape); + + // then + expect(participant.processRef).toBe(process); + + var collaborationRoot = canvas.getRootElement(), + collaboration = collaborationRoot.businessObject, + collaborationDi = collaboration.di; + + expect(collaboration.$instanceOf('bpmn:Collaboration')).toBe(true); + + // participant / collaboration are wired + expect(participant.$parent).toBe(collaboration); + expect(collaboration.participants).toContain(participant); + + + // collaboration is added to root elements + expect(collaboration.$parent).toBe(process.$parent); + + // di is wired + var participantDi = participant.di; + + expect(participantDi.$parent).toBe(collaborationDi); + expect(collaborationDi.$parent).toBe(diRoot); + })); + + + it('undo', inject(function(modeling, elementFactory, canvas, commandStack) { + + // given + var processShape = canvas.getRootElement(), + process = processShape.businessObject, + processDi = process.di, + participantShape = elementFactory.createParticipantShape(true), + participant = participantShape.businessObject, + oldParticipantProcessRef = participant.processRef, + diRoot = process.di.$parent; + + modeling.createShape(participantShape, { x: 350, y: 200 }, processShape); + + var collaborationRoot = canvas.getRootElement(), + collaboration = collaborationRoot.businessObject; + + // when + commandStack.undo(); + + + // then + expect(participant.processRef).toBe(oldParticipantProcessRef); + + expect(participant.$parent).toBe(null); + expect(collaboration.participants).not.toContain(participant); + + // collaboration is detached + expect(collaboration.$parent).toBe(null); + + // di is wired + expect(processDi.$parent).toBe(diRoot); + })); + + }); + + + describe('should wrap existing elements', function() { + + var processDiagramXML = require('../../../fixtures/bpmn/collaboration/process.bpmn'); + + beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + + + it('execute', inject(function(modeling, elementFactory, canvas) { + + // given + var processShape = canvas.getRootElement(), + process = processShape.businessObject, + participantShape = elementFactory.createParticipantShape(true), + participant = participantShape.businessObject; + + // when + modeling.createShape(participantShape, { x: 350, y: 200 }, processShape); + + // then + expect(participant.processRef).toBe(process); + + var newRootShape = canvas.getRootElement(), + collaboration = newRootShape.businessObject; + + expect(collaboration.$instanceOf('bpmn:Collaboration')).toBe(true); + + expect(participant.$parent).toBe(collaboration); + expect(collaboration.participants).toContain(participant); + })); + + + it('undo', inject(function(modeling, elementFactory, canvas, commandStack) { + + // given + var processShape = canvas.getRootElement(), + participantShape = elementFactory.createParticipantShape(true); + + modeling.createShape(participantShape, { x: 350, y: 200 }, processShape); + + // when + commandStack.undo(); + + + // then + expect(participantShape.children.length).toBe(0); + expect(processShape.children.length).toBe(9); + })); + + }); + + }); + + + describe('should add to collaboration', function() { + + var collaborationDiagramXML = require('../../../fixtures/bpmn/collaboration/collaboration-participant.bpmn'); + + beforeEach(bootstrapModeler(collaborationDiagramXML, { modules: testModules })); + + + it('execute', inject(function(modeling, elementFactory, canvas) { + + // given + var collaborationRoot = canvas.getRootElement(), + collaboration = collaborationRoot.businessObject, + participantShape = elementFactory.createParticipantShape(true), + participant = participantShape.businessObject; + + // when + modeling.createShape(participantShape, { x: 350, y: 500 }, collaborationRoot); + + // then + expect(collaborationRoot.children).toContain(participantShape); + + expect(participant.$parent).toBe(collaboration); + expect(collaboration.participants).toContain(participant); + })); + + + it('undo', inject(function(modeling, elementFactory, canvas, commandStack) { + + // given + var collaborationRoot = canvas.getRootElement(), + collaboration = collaborationRoot.businessObject, + participantShape = elementFactory.createParticipantShape(true), + participant = participantShape.businessObject; + + modeling.createShape(participantShape, { x: 350, y: 500 }, collaborationRoot); + + // when + commandStack.undo(); + + // then + expect(collaborationRoot.children).not.toContain(participantShape); + + expect(participant.$parent).toBeFalsy(); + expect(collaboration.participants).not.toContain(participant); + })); + + }); + +}); \ No newline at end of file diff --git a/test/spec/features/modeling/DeleteParticipantSpec.js b/test/spec/features/modeling/DeleteParticipantSpec.js new file mode 100644 index 00000000..b5533d4d --- /dev/null +++ b/test/spec/features/modeling/DeleteParticipantSpec.js @@ -0,0 +1,97 @@ +'use strict'; + +var TestHelper = require('../../../TestHelper'); + +/* global bootstrapModeler, inject */ + + +var modelingModule = require('../../../../lib/features/modeling'), + coreModule = require('../../../../lib/core'); + + +describe('features/modeling - delete participant', function() { + + var testModules = [ coreModule, modelingModule ]; + + + describe('last remaining', function() { + + describe('should transform diagram into process diagram', function() { + + var processDiagramXML = require('../../../fixtures/bpmn/collaboration/collaboration-empty-participant.bpmn'); + + beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + + + it('execute', inject(function(modeling, elementRegistry, canvas) { + + // given + var participantShape = elementRegistry.get('_Participant_2'), + participant = participantShape.businessObject, + participantDi = participant.di, + process = participant.processRef, + collaborationElement = participantShape.parent, + collaboration = collaborationElement.businessObject, + diPlane = collaboration.di, + bpmnDefinitions = collaboration.$parent; + + // when + modeling.removeShape(participantShape); + + // then + expect(participant.$parent).toBeFalsy(); + + var newRootShape = canvas.getRootElement(), + newRootBusinessObject = newRootShape.businessObject; + + expect(newRootBusinessObject.$instanceOf('bpmn:Process')).toBe(true); + + // collaboration DI is unwired + expect(participantDi.$parent).toBeFalsy(); + expect(collaboration.di).toBeFalsy(); + + expect(bpmnDefinitions.rootElements).not.toContain(process); + expect(bpmnDefinitions.rootElements).not.toContain(collaboration); + + // process DI is wired + expect(diPlane.bpmnElement).toBe(newRootBusinessObject); + expect(newRootBusinessObject.di).toBe(diPlane); + + expect(bpmnDefinitions.rootElements).toContain(newRootBusinessObject); + })); + + + it('undo', inject(function(modeling, elementRegistry, canvas, commandStack) { + + // given + var participantShape = elementRegistry.get('_Participant_2'), + participant = participantShape.businessObject, + originalRootElement = participantShape.parent, + originalRootElementBo = originalRootElement.businessObject, + bpmnDefinitions = originalRootElementBo.$parent, + participantDi = participant.di, + diPlane = participantDi.$parent; + + modeling.removeShape(participantShape); + + // when + commandStack.undo(); + + // then + expect(participant.$parent).toBe(originalRootElementBo); + expect(originalRootElementBo.$parent).toBe(bpmnDefinitions); + + expect(canvas.getRootElement()).toBe(originalRootElement); + + // di is unwired + expect(participantDi.$parent).toBe(originalRootElementBo.di); + + // new di is wired + expect(diPlane.bpmnElement).toBe(originalRootElementBo); + })); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/spec/features/palette/PaletteProviderSpec.js b/test/spec/features/palette/PaletteProviderSpec.js index 71590a76..d56865d4 100644 --- a/test/spec/features/palette/PaletteProviderSpec.js +++ b/test/spec/features/palette/PaletteProviderSpec.js @@ -32,7 +32,7 @@ describe('palette', function() { var paletteElement = domQuery('.djs-palette', container); // then - expect(domQuery.all('.entry', paletteElement).length).toBe(7); + expect(domQuery.all('.entry', paletteElement).length).toBe(8); done(err); });