From 226a0d76ed8757d19c2bdf923bcb72788930a4ce Mon Sep 17 00:00:00 2001 From: hoferch91 Date: Thu, 16 Jun 2016 13:57:32 +0200 Subject: [PATCH] feat(replace): properly collapse / expand SubProcess(es) * correctly toggle collapse / expand state * update children visibility Closes #575 Closes #510 review(collapse-expand) --- .../ToggleElementCollapseBehaviour.js | 139 ++++ lib/features/modeling/behavior/index.js | 6 +- .../popup-menu/ReplaceMenuProvider.js | 10 +- lib/features/replace/BpmnReplace.js | 65 +- lib/features/replace/ReplaceOptions.js | 9 + .../import/collapsed/processWithChildren.bpmn | 422 +++++++---- .../ToggleElementCollapseBehaviourSpec.js | 653 ++++++++++++++++++ ...placeMenuProvider.collapsedSubProcess.bpmn | 13 + .../popup-menu/ReplaceMenuProviderSpec.js | 24 + test/spec/features/replace/BpmnReplaceSpec.js | 79 +-- .../replace/SubProcess-collapsed.bpmn | 37 + 11 files changed, 1235 insertions(+), 222 deletions(-) create mode 100644 lib/features/modeling/behavior/ToggleElementCollapseBehaviour.js create mode 100644 test/spec/features/modeling/behavior/ToggleElementCollapseBehaviourSpec.js create mode 100644 test/spec/features/popup-menu/ReplaceMenuProvider.collapsedSubProcess.bpmn create mode 100644 test/spec/features/replace/SubProcess-collapsed.bpmn diff --git a/lib/features/modeling/behavior/ToggleElementCollapseBehaviour.js b/lib/features/modeling/behavior/ToggleElementCollapseBehaviour.js new file mode 100644 index 00000000..d5bd7fda --- /dev/null +++ b/lib/features/modeling/behavior/ToggleElementCollapseBehaviour.js @@ -0,0 +1,139 @@ +'use strict'; + +var inherits = require('inherits'); + +var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'), + getBusinessObject = require('../../../util/ModelUtil').getBusinessObject, + is = require('../../../util/ModelUtil').is, + computeChildrenBBox = require('diagram-js/lib/features/resize/ResizeUtil').computeChildrenBBox; + + +var LOW_PRIORITY = 500; + + +function ToggleElementCollapseBehaviour(eventBus, elementFactory, modeling, resize) { + CommandInterceptor.call(this, eventBus); + + + function hideEmptyLables(children) { + if (children.length) { + children.forEach(function(child) { + if (child.type === 'label' && !child.businessObject.name) { + child.hidden = true; + } + }); + } + } + + function expandedBounds(shape, defaultSize) { + var children = shape.children, + newBounds = defaultSize, + visibleElements, + visibleBBox; + + visibleElements = filterVisible(children).concat([ shape ]); + + visibleBBox = computeChildrenBBox(visibleElements); + + if (visibleBBox) { + // center to visibleBBox with max(defaultSize, childrenBounds) + newBounds.width = Math.max(visibleBBox.width, newBounds.width); + newBounds.height = Math.max(visibleBBox.height, newBounds.height); + + newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2; + newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2; + } else { + // center to collapsed shape with defaultSize + newBounds.x = shape.x + (shape.width - newBounds.width) / 2; + newBounds.y = shape.y + (shape.height - newBounds.height) / 2; + } + + return newBounds; + } + + function collapsedBounds(shape, defaultSize) { + + return { + x: shape.x + (shape.width - defaultSize.width) / 2, + y: shape.y + (shape.height - defaultSize.height) / 2, + width: defaultSize.width, + height: defaultSize.height + }; + } + + this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) { + + var context = e.context, + shape = context.shape; + + if (!is(shape, 'bpmn:SubProcess')) { + return; + } + + if (!shape.collapsed) { + // all children got made visible through djs, hide empty labels + hideEmptyLables(shape.children); + + // remove collapsed marker + getBusinessObject(shape).di.isExpanded = true; + } else { + // place collapsed marker + getBusinessObject(shape).di.isExpanded = false; + } + }); + + this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) { + + var context = e.context; + var shape = context.shape; + + + // revert removing/placing collapsed marker + if (!shape.collapsed) { + getBusinessObject(shape).di.isExpanded = true; + + } else { + getBusinessObject(shape).di.isExpanded = false; + } + }); + + this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) { + var shape = e.context.shape, + defaultSize = elementFactory._getDefaultSize(shape), + newBounds; + + if (shape.collapsed) { + + // resize to default size of collapsed shapes + newBounds = collapsedBounds(shape, defaultSize); + } else { + + // resize to bounds of max(visible children, defaultSize) + newBounds = expandedBounds(shape, defaultSize); + } + + modeling.resizeShape(shape, newBounds); + }); + +} + + +inherits(ToggleElementCollapseBehaviour, CommandInterceptor); + +ToggleElementCollapseBehaviour.$inject = [ + 'eventBus', + 'elementFactory', + 'modeling' +]; + +module.exports = ToggleElementCollapseBehaviour; + + + +/////// helpers /////////////////////////// + +function filterVisible(elements) { + return elements.filter(function(e) { + return !e.hidden; + }); +} \ No newline at end of file diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 12116b0b..59c11df4 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -18,7 +18,8 @@ module.exports = { 'unsetDefaultFlowBehavior', 'updateFlowNodeRefsBehavior', 'removeElementBehavior', - 'unclaimIdBehavior' + 'unclaimIdBehavior', + 'toggleElementCollapseBehaviour' ], appendBehavior: [ 'type', require('./AppendBehavior') ], copyPasteBehavior: [ 'type', require('./CopyPasteBehavior') ], @@ -38,5 +39,6 @@ module.exports = { unsetDefaultFlowBehavior: [ 'type', require('./UnsetDefaultFlowBehavior') ], updateFlowNodeRefsBehavior: [ 'type', require('./UpdateFlowNodeRefsBehavior') ], removeElementBehavior: [ 'type', require('./RemoveElementBehavior') ], - unclaimIdBehavior: [ 'type', require('./UnclaimIdBehavior') ] + unclaimIdBehavior: [ 'type', require('./UnclaimIdBehavior') ], + toggleElementCollapseBehaviour : [ 'type', require('./ToggleElementCollapseBehaviour') ] }; diff --git a/lib/features/popup-menu/ReplaceMenuProvider.js b/lib/features/popup-menu/ReplaceMenuProvider.js index 128d9822..c909dcf4 100644 --- a/lib/features/popup-menu/ReplaceMenuProvider.js +++ b/lib/features/popup-menu/ReplaceMenuProvider.js @@ -7,7 +7,8 @@ var is = require('../../util/ModelUtil').is, isDifferentType = require('./util/TypeUtil').isDifferentType; var forEach = require('lodash/collection/forEach'), - filter = require('lodash/collection/filter'); + filter = require('lodash/collection/filter'), + reject = require('lodash/collection/reject'); var replaceOptions = require ('../replace/ReplaceOptions'); @@ -210,6 +211,13 @@ ReplaceMenuProvider.prototype.getEntries = function(element) { }); } + // collapsed SubProcess can not be replaced with itself + if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(businessObject)) { + entries = reject(entries, function(entry) { + return entry.label === 'Sub Process (collapsed)'; + }); + } + return this._createEntries(element, entries); } diff --git a/lib/features/replace/BpmnReplace.js b/lib/features/replace/BpmnReplace.js index b72632aa..a98d351f 100644 --- a/lib/features/replace/BpmnReplace.js +++ b/lib/features/replace/BpmnReplace.js @@ -1,7 +1,8 @@ 'use strict'; var pick = require('lodash/object/pick'), - assign = require('lodash/object/assign'); + assign = require('lodash/object/assign'), + has = require('lodash/object/has'); var is = require('../../util/ModelUtil').is, isExpanded = require('../../util/DiUtil').isExpanded, @@ -15,6 +16,30 @@ var CUSTOM_PROPERTIES = [ 'isInterrupting' ]; +function toggeling(element, target) { + + var oldCollapsed = has(element, 'collapsed') ? + element.collapsed : !isExpanded(element); + + var targetCollapsed; + + if (has(target, 'collapsed') || has(target, 'isExpanded')) { + // property is explicitly set so use it + targetCollapsed = has(target, 'collapsed') ? + target.collapsed : !target.isExpanded; + } else { + // keep old state + targetCollapsed = oldCollapsed; + } + + if (oldCollapsed !== targetCollapsed) { + element.collapsed = oldCollapsed; + return true; + } + + return false; +} + /** * This module takes care of replacing BPMN elements @@ -36,8 +61,23 @@ function BpmnReplace(bpmnFactory, replace, selection, modeling) { hints = hints || {}; var type = target.type, - oldBusinessObject = element.businessObject, - newBusinessObject = bpmnFactory.create(type); + oldBusinessObject = element.businessObject; + + + + if (is(oldBusinessObject, 'bpmn:SubProcess')) { + if (type === 'bpmn:SubProcess') { + if (toggeling(element, target)) { + // expanding or collapsing process + modeling.toggleCollapse(element); + + return element; + } + } + } + + + var newBusinessObject = bpmnFactory.create(type); var newElement = { type: type, @@ -52,21 +92,16 @@ function BpmnReplace(bpmnFactory, replace, selection, modeling) { // initialize special properties defined in target definition assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES)); - if (is(oldBusinessObject, 'bpmn:SubProcess')) { - - newElement.isExpanded = isExpanded(oldBusinessObject); - } - - // preserve adhoc state while switching collapsed/expanded subprocess - if (is(oldBusinessObject, 'bpmn:AdHocSubProcess') && target.isExpanded) { - newElement.businessObject = bpmnFactory.create('bpmn:AdHocSubProcess'); - } if (is(oldBusinessObject, 'bpmn:Activity')) { - // switch collapsed/expanded subprocesses - if (target.isExpanded === true) { - newElement.isExpanded = true; + if (is(oldBusinessObject, 'bpmn:SubProcess')) { + // no toggeling, so keep old state + newElement.isExpanded = isExpanded(oldBusinessObject); + } + // else if property is explicitly set, use it + else if (has(target, 'isExpanded')) { + newElement.isExpanded = target.isExpanded; } // TODO: need also to respect min/max Size diff --git a/lib/features/replace/ReplaceOptions.js b/lib/features/replace/ReplaceOptions.js index 8d2576f3..5d7c3ac5 100644 --- a/lib/features/replace/ReplaceOptions.js +++ b/lib/features/replace/ReplaceOptions.js @@ -357,6 +357,15 @@ module.exports.SUBPROCESS_EXPANDED = [ triggeredByEvent: true, isExpanded: true } + }, + { + label: 'Sub Process (collapsed)', + actionName: 'replace-with-collapsed-subprocess', + className: 'bpmn-icon-subprocess-collapsed', + target: { + type: 'bpmn:SubProcess', + isExpanded: false + } } ]; diff --git a/test/fixtures/bpmn/import/collapsed/processWithChildren.bpmn b/test/fixtures/bpmn/import/collapsed/processWithChildren.bpmn index 7e444a0c..617db520 100644 --- a/test/fixtures/bpmn/import/collapsed/processWithChildren.bpmn +++ b/test/fixtures/bpmn/import/collapsed/processWithChildren.bpmn @@ -1,149 +1,273 @@ - - - - - SequenceFlow_1 - SequenceFlow_4 - - SequenceFlow_2 - - - - SequenceFlow_2 - SequenceFlow_5 - SequenceFlow_6 - - - - - SequenceFlow_6 - SequenceFlow_7 - - - - SequenceFlow_5 - SequenceFlow_3 - - - - SequenceFlow_3 - - - SequenceFlow_7 - - - - - SequenceFlow_1 - - - - SequenceFlow_4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + SequenceFlow_1 + SequenceFlow_4 + + SequenceFlow_2 + + + + SequenceFlow_2 + SequenceFlow_5 + SequenceFlow_6 + + + + + SequenceFlow_6 + SequenceFlow_7 + + + + SequenceFlow_5 + SequenceFlow_3 + + + + SequenceFlow_3 + + + SequenceFlow_7 + + + + + SequenceFlow_1 + + + + SequenceFlow_4 + + + SequenceFlow_9 + + + + SequenceFlow_8 + + + + + + + + + SequenceFlow_9 + SequenceFlow_8 + + + SequenceFlow_10 + + + SequenceFlow_10 + SequenceFlow_11 + + + SequenceFlow_11 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/ToggleElementCollapseBehaviourSpec.js b/test/spec/features/modeling/behavior/ToggleElementCollapseBehaviourSpec.js new file mode 100644 index 00000000..3c2709cd --- /dev/null +++ b/test/spec/features/modeling/behavior/ToggleElementCollapseBehaviourSpec.js @@ -0,0 +1,653 @@ +'use strict'; + +require('../../../../TestHelper'); + +/* global bootstrapModeler, inject */ + +var modelingModule = require('../../../../../lib/features/modeling'), + coreModule = require('../../../../../lib/core'); + +var is = require('../../../../../lib/util/ModelUtil').is; + +var testModules = [ + modelingModule, + coreModule +]; + +describe('features/modeling - collapse and expand elements', function() { + + var diagramXML = require('../../../../fixtures/bpmn/import/collapsed/processWithChildren.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + + describe('expand', function() { + + var defaultSize = { + width: 350, + height: 200 + }; + + + it('collapsed-marker is removed', + inject(function(elementRegistry, bpmnReplace) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_3'); + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + var businessObject = expandedSubProcess.businessObject; + + // then +-marker is removed + expect(businessObject.di.isExpanded).to.eql(true); + }) + ); + + + it('show all children, but hide empty labels', + inject(function(elementRegistry, bpmnReplace) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_1'); + var originalChildren = collapsedSubProcess.children.slice(); + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then keep children + originalChildren.forEach(function(c) { + expect(expandedSubProcess.children).to.include(c); + }); + + // and show them + expect(expandedSubProcess.children).to.satisfy(allShown()); + }) + ); + + + it('keep ad-hoc and multiInstance-marker', + inject(function(elementRegistry, bpmnReplace) { + + // given + var collapsedAdHocSubProcess = elementRegistry.get('SubProcess_4'); + + // when + var expandedAdHocSubProcess = bpmnReplace.replaceElement(collapsedAdHocSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then + expect(is(expandedAdHocSubProcess, 'bpmn:AdHocSubProcess')).to.eql(true); + var businessObject = expandedAdHocSubProcess.businessObject; + expect(businessObject.loopCharacteristics).to.not.be.undefined; + }) + ); + + + describe('resizing', function() { + + + it('ignors hidden children', + inject(function(elementRegistry, bpmnReplace, eventBus) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_5'); + var hiddenStartEvent = elementRegistry.get('StartEvent_6'); + eventBus.once('commandStack.shape.toggleCollapse.postExecute', function(e) { + hiddenStartEvent.hidden = true; + }); + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then hidden child should not be covered + expect(expandedSubProcess.x).to.be.greaterThan(hiddenStartEvent.x); + expect(expandedSubProcess.y).to.be.greaterThan(hiddenStartEvent.y); + }) + ); + + + it('without children is centered and has defaultBounds', + inject(function(elementRegistry, bpmnReplace) { + + // given collapsed SubProcess without children + var collapsedSubProcess = elementRegistry.get('SubProcess_3'); + + var oldMid = { + x: collapsedSubProcess.x + collapsedSubProcess.width / 2, + y: collapsedSubProcess.y + collapsedSubProcess.height / 2 + }; + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then + var newMid = { + x: expandedSubProcess.x + expandedSubProcess.width / 2, + y: expandedSubProcess.y + expandedSubProcess.height / 2 + }; + + expect(newMid).to.eql(oldMid); + expect(expandedSubProcess.width).to.be.at.least(defaultSize.width); + expect(expandedSubProcess.height).to.be.at.least(defaultSize.height); + }) + ); + + + it('with children is centered to childrenBoundingBox and has at least defaultBounds', + inject(function(elementRegistry, bpmnReplace) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_4'); + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then + var startEvent = elementRegistry.get('StartEvent_5'); + var midChildren = { + x: startEvent.x + startEvent.width / 2, + y: startEvent.y + startEvent.height / 2 + }; + + var expandedMid = { + x: expandedSubProcess.x + expandedSubProcess.width / 2, + y: expandedSubProcess.y + expandedSubProcess.height / 2 + }; + + expect(expandedMid).to.eql(midChildren), + expect(expandedSubProcess.width).to.be.at.least(defaultSize.width); + expect(expandedSubProcess.height).to.be.at.least(defaultSize.height); + }) + ); + + + it('to expanding collapsedSubProcess is coverd in childrenBoundingBox', + inject(function(elementRegistry, bpmnReplace) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_5'); + var collapsedDownRightCorner = { + x: collapsedSubProcess.x + collapsedSubProcess.width, + y: collapsedSubProcess.y + collapsedSubProcess.height + }; + + // when + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then + var expandedDownRightCorner = { + x: expandedSubProcess.x + expandedSubProcess.width, + y: expandedSubProcess.y + expandedSubProcess.height + }; + + expect(expandedDownRightCorner.x).to.be.at.least(collapsedDownRightCorner.x); + expect(expandedDownRightCorner.y).to.be.at.least(collapsedDownRightCorner.y); + }) + ); + + }); + + + describe('undo', function() { + + + it('collapsed-marker is placed', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_1'); + var expandedSubProcess = bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // when + commandStack.undo(); + var businessObject = expandedSubProcess.businessObject; + + // then +-marker is placed + expect(businessObject.di.isExpanded).to.eql(false); + }) + ); + + + it('restore previous bounds', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_1'); + var originalBounds = { + x: collapsedSubProcess.x, + y: collapsedSubProcess.y, + width: collapsedSubProcess.width, + height: collapsedSubProcess.height + }; + + bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // when + commandStack.undo(); + + // then + expect(collapsedSubProcess).to.have.bounds(originalBounds); + }) + ); + + + it('hide children', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_1'); + var originalChildren = collapsedSubProcess.children.slice(); + + bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // when + commandStack.undo(); + + // then keep children + originalChildren.forEach(function(c) { + expect(collapsedSubProcess.children).to.include(c); + }); + // and hide them + expect(collapsedSubProcess.children).to.satisfy(allHidden()); + }) + ); + + }); + + }); + + + describe('collapse', function() { + + var defaultSize = { + width: 100, + height: 80 + }; + + + it('collapsed-marker is placed', + inject(function(elementRegistry, bpmnReplace) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + + // when + var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + var businessObject = collapsedSubProcess.businessObject; + + // then +-marker is set + expect(businessObject.di.isExpanded).to.eql(false); + }) + ); + + + it('hide all children', + inject(function(elementRegistry, bpmnReplace) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + var originalChildren = expandedSubProcess.children.slice(); + + // when + var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // then keep children + originalChildren.forEach(function(c) { + expect(collapsedSubProcess.children).to.include(c); + }); + + // and hide them + expect(collapsedSubProcess.children).to.satisfy(allHidden()); + }) + ); + + + it('keep ad-hoc and multiInstance-marker', + inject(function(elementRegistry, bpmnReplace) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + + // when + var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // then + expect(is(collapsedSubProcess, 'bpmn:AdHocSubProcess')).to.eql(true); + var businessObject = collapsedSubProcess.businessObject; + expect(businessObject.loopCharacteristics).to.not.be.undefined; + }) + ); + + + describe('resize', function() { + + + it('is centered and has default bounds', + inject(function(elementRegistry, bpmnReplace) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + var oldMid = { + x: expandedSubProcess.x + expandedSubProcess.width / 2, + y: expandedSubProcess.y + expandedSubProcess.height / 2 + }; + + // when + var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // then + var newMid = { + x: collapsedSubProcess.x + collapsedSubProcess.width / 2, + y: collapsedSubProcess.y + collapsedSubProcess.height / 2 + }; + + expect(newMid).to.eql(oldMid); + expect(collapsedSubProcess.width).to.be.at.least(defaultSize.width); + expect(collapsedSubProcess.height).to.be.at.least(defaultSize.height); + + }) + ); + + }); + + + describe('undo', function() { + + + it('collapsed marker is removed', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + var collapsedSubProcess = bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // when + commandStack.undo(); + var businessObject = collapsedSubProcess.businessObject; + + // then +-marker is placed + expect(businessObject.di.isExpanded).to.eql(true); + }) + ); + + + it('originalBounds are restored', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + var originalBounds = { + x: expandedSubProcess.x, + y: expandedSubProcess.y, + width: expandedSubProcess.width, + height: expandedSubProcess.height + }; + + bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // when + commandStack.undo(); + + // then + expect(expandedSubProcess).to.have.bounds(originalBounds); + }) + ); + + + it('show children that were visible', + inject(function(elementRegistry, bpmnReplace, commandStack) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + var originalChildren = expandedSubProcess.children.slice(); + + bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // when + commandStack.undo(); + + // then keep children + originalChildren.forEach(function(c) { + expect(expandedSubProcess.children).to.include(c); + }); + + // and show the previously visible ones + expect(expandedSubProcess.children).to.satisfy(allShown()); + }) + ); + + }); + }); + + + describe('attaching marker', function() { + + + describe('collapsed', function() { + + + it('add ad-hoc-marker does not call toggleProvider', + inject(function(eventBus, bpmnReplace, elementRegistry) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_3'); + + // should not be called + eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) { + expect(true).to.eql(false); + }); + + // when + bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:AdHocSubProcess', + isExpanded: false + } + ); + + // then + + }) + ); + + + it('remove ad-hoc-marker does not call toggleProvider', + inject(function(eventBus, bpmnReplace, elementRegistry) { + + // given + var collapsedSubProcess = elementRegistry.get('SubProcess_4'); + + // should not be called + eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) { + expect(true).to.eql(false); + }); + + // when + bpmnReplace.replaceElement(collapsedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: false + } + ); + + // then + + }) + ); + + }); + + + describe('expanded', function() { + + + it('add ad-hoc-marker does not call toggleProvider', + inject(function(eventBus, bpmnReplace, elementRegistry) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_6'); + + // should not be called + eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) { + expect(true).to.eql(false); + }); + + // when + bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:AdHocSubProcess', + isExpanded: true + } + ); + + // then + + }) + ); + + + it('remove ad-hoc-marker does not call toggleProvider', + inject(function(eventBus, bpmnReplace, elementRegistry) { + + // given + var expandedSubProcess = elementRegistry.get('SubProcess_2'); + + // should not be called + eventBus.once('commandStack.shape.toggleCollapse.execute', function(e) { + expect(true).to.eql(false); + }); + + // when + bpmnReplace.replaceElement(expandedSubProcess, + { + type: 'bpmn:SubProcess', + isExpanded: true + } + ); + + // then + + }) + ); + + }); + + + + }); + + +}); + + +/////////// helpers ///////////////////////////// + + +function allHidden() { + return childrenHidden(true); +} + +function allShown() { + return childrenHidden(false); +} + +function childrenHidden(hidden) { + return function(children) { + return children.every(function(child) { + // empty labels are allways hidden + if (child.type === 'label' && !child.businessObject.name) { + return child.hidden; + } + else { + return child.hidden == hidden; + } + }); + }; +} diff --git a/test/spec/features/popup-menu/ReplaceMenuProvider.collapsedSubProcess.bpmn b/test/spec/features/popup-menu/ReplaceMenuProvider.collapsedSubProcess.bpmn new file mode 100644 index 00000000..aa2b19e1 --- /dev/null +++ b/test/spec/features/popup-menu/ReplaceMenuProvider.collapsedSubProcess.bpmn @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/test/spec/features/popup-menu/ReplaceMenuProviderSpec.js b/test/spec/features/popup-menu/ReplaceMenuProviderSpec.js index 0215ab33..3b8c1ce5 100644 --- a/test/spec/features/popup-menu/ReplaceMenuProviderSpec.js +++ b/test/spec/features/popup-menu/ReplaceMenuProviderSpec.js @@ -1086,6 +1086,30 @@ describe('features/popup-menu - replace menu provider', function() { }); + describe('collapsed subprocesses', function() { + + var diagramXML = require('./ReplaceMenuProvider.collapsedSubProcess.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + + it('options do not include collapsed subprocesses itself', inject(function(elementRegistry, popupMenu) { + + // given + var collapsedSubProcess = elementRegistry.get('Task_1'); + + // when + openPopup(collapsedSubProcess); + + var collapsedSubProcessEntry = queryEntry(popupMenu, 'replace-with-collapsed-subprocess'); + + // then + expect(collapsedSubProcessEntry).to.not.exist; + })); + + }); + + }); diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index 12aa5443..d3c703f0 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -781,7 +781,7 @@ describe('features/replace - bpmn replace', function() { }); - describe('collapsed to expanded', function() { + describe('morph task with boundaryEvent', function() { var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn'); @@ -790,7 +790,7 @@ describe('features/replace - bpmn replace', function() { })); - it('should morph task -> expanded sub process', inject(function(bpmnReplace, elementRegistry) { + it('to expanded sub process', inject(function(bpmnReplace, elementRegistry) { // given var element = elementRegistry.get('Task_1'); @@ -805,63 +805,32 @@ describe('features/replace - bpmn replace', function() { // then expect(is(newElement, 'bpmn:SubProcess')).to.be.true; expect(isExpanded(newElement)).to.be.true; - })); - - it('should expand sub-process', inject(function(bpmnReplace, elementRegistry) { - - // given - var element = elementRegistry.get('SubProcessCollapsed'); - var newElementData = { - type: 'bpmn:SubProcess', - isExpanded: true - }; - - // when - var newElement = bpmnReplace.replaceElement(element, newElementData); - - // then - expect(is(newElement, 'bpmn:SubProcess')).to.be.true; - expect(isExpanded(newElement)).to.be.true; - })); - - - it('should expand ad hoc sub-process', inject(function(bpmnReplace, elementRegistry) { - - // given - var element = elementRegistry.get('AdHocSubProcessCollapsed'); - var newElementData = { - type: 'bpmn:SubProcess', - isExpanded: true - }; - - // when - var newElement = bpmnReplace.replaceElement(element, newElementData); - - // then - expect(is(newElement, 'bpmn:AdHocSubProcess')).to.be.true; - expect(isExpanded(newElement)).to.be.true; - })); - - - it('should keep boundary events', inject(function(bpmnReplace, elementRegistry) { - - // given - var element = elementRegistry.get('Task_1'); - var newElementData = { - type: 'bpmn:SubProcess', - isExpanded: true - }; - - // when - var newElement = bpmnReplace.replaceElement(element, newElementData); - - // then - expect(is(newElement, 'bpmn:SubProcess')).to.be.true; - expect(isExpanded(newElement)).to.be.true; + // and keep boundaryEvent expect(newElement.attachers.length).to.be.equal(2); })); + + it('to collapsed sub process', inject(function(bpmnReplace, elementRegistry) { + + // given + var element = elementRegistry.get('Task_1'); + var newElementData = { + type: 'bpmn:SubProcess', + isExpanded: false + }; + + // when + var newElement = bpmnReplace.replaceElement(element, newElementData); + + // then + expect(is(newElement, 'bpmn:SubProcess')).to.be.true; + expect(isExpanded(newElement)).to.be.false; + + // and keep boundaryEvent + expect(newElement.attachers.length).to.eql(2); + })); + }); diff --git a/test/spec/features/replace/SubProcess-collapsed.bpmn b/test/spec/features/replace/SubProcess-collapsed.bpmn new file mode 100644 index 00000000..5f80395f --- /dev/null +++ b/test/spec/features/replace/SubProcess-collapsed.bpmn @@ -0,0 +1,37 @@ + + + + + + SequenceFlow_1 + + + + SequenceFlow_1 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file