diff --git a/assets/bpmn-js.css b/assets/bpmn-js.css index 044fec7fea..2c72545200 100644 --- a/assets/bpmn-js.css +++ b/assets/bpmn-js.css @@ -103,3 +103,11 @@ fill: var(--drilldown-fill-color); background-color: var(--drilldown-background-color); } + +.bjs-drilldown-empty { + display: none; +} + +.selected .bjs-drilldown-empty { + display: inherit; +} \ No newline at end of file diff --git a/lib/features/auto-resize/BpmnAutoResizeProvider.js b/lib/features/auto-resize/BpmnAutoResizeProvider.js index d44e9aa6eb..721b08101a 100644 --- a/lib/features/auto-resize/BpmnAutoResizeProvider.js +++ b/lib/features/auto-resize/BpmnAutoResizeProvider.js @@ -33,6 +33,12 @@ BpmnAutoResizeProvider.$inject = [ */ BpmnAutoResizeProvider.prototype.canResize = function(elements, target) { + // do not resize plane elements: + // root elements, collapsed sub-processes + if (is(target.di, 'bpmndi:BPMNPlane')) { + return false; + } + if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) { return false; } diff --git a/lib/features/di-ordering/BpmnDiOrdering.js b/lib/features/di-ordering/BpmnDiOrdering.js index 86e8df9452..5b66cd77da 100644 --- a/lib/features/di-ordering/BpmnDiOrdering.js +++ b/lib/features/di-ordering/BpmnDiOrdering.js @@ -2,6 +2,7 @@ import { getDi } from '../../util/ModelUtil'; import { filter, + forEach, map } from 'min-dash'; @@ -10,27 +11,32 @@ import { selfAndAllChildren } from 'diagram-js/lib/util/Elements'; var HIGH_PRIORITY = 2000; -export default function BpmnDiOrdering(eventBus, canvas) { +export default function BpmnDiOrdering(eventBus, elementRegistry) { eventBus.on('saveXML.start', HIGH_PRIORITY, orderDi); function orderDi() { - var root = canvas.getRootElement(), - rootDi = getDi(root), - elements, - diElements; + var rootElements = elementRegistry.filter(function(element) { + return !element.parent; + }); - elements = selfAndAllChildren([ root ], false); + forEach(rootElements, function(root) { + var rootDi = getDi(root), + elements, + diElements; - // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane - elements = filter(elements, function(element) { - return element !== root && !element.labelTarget; - }); + elements = selfAndAllChildren([ root ], false); - diElements = map(elements, getDi); + // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane + elements = filter(elements, function(element) { + return element !== root && !element.labelTarget; + }); - rootDi.set('planeElement', diElements); + diElements = map(elements, getDi); + + rootDi.set('planeElement', diElements); + }); } } -BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ]; +BpmnDiOrdering.$inject = [ 'eventBus', 'elementRegistry' ]; diff --git a/lib/features/drilldown/DrilldownOverlays.js b/lib/features/drilldown/DrilldownBreadcrumbs.js similarity index 59% rename from lib/features/drilldown/DrilldownOverlays.js rename to lib/features/drilldown/DrilldownBreadcrumbs.js index ccfbe686df..f234ecde6c 100644 --- a/lib/features/drilldown/DrilldownOverlays.js +++ b/lib/features/drilldown/DrilldownBreadcrumbs.js @@ -3,8 +3,6 @@ import { domify, classes } from 'min-dom'; import { escapeHTML } from 'diagram-js/lib/util/EscapeUtil'; import { getBusinessObject, is } from '../../util/ModelUtil'; -var ARROW_DOWN_SVG = ''; - var OPEN_CLASS = 'bjs-breadcrumbs-shown'; /** @@ -15,7 +13,7 @@ var OPEN_CLASS = 'bjs-breadcrumbs-shown'; * @param {overlays} overlays * @param {canvas} canvas */ -export default function DrilldownOverlays(eventBus, elementRegistry, overlays, canvas) { +export default function DrilldownBreadcrumbs(eventBus, elementRegistry, overlays, canvas) { var breadcrumbs = domify(''); var container = canvas.getContainer(); var containerClasses = classes(container); @@ -58,40 +56,9 @@ export default function DrilldownOverlays(eventBus, elementRegistry, overlays, c updateBreadcrumbs(plane); }); - var createOverlay = function(element) { - var html = domify(''); - - html.addEventListener('click', function() { - canvas.setActivePlane(element.id); - }); - - overlays.add(element, { - position: { - bottom: -7, - right: -8 - }, - html: html - }); - }; - - var addOverlays = function(elements) { - elements.forEach(function(element) { - if (is(element, 'bpmn:SubProcess') - && element.collapsed - && canvas.getPlane(element.id)) { - createOverlay(element); - } - }); - }; - - eventBus.on('import.done', function() { - addOverlays(elementRegistry.filter(function(el) { - return is(el, 'bpmn:SubProcess'); - })); - }); } -DrilldownOverlays.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ]; +DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ]; // helpers diff --git a/lib/features/drilldown/DrilldownOverlayBehavior.js b/lib/features/drilldown/DrilldownOverlayBehavior.js new file mode 100644 index 0000000000..fa2ddb4217 --- /dev/null +++ b/lib/features/drilldown/DrilldownOverlayBehavior.js @@ -0,0 +1,132 @@ +import inherits from 'inherits'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +import { is } from '../../util/ModelUtil'; +import { classes, domify } from 'min-dom'; + +var LOW_PRIORITY = 250; +var ARROW_DOWN_SVG = ''; + +var EMPTY_MARKER = 'bjs-drilldown-empty'; + +export default function DrilldownOverlayBehavior( + canvas, eventBus, elementRegistry, overlays +) { + CommandInterceptor.call(this, eventBus); + + this._canvas = canvas; + this._eventBus = eventBus; + this._elementRegistry = elementRegistry; + this._overlays = overlays; + + var self = this; + + this.executed('shape.create', LOW_PRIORITY, function(context) { + var shape = context.shape; + + // Add overlay to the collapsed shape + if (self.canDrillDown(shape)) { + self.addOverlay(shape); + } + + // with the first element added, we remove the overlay marker + // to make the drilldown button permanent + updateDrilldownOverlay(shape); + }, true); + + this.reverted('shape.create', LOW_PRIORITY, function(context) { + updateDrilldownOverlay(context.parent); + }, true); + + this.executed('shape.delete', LOW_PRIORITY, function(context) { + updateDrilldownOverlay(context.oldParent); + }, true); + + this.reverted('shape.delete', LOW_PRIORITY, function(context) { + updateDrilldownOverlay(context.oldParent); + }, true); + + // TODO(marstamm): remove overlays when expanding an element + + + eventBus.on('import.done', function() { + elementRegistry.filter(function(e) { + return self.canDrillDown(e); + }).map(function(el) { + self.addOverlay(el); + }); + }); + + function updateDrilldownOverlay(shape) { + if (!shape) { + return; + } + + var parentPlane = canvas.findPlane(shape); + if (parentPlane) { + self.updateOverlayVisibility(parentPlane.rootElement); + } + } +} + +inherits(DrilldownOverlayBehavior, CommandInterceptor); + +DrilldownOverlayBehavior.prototype.canDrillDown = function(element) { + return is(element, 'bpmn:SubProcess') && this._canvas.getPlane(element.id); +}; + +/** + * Updates visibility of the drilldown overlay. If the plane has no elements, + * the drilldown will be only shown when the element is selected. + * + * @param {djs.model.Shape|djs.model.Root} element collapsed shape or root element + */ +DrilldownOverlayBehavior.prototype.updateOverlayVisibility = function(element) { + var overlays = this._overlays; + + var bo = element.businessObject; + + var overlay = overlays.get({ element: bo.id, type: 'drilldown' })[0]; + + if (!overlay) { + return; + } + + var hasContent = bo && bo.flowElements && bo.flowElements.length; + classes(overlay.html).toggle(EMPTY_MARKER, !hasContent); +}; + +/** + * Attaches a drilldown button to the given element. We assume that the plane has + * the same id as the element. + * + * @param {djs.model.Shape} element collapsed shape + */ +DrilldownOverlayBehavior.prototype.addOverlay = function(element) { + var canvas = this._canvas; + var overlays = this._overlays; + + var button = domify(''); + + button.addEventListener('click', function() { + canvas.setActivePlane(element.id); + }); + + overlays.add(element, 'drilldown', { + position: { + bottom: -7, + right: -8 + }, + html: button + }); + + this.updateOverlayVisibility(element); +}; + + +DrilldownOverlayBehavior.$inject = [ + 'canvas', + 'eventBus', + 'elementRegistry', + 'overlays' +]; \ No newline at end of file diff --git a/lib/features/drilldown/SubprocessCompatibility.js b/lib/features/drilldown/SubprocessCompatibility.js index dd7eaed87c..5aa57b8a68 100644 --- a/lib/features/drilldown/SubprocessCompatibility.js +++ b/lib/features/drilldown/SubprocessCompatibility.js @@ -83,6 +83,15 @@ SubprocessCompatibility.prototype.createNewDiagrams = function(plane) { var newDiagrams = []; + // create new planes for all collapsed subprocesses, even when they are empty + collapsedElements.forEach(function(element) { + if (!self._processToDiagramMap[element.id]) { + var diagram = self.createDiagram(element); + self._processToDiagramMap[element.id] = diagram; + newDiagrams.push(diagram); + } + }); + elementsToMove.forEach(function(element) { var diElement = element.diElement; var parent = element.parent; @@ -98,12 +107,6 @@ SubprocessCompatibility.prototype.createNewDiagrams = function(plane) { } var diagram = self._processToDiagramMap[parent.id]; - if (!diagram) { - diagram = self.createDiagram(parent); - self._processToDiagramMap[parent.id] = diagram; - newDiagrams.push(diagram); - } - self.moveToDiPlane(diElement, diagram.plane); }); diff --git a/lib/features/drilldown/index.js b/lib/features/drilldown/index.js index 51590c5114..328c53c01e 100644 --- a/lib/features/drilldown/index.js +++ b/lib/features/drilldown/index.js @@ -1,14 +1,17 @@ import OverlaysModule from 'diagram-js/lib/features/overlays'; import ChangeSupportModule from 'diagram-js/lib/features/change-support'; +import PlanesModule from 'diagram-js/lib/features/planes'; -import DrilldownOverlays from './DrilldownOverlays'; +import DrilldownBreadcrumbs from './DrilldownBreadcrumbs'; import DrilldownCentering from './DrilldownCentering'; import SubprocessCompatibility from './SubprocessCompatibility'; +import DrilldownOverlayBehavior from './DrilldownOverlayBehavior'; export default { - __depends__: [ OverlaysModule, ChangeSupportModule ], - __init__: [ 'drilldownOverlays', 'drilldownCentering', 'subprocessCompatibility'], - drilldownOverlays: [ 'type', DrilldownOverlays ], + __depends__: [ OverlaysModule, ChangeSupportModule, PlanesModule ], + __init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility'], + drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ], drilldownCentering: [ 'type', DrilldownCentering ], + drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ], subprocessCompatibility: [ 'type', SubprocessCompatibility ] }; \ No newline at end of file diff --git a/lib/features/modeling/behavior/SubProcessPlaneBehavior.js b/lib/features/modeling/behavior/SubProcessPlaneBehavior.js new file mode 100644 index 0000000000..31ffe28879 --- /dev/null +++ b/lib/features/modeling/behavior/SubProcessPlaneBehavior.js @@ -0,0 +1,212 @@ +import inherits from 'inherits'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { isExpanded } from '../../../util/DiUtil'; +import { getBusinessObject, is } from '../../../util/ModelUtil'; + +/** + * Creates diPlanes and canvas planes when collapses subprocesses are created. + * + * @param {Canvas} canvas + * @param {EventBus} eventBus + * @param {Modeling} modeling + * @param {ElementFactory} elementFactory + * @param {BpmnFactory} bpmnFactory + * @param {Bpmnjs} bpmnjs + */ +export default function SubProcessPlaneBehavior( + canvas, eventBus, modeling, + elementFactory, bpmnFactory, bpmnjs) { + + CommandInterceptor.call(this, eventBus); + + this._canvas = canvas; + this._eventBus = eventBus; + this._modeling = modeling; + this._elementFactory = elementFactory; + this._bpmnFactory = bpmnFactory; + this._bpmnjs = bpmnjs; + + var self = this; + + function isCollapsedSubProcess(element) { + return is(element, 'bpmn:SubProcess') && !isExpanded(element); + } + + function createPlane(context) { + var shape = context.shape, + plane = context.targetPlane, + rootElement = plane && plane.rootElement; + + var businessObject = getBusinessObject(shape); + + rootElement = self._addDiagram(rootElement || businessObject); + context.targetPlane = canvas.createPlane( + plane || + { name: businessObject.id, rootElement: rootElement }); + } + + function removePlane(context) { + var shape = context.shape; + + var businessObject = getBusinessObject(shape); + + self._removeDiagram(businessObject); + + context.targetPlane = canvas.removePlane(businessObject.id); + } + + + // add plane elements for newly created sub-processes + // this ensures we can actually drill down into the element + this.executed('shape.create', function(context) { + var shape = context.shape; + + if (!isCollapsedSubProcess(shape)) { + return; + } + + createPlane(context); + }, true); + + + this.reverted('shape.create', 500, function(context) { + var shape = context.shape; + + if (!isCollapsedSubProcess(shape)) { + return; + } + + removePlane(context); + }, true); + + + this.executed('element.updateProperties', function(context) { + var shape = context.element; + + if (!isCollapsedSubProcess(shape)) { + return; + } + + var properties = context.properties; + var oldProperties = context.oldProperties; + + var oldId = oldProperties.id, + newId = properties.id; + + if (oldId === newId) { + return; + } + + canvas.renamePlane(oldId, newId); + }, true); + + + this.reverted('element.updateProperties', function(context) { + var shape = context.element; + var properties = context.properties; + var oldProperties = context.oldProperties; + + var oldId = oldProperties.id, + newId = properties.id; + + + if (!isCollapsedSubProcess(shape)) { + return; + } + + if (oldId === newId) { + return; + } + + canvas.renamePlane(newId, oldId); + }, true); + +} + +inherits(SubProcessPlaneBehavior, CommandInterceptor); + + +/** +* Adds a given diagram to the definitions and returns a . +* +* @param {Object} planeElement +*/ +SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) { + var bpmnjs = this._bpmnjs; + var diagrams = bpmnjs.getDefinitions().diagrams; + + if (!planeElement.businessObject) { + planeElement = this._createDiagram(planeElement); + } + + diagrams.push(planeElement.di.$parent); + + return planeElement; +}; + +/** +* Creates a new plane element for the given sub process. +* +* @param {Object} bpmnElement +* +* @return {Object} new diagram element +*/ +SubProcessPlaneBehavior.prototype._createDiagram = function(bpmnElement) { + var bpmnFactory = this._bpmnFactory; + var elementFactory = this._elementFactory; + + var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', { + bpmnElement: bpmnElement + }); + var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', { + plane: diPlane + }); + diPlane.$parent = diDiagram; + + // add a virtual element (not being drawn), + // a copy cat of our BpmnImporter code + var planeElement = elementFactory.createRoot({ + id: bpmnElement.id + '_plane', + type: bpmnElement.$type, + di: diPlane, + businessObject: bpmnElement, + collapsed: true + }); + + return planeElement; +}; + +/** + * Removes the diagram for a given root element + * + * @param {Object} rootElement + * @returns {Object} removed bpmndi:BPMNDiagram + */ +SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) { + var bpmnjs = this._bpmnjs; + + var diagrams = bpmnjs.getDefinitions().diagrams; + + var removedDiagram = diagrams.find(function(diagram) { + return diagram.plane.bpmnElement.id === rootElement.id; + }); + + diagrams.splice(diagrams.indexOf(removedDiagram), 1); + + return removedDiagram; +}; + + +SubProcessPlaneBehavior.$inject = [ + 'canvas', + 'eventBus', + 'modeling', + 'elementFactory', + 'bpmnFactory', + 'bpmnjs' +]; + + + diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 6c99ee6f71..045613d3e1 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -28,6 +28,7 @@ import ResizeLaneBehavior from './ResizeLaneBehavior'; import RemoveElementBehavior from './RemoveElementBehavior'; import SpaceToolBehavior from './SpaceToolBehavior'; import SubProcessStartEventBehavior from './SubProcessStartEventBehavior'; +import SubProcessPlaneBehavior from './SubProcessPlaneBehavior'; import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour'; import UnclaimIdBehavior from './UnclaimIdBehavior'; import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior'; @@ -66,6 +67,7 @@ export default { 'toggleElementCollapseBehaviour', 'spaceToolBehavior', 'subProcessStartEventBehavior', + 'subProcessPlaneBehavior', 'unclaimIdBehavior', 'unsetDefaultFlowBehavior', 'updateFlowNodeRefsBehavior' @@ -101,6 +103,7 @@ export default { toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ], spaceToolBehavior: [ 'type', SpaceToolBehavior ], subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ], + subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ], unclaimIdBehavior: [ 'type', UnclaimIdBehavior ], updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ], unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ] diff --git a/lib/import/BpmnImporter.js b/lib/import/BpmnImporter.js index 78bdc96035..b0b38eb913 100644 --- a/lib/import/BpmnImporter.js +++ b/lib/import/BpmnImporter.js @@ -26,6 +26,13 @@ import { } from './Util'; +/** + * @param {ModdleElement} semantic + * @param {ModdleElement} di + * @param {Object} [attrs=null] + * + * @return {Object} + */ function elementData(semantic, di, attrs) { return assign({ id: semantic.id, @@ -105,13 +112,12 @@ BpmnImporter.prototype.add = function(semantic, di, parentElement) { // invisible root element (process, subprocess or collaboration) if (is(di, 'bpmndi:BPMNPlane')) { - // add a virtual element (not being drawn) - element = this._elementFactory.createRoot(elementData(semantic, di)); + var attrs = is(semantic, 'bpmn:SubProcess') + ? { id: semantic.id + '_plane' } + : {}; - // for subprocesses, the id is already defined on the collapsed shape - if (is(semantic, 'bpmn:SubProcess')) { - element.id = element.id + '_plane'; - } + // add a virtual element (not being drawn) + element = this._elementFactory.createRoot(elementData(semantic, di, attrs)); this._canvas.createPlane(semantic.id, element); } diff --git a/package-lock.json b/package-lock.json index afa4dfac90..62c4dc6e59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3333,9 +3333,8 @@ "dev": true }, "diagram-js": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-7.8.1.tgz", - "integrity": "sha512-Ziy5vTmB8V/kxuhgxQXdnNxSYnqlWxFrBih37MOOglDzyQ5mBIA8tFNssp/ncHpZmhTGC8sb54lYknovzyrrzg==", + "version": "github:bpmn-io/diagram-js#ae86c626e7ec8ab021b5cf40f3e1a24e66365566", + "from": "github:bpmn-io/diagram-js#create-planes-from-descriptor", "requires": { "css.escape": "^1.5.1", "didi": "^5.2.1", diff --git a/package.json b/package.json index 460bf35ad3..3e94661550 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "dependencies": { "bpmn-moddle": "^7.1.2", "css.escape": "^1.5.1", - "diagram-js": "^7.8.1", + "diagram-js": "github:bpmn-io/diagram-js#create-planes-from-descriptor", "diagram-js-direct-editing": "^1.6.3", "ids": "^1.0.0", "inherits": "^2.0.4", diff --git a/test/fixtures/bpmn/collapsed-sub-process-legacy.bpmn b/test/fixtures/bpmn/collapsed-sub-process-legacy.bpmn new file mode 100644 index 0000000000..3641909928 --- /dev/null +++ b/test/fixtures/bpmn/collapsed-sub-process-legacy.bpmn @@ -0,0 +1,51 @@ + + + + + + + Flow_0obnxbt + + + + Flow_1d6ajf7 + + + Flow_0obnxbt + Flow_1d6ajf7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/ModelerSpec.js b/test/spec/ModelerSpec.js index c023e04630..d016f9a2c8 100644 --- a/test/spec/ModelerSpec.js +++ b/test/spec/ModelerSpec.js @@ -710,8 +710,7 @@ describe('Modeler', function() { describe('drill down', function() { - it('should allow drill down into collapsed sub-process', function() { - var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn'); + function verifyDrilldown(xml) { return createModeler(xml).then(function() { var drilldown = container.querySelector('.bjs-drilldown'); @@ -730,6 +729,19 @@ describe('Modeler', function() { expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.true; }); + } + + it('should allow drill down into collapsed sub-process', function() { + var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn'); + + return verifyDrilldown(xml); + }); + + + it('should allow drill down into legacy collapsed sub-process', function() { + var xml = require('../fixtures/bpmn/collapsed-sub-process-legacy.bpmn'); + + return verifyDrilldown(xml); }); }); diff --git a/test/spec/ViewerSpec.js b/test/spec/ViewerSpec.js index 32f87c5d7a..d41c867b15 100644 --- a/test/spec/ViewerSpec.js +++ b/test/spec/ViewerSpec.js @@ -372,9 +372,7 @@ describe('Viewer', function() { describe('drill down', function() { - it('should allow drill down into collapsed sub-process', function() { - - var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn'); + function verifyDrilldown(xml) { return createViewer(container, Viewer, xml).then(function() { var drilldown = container.querySelector('.bjs-drilldown'); @@ -393,6 +391,19 @@ describe('Viewer', function() { expect(djsContainer.classList.contains('bjs-breadcrumbs-shown')).to.be.true; }); + } + + it('should allow drill down into collapsed sub-process', function() { + var xml = require('../fixtures/bpmn/collapsed-sub-process.bpmn'); + + return verifyDrilldown(xml); + }); + + + it('should allow drill down into legacy collapsed sub-process', function() { + var xml = require('../fixtures/bpmn/collapsed-sub-process-legacy.bpmn'); + + return verifyDrilldown(xml); }); }); diff --git a/test/spec/features/drilldown/DrilldownOverlayBehaviorSpec.bpmn b/test/spec/features/drilldown/DrilldownOverlayBehaviorSpec.bpmn new file mode 100644 index 0000000000..4164caee63 --- /dev/null +++ b/test/spec/features/drilldown/DrilldownOverlayBehaviorSpec.bpmn @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/spec/features/drilldown/DrilldownOverlaysBehaviorSpec.js b/test/spec/features/drilldown/DrilldownOverlaysBehaviorSpec.js new file mode 100644 index 0000000000..3f39ea8771 --- /dev/null +++ b/test/spec/features/drilldown/DrilldownOverlaysBehaviorSpec.js @@ -0,0 +1,265 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; +import replaceModule from 'lib/features/replace'; +import drilldownModule from 'lib/features/drilldown'; +import { classes } from 'min-dom'; + + +describe('features/modeling/behavior - subprocess planes', function() { + + var diagramXML = require('./DrilldownOverlayBehaviorSpec.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: [ + coreModule, + modelingModule, + replaceModule, + drilldownModule + ] + })); + + + describe('create new drilldowns', function() { + + it('should create drilldown for new process', + inject(function(elementFactory, modeling, canvas, overlays) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // when + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // then + var elementOverlays = overlays.get({ element: subProcess }); + expect(elementOverlays).to.not.be.empty; + + }) + ); + + + it('should not create drilldown for expanded subprocess', + inject(function(elementFactory, modeling, canvas, overlays) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: true + }); + + // when + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // then + var elementOverlays = overlays.get({ element: subProcess }); + expect(elementOverlays).to.be.empty; + }) + ); + + + it('should undo', + inject(function(elementFactory, modeling, commandStack, canvas, overlays) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // when + commandStack.undo(); + + // then + var elementOverlays = overlays.get({ element: subProcess }); + expect(elementOverlays).to.be.empty; + }) + ); + + + it('should redo', + inject(function(elementFactory, modeling, commandStack, canvas, overlays) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // when + commandStack.undo(); + commandStack.redo(); + + // then + var elementOverlays = overlays.get({ element: subProcess }); + expect(elementOverlays).to.not.be.empty; + }) + ); + + }); + + + describe('overlay visibility', function() { + + describe('empty subprocess', function() { + + it('should hide drilldown', inject(function(elementRegistry, overlays) { + + // given + var subProcess = elementRegistry.get('Subprocess_empty'); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true; + })); + + + it('should show when content is added', + inject(function(elementRegistry, overlays, elementFactory, modeling, canvas) { + + // given + var subProcess = elementRegistry.get('Subprocess_empty'); + var task = elementFactory.createShape({ type: 'bpmn:Task' }); + var planeRoot = canvas.getPlane('Subprocess_empty').rootElement; + + // when + modeling.createShape(task, { x: 300, y: 300 }, planeRoot); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false; + }) + ); + + + it('should undo', + inject(function(elementRegistry, overlays, elementFactory, + modeling, canvas, commandStack) { + + // given + var subProcess = elementRegistry.get('Subprocess_empty'); + var task = elementFactory.createShape({ type: 'bpmn:Task' }); + var planeRoot = canvas.getPlane('Subprocess_empty').rootElement; + modeling.createShape(task, { x: 300, y: 300 }, planeRoot); + + // when + commandStack.undo(); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true; + }) + ); + + + it('should redo', + inject(function(elementRegistry, overlays, elementFactory, + modeling, canvas, commandStack) { + + // given + var subProcess = elementRegistry.get('Subprocess_empty'); + var task = elementFactory.createShape({ type: 'bpmn:Task' }); + var planeRoot = canvas.getPlane('Subprocess_empty').rootElement; + modeling.createShape(task, { x: 300, y: 300 }, planeRoot); + + // when + commandStack.undo(); + commandStack.redo(); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false; + }) + ); + + }); + + + describe('subprocess with content', function() { + + it('should show drilldown', inject(function(elementRegistry, overlays) { + + // given + var subProcess = elementRegistry.get('Subprocess_with_content'); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false; + })); + + + it('should hide when content is removed', + inject(function(elementRegistry, overlays, modeling) { + + // given + var subProcess = elementRegistry.get('Subprocess_with_content'); + var startEvent = elementRegistry.get('StartEvent_embedded'); + + // when + modeling.removeShape(startEvent); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true; + }) + ); + + + it('should undo', + inject(function(elementRegistry, overlays, modeling, commandStack) { + + // given + var subProcess = elementRegistry.get('Subprocess_with_content'); + var startEvent = elementRegistry.get('StartEvent_embedded'); + modeling.removeShape(startEvent); + + // when + commandStack.undo(); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.false; + }) + ); + + + it('should redo', + inject(function(elementRegistry, overlays, modeling, commandStack) { + + // given + var subProcess = elementRegistry.get('Subprocess_with_content'); + var startEvent = elementRegistry.get('StartEvent_embedded'); + modeling.removeShape(startEvent); + + // when + commandStack.undo(); + commandStack.redo(); + + // then + var overlay = overlays.get({ element: subProcess })[0]; + + expect(classes(overlay.html).contains('bjs-drilldown-empty')).to.be.true; + }) + ); + + }); + + }); + +}); diff --git a/test/spec/features/drilldown/DrilldownSpec.js b/test/spec/features/drilldown/DrilldownSpec.js index fe9b11a846..b1d95144ac 100644 --- a/test/spec/features/drilldown/DrilldownSpec.js +++ b/test/spec/features/drilldown/DrilldownSpec.js @@ -195,6 +195,16 @@ describe('features - drilldown', function() { expect(startEvent.y).to.equal(160); })); + + it('should create new planes for empty processes', inject(function(canvas) { + + // when + var emptyPlane = canvas.getPlane('emptyProcess'); + + // then + expect(emptyPlane).to.exist; + })); + }); }); diff --git a/test/spec/features/drilldown/legacy-subprocesses.bpmn b/test/spec/features/drilldown/legacy-subprocesses.bpmn index 4626c1f769..777823e07b 100644 --- a/test/spec/features/drilldown/legacy-subprocesses.bpmn +++ b/test/spec/features/drilldown/legacy-subprocesses.bpmn @@ -17,6 +17,7 @@ + @@ -37,15 +38,18 @@ + + + - - + + - + \ No newline at end of file diff --git a/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn b/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn new file mode 100644 index 0000000000..25d99d994b --- /dev/null +++ b/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js b/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js new file mode 100644 index 0000000000..234e5253be --- /dev/null +++ b/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js @@ -0,0 +1,199 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; +import replaceModule from 'lib/features/replace'; + +describe('features/modeling/behavior - subprocess planes', function() { + + var diagramXML = require('./SubProcessBehavior.planes.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: [ + coreModule, + modelingModule, + replaceModule + ] + })); + + + describe('create', function() { + + it('should create new diagram for collapsed subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // when + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(2); + expect(canvas.getPlane(subProcess.id)).to.exist; + })); + + + it('should not create new plane for expanded subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: true + }); + + // when + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(1); + expect(canvas.getPlane(subProcess.id)).to.not.exist; + })); + + + it('should undo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + + // when + commandStack.undo(); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(1); + expect(canvas.getPlane(subProcess.id)).to.not.exist; + })); + + + it('should redo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) { + + // given + var subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: false + }); + modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement()); + var plane = canvas.getPlane(subProcess.id); + + // when + commandStack.undo(); + commandStack.redo(); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(2); + expect(canvas.getPlane(subProcess.id)).to.exist; + expect(canvas.getPlane(subProcess.id)).to.equal(plane); + })); + + }); + + + describe('replace', function() { + + describe('task -> collapsed subprocess', function() { + + it('should add new diagram for collapsed subprocess', inject( + function(elementRegistry, bpmnReplace, bpmnjs, canvas) { + + // given + var task = elementRegistry.get('Task_1'), + collapsedSubProcess; + + // when + collapsedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(2); + expect(canvas.getPlane(collapsedSubProcess.id)).to.exist; + } + )); + + + it('should undo', inject( + function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) { + + // given + var task = elementRegistry.get('Task_1'), + collapsedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // when + commandStack.undo(); + + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(1); + expect(canvas.getPlane(collapsedSubProcess.id)).to.not.exist; + } + )); + + + it('should redo', inject( + function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) { + + // given + var task = elementRegistry.get('Task_1'), + collapsedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // when + commandStack.undo(); + commandStack.redo(); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(2); + expect(canvas.getPlane(collapsedSubProcess.id)).to.exist; + } + )); + }); + + describe('task -> expanded subprocess', function() { + + it('should not add new diagram for collapsed subprocess', inject( + function(elementRegistry, bpmnReplace, bpmnjs, canvas) { + + // given + var task = elementRegistry.get('Task_1'), + collapsedSubProcess; + + // when + collapsedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: true + }); + + // then + var diagrams = bpmnjs.getDefinitions().diagrams; + expect(diagrams.length).to.equal(1); + expect(canvas.getPlane(collapsedSubProcess.id)).to.not.exist; + } + )); + + }); + + }); + +}); diff --git a/test/spec/features/ordering/BpmnDiOrderingSpec.js b/test/spec/features/ordering/BpmnDiOrderingSpec.js index 665cdfb021..450da69d59 100644 --- a/test/spec/features/ordering/BpmnDiOrderingSpec.js +++ b/test/spec/features/ordering/BpmnDiOrderingSpec.js @@ -109,6 +109,33 @@ describe('features/modeling - di ordering', function() { task1.id, ]); }); + + it('should order subprocess planes', function() { + + // given + var canvas = getBpmnJS().get('canvas'), + root; + + // when + var subProcess = add( + { type: 'bpmn:SubProcess', isExpanded: false, width: 300, height: 200 }, { x: 300, y: 200 } + ); + + var participant = add({ type: 'bpmn:Participant', width: 500, height: 300 }, { x: 300, y: 200 }), + task1 = add({ type: 'bpmn:Task' }, { x: 250, y: 200 }, subProcess.id + '_plane'); + + root = canvas.getRootElement(); + + // then + // subProcess id exists twice: once as collapsed shape and once as plane element + return expectDiOrder([ + root.id, + participant.id, + subProcess.id, + subProcess.id, + task1.id, + ]); + }); }); diff --git a/test/spec/features/replace/BpmnReplace.collapsedSubProcess.bpmn b/test/spec/features/replace/BpmnReplace.collapsedSubProcess.bpmn new file mode 100644 index 0000000000..7c9d08aee5 --- /dev/null +++ b/test/spec/features/replace/BpmnReplace.collapsedSubProcess.bpmn @@ -0,0 +1,377 @@ + + + + + + sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300 + + + sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300 + sid-F06605E1-AEC1-4B39-8843-4AD3F547B557 + sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500 + + + sid-F06605E1-AEC1-4B39-8843-4AD3F547B557 + sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05 + + sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657 + + + sid-EB275CF2-5EF1-44FA-B41B-71EB37CC2657 + sid-FB543319-8DFB-4445-AAA3-720137FB230B + + + + + + + sid-FB543319-8DFB-4445-AAA3-720137FB230B + sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0 + + sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC + + + sid-472B540C-A0CD-46F4-9640-DF692EC1BFFC + sid-910420B0-D11B-4F9D-B285-703D8AC0BA90 + + sid-A7460113-CB75-491D-817B-5E1A8C606B8C + + + sid-A7460113-CB75-491D-817B-5E1A8C606B8C + sid-01982395-64E8-43EF-A6D3-CDD276C312AA + + + sid-01982395-64E8-43EF-A6D3-CDD276C312AA + + + + + + + + sid-910420B0-D11B-4F9D-B285-703D8AC0BA90 + + + + + + + + sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0 + + + + + + + + + + sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500 + sid-5B23450F-AF5E-4519-B134-32107776BD44 + + sid-E71F5783-AFE7-44ED-8A9C-378C95087448 + + + sid-E71F5783-AFE7-44ED-8A9C-378C95087448 + sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16 + + + sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16 + sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519 + + sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC + + + sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC + sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E + + + sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E + + + + + + + + sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519 + + + + + + + + + + + + + + + + sid-5B23450F-AF5E-4519-B134-32107776BD44 + sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05 + sid-F7DA1903-6A1A-4858-AF4B-286A968C957F + + + sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4 + + + + sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4 + + + sid-F7DA1903-6A1A-4858-AF4B-286A968C957F + sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3 + + + sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index e4c1b345ec..1df88c2e83 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -311,6 +311,81 @@ describe('features/replace - bpmn replace', function() { }); + describe('should replace in sub-process (collapsed)', function() { + + var diagramXML = require('./BpmnReplace.collapsedSubProcess.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules, + moddleExtensions: { + camunda: camundaPackage + } + })); + + + beforeEach(inject(function(canvas) { + canvas.setActivePlane('SubProcess_Collapsed'); + })); + + + it('task', inject(function(elementRegistry, bpmnReplace) { + + // given + var task = elementRegistry.get('UserTask'); + var newElementData = { + type: 'bpmn:ServiceTask' + }; + + // when + var newElement = bpmnReplace.replaceElement(task, newElementData); + + // then + var businessObject = newElement.businessObject; + + expect(newElement).to.exist; + expect(is(businessObject, 'bpmn:ServiceTask')).to.be.true; + })); + + + it('task with collapsed sub-process', inject(function(elementRegistry, bpmnReplace) { + + // given + var task = elementRegistry.get('UserTask'); + var newElementData = { + type: 'bpmn:SubProcess' + }; + + // when + var newElement = bpmnReplace.replaceElement(task, newElementData); + + // then + var businessObject = newElement.businessObject; + + expect(newElement).to.exist; + expect(is(businessObject, 'bpmn:SubProcess')).to.be.true; + })); + + + it('collapsed sub-process with task', inject(function(elementRegistry, bpmnReplace) { + + // given + var task = elementRegistry.get('NestedCollapsed_SubProcess'); + var newElementData = { + type: 'bpmn:Task' + }; + + // when + var newElement = bpmnReplace.replaceElement(task, newElementData); + + // then + var businessObject = newElement.businessObject; + + expect(newElement).to.exist; + expect(is(businessObject, 'bpmn:Task')).to.be.true; + })); + }); + + describe('should replace in collaboration', function() { var diagramXML = require('./BpmnReplace.collaboration.bpmn');