diff --git a/lib/features/modeling/behavior/ModelingFeedback.js b/lib/features/modeling/behavior/ModelingFeedback.js index 762952a6..130390a7 100644 --- a/lib/features/modeling/behavior/ModelingFeedback.js +++ b/lib/features/modeling/behavior/ModelingFeedback.js @@ -2,18 +2,19 @@ var is = require('../../../util/ModelUtil').is; -var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants'; +var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants', + PROCESS_ERR_MSG = 'participants cannot be pasted onto a non-empty process diagram'; function ModelingFeedback(eventBus, tooltips, translate) { - function showError(position, message) { + function showError(position, message, timeout) { tooltips.add({ position: { x: position.x + 5, y: position.y + 5 }, type: 'error', - timeout: 2000, + timeout: timeout || 2000, html: '
' + message + '
' }); } @@ -36,6 +37,10 @@ function ModelingFeedback(eventBus, tooltips, translate) { if (is(target, 'bpmn:Collaboration')) { showError(position, translate(COLLAB_ERR_MSG)); } + + if (is(target, 'bpmn:Process')) { + showError(position, translate(PROCESS_ERR_MSG), 3000); + } }); } diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js index bc8fd527..f57dc239 100644 --- a/lib/features/rules/BpmnRules.js +++ b/lib/features/rules/BpmnRules.js @@ -427,7 +427,8 @@ function canDrop(element, target, position) { } function canPaste(tree, target) { - var topLevel = tree[0]; + var topLevel = tree[0], + participants; if (is(target, 'bpmn:Collaboration')) { return every(topLevel, function(e) { @@ -435,6 +436,14 @@ function canPaste(tree, target) { }); } + if (is(target, 'bpmn:Process')) { + participants = any(topLevel, function(e) { + return e.type === 'bpmn:Participant'; + }); + + return !(participants && target.children.length > 0); + } + // disallow to create elements on collapsed pools if (is(target, 'bpmn:Participant') && !isExpanded(target)) { return false; diff --git a/test/spec/features/copy-paste/BpmnCopyPasteSpec.js b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js index 26f6ad5c..94b9ea6a 100644 --- a/test/spec/features/copy-paste/BpmnCopyPasteSpec.js +++ b/test/spec/features/copy-paste/BpmnCopyPasteSpec.js @@ -48,7 +48,7 @@ describe('features/copy-paste', function() { collaborationMultipleXML = require('../../../fixtures/bpmn/features/copy-paste/collaboration-multiple.bpmn'); - var integrationTest = function(ids) { + function integrationTest(ids) { return function(canvas, elementRegistry, modeling, copyPaste, commandStack) { // given var shapes = elementRegistry.getAll(), @@ -127,280 +127,320 @@ describe('features/copy-paste', function() { }; }; - describe('basic diagram', function() { - beforeEach(bootstrapModeler(basicXML, { modules: testModules })); + describe('basic diagram', function() { - describe('copy', function() { + beforeEach(bootstrapModeler(basicXML, { modules: testModules })); - it('selected elements', inject(function(elementRegistry, copyPaste) { + describe('copy', function() { - // given - var subProcess, startEvent, boundaryEvent, textAnnotation, conditionalFlow, defaultFlow, - tree; + it('selected elements', inject(function(elementRegistry, copyPaste) { - // when - copyPaste.copy(elementRegistry.get('SubProcess_1kd6ist')); + // given + var subProcess, startEvent, boundaryEvent, textAnnotation, conditionalFlow, defaultFlow, + tree; - tree = new DescriptorTree(copyPaste._tree); + // when + copyPaste.copy(elementRegistry.get('SubProcess_1kd6ist')); - startEvent = tree.getElement('StartEvent_1'); - boundaryEvent = tree.getElement('BoundaryEvent_1c94bi9'); - subProcess = tree.getElement('SubProcess_1kd6ist'); - textAnnotation = tree.getElement('TextAnnotation_0h1hhgg'); - conditionalFlow = tree.getElement('SequenceFlow_07vo2r8'); - defaultFlow = tree.getElement('Task_1fo63a7'); + tree = new DescriptorTree(copyPaste._tree); - // then - expect(tree.getLength()).to.equal(3); + startEvent = tree.getElement('StartEvent_1'); + boundaryEvent = tree.getElement('BoundaryEvent_1c94bi9'); + subProcess = tree.getElement('SubProcess_1kd6ist'); + textAnnotation = tree.getElement('TextAnnotation_0h1hhgg'); + conditionalFlow = tree.getElement('SequenceFlow_07vo2r8'); + defaultFlow = tree.getElement('Task_1fo63a7'); - expect(tree.getDepthLength(0)).to.equal(1); - expect(tree.getDepthLength(1)).to.equal(3); - expect(tree.getDepthLength(2)).to.equal(15); + // then + expect(tree.getLength()).to.equal(3); - expect(subProcess.isExpanded).to.be.true; - expect(startEvent.name).to.equal('hello'); - expect(textAnnotation.text).to.equal('foo'); - expect(boundaryEvent.eventDefinitions).to.contain('bpmn:TimerEventDefinition'); - })); + expect(tree.getDepthLength(0)).to.equal(1); + expect(tree.getDepthLength(1)).to.equal(3); + expect(tree.getDepthLength(2)).to.equal(15); - }); - - describe('integration', function() { - - it('should retain label\'s relative position', - inject(function(modeling, copyPaste, canvas, elementRegistry) { - // given - var startEvent = elementRegistry.get('StartEvent_1'), - startEventLabel = startEvent.label, - seqFlow = elementRegistry.get('SequenceFlow_1rtr33r'), - seqFlowLabel = seqFlow.label, - task = elementRegistry.get('Task_1fo63a7'), - rootElement = canvas.getRootElement(), - newStrtEvt, newSeqFlow; - - // when - copyPaste.copy([ startEvent, task ]); - - copyPaste.paste({ - element: rootElement, - point: { - x: 1100, - y: 250 - } - }); - - newStrtEvt = elementRegistry.filter(function(element) { - return element.parent === rootElement && element.type === 'bpmn:StartEvent'; - })[0]; - - newSeqFlow = elementRegistry.filter(function(element) { - return element.parent === rootElement && element.type === 'bpmn:SequenceFlow'; - })[0]; - - // then - expect(newStrtEvt.label.x - newStrtEvt.x).to.equal(startEventLabel.x - startEvent.x); - expect(newStrtEvt.label.y - newStrtEvt.y).to.equal(startEventLabel.y - startEvent.y); - - expect(newSeqFlow.label.x - newSeqFlow.waypoints[0].x).to.equal(seqFlowLabel.x - seqFlow.waypoints[0].x); - expect(newSeqFlow.label.y - newSeqFlow.waypoints[0].y).to.equal(seqFlowLabel.y - seqFlow.waypoints[0].y); - })); - - - it('should retain default & conditional flow property', - inject(function(elementRegistry, copyPaste, canvas, modeling) { - // given - var subProcess = elementRegistry.get('SubProcess_1kd6ist'), - rootElement = canvas.getRootElement(), - task, defaultFlow, conditionalFlow; - - // when - copyPaste.copy(subProcess); - - modeling.removeElements([ subProcess ]); - - copyPaste.paste({ - element: rootElement, - point: { - x: 1100, - y: 250 - } - }); - - task = elementRegistry.filter(function(element) { - return element.type === 'bpmn:Task'; - })[0]; - - defaultFlow = elementRegistry.filter(function(element) { - return !!(element.type === 'bpmn:SequenceFlow' && task.businessObject.default.id === element.id); - })[0]; - - conditionalFlow = elementRegistry.filter(function(element) { - return !!(element.type === 'bpmn:SequenceFlow' && element.businessObject.conditionExpression); - })[0]; - - expect(defaultFlow).to.exist; - expect(conditionalFlow).to.exist; - })); - - - it('should retain loop characteristics', - inject(function(elementRegistry, copyPaste, canvas, modeling) { - // given - var subProcess = elementRegistry.get('SubProcess_0gev7mx'), - rootElement = canvas.getRootElement(), - loopCharacteristics; - - // when - copyPaste.copy(subProcess); - - modeling.removeElements([ subProcess ]); - - copyPaste.paste({ - element: rootElement, - point: { - x: 1100, - y: 250 - } - }); - - subProcess = elementRegistry.filter(function(element) { - return !!(element.id !== 'SubProcess_1kd6ist' && element.type === 'bpmn:SubProcess'); - })[0]; - - loopCharacteristics = subProcess.businessObject.loopCharacteristics; - - expect(loopCharacteristics.$type).to.equal('bpmn:MultiInstanceLoopCharacteristics'); - expect(loopCharacteristics.isSequential).to.be.true; - })); - - - it('selected elements', inject(integrationTest([ 'SubProcess_1kd6ist' ]))); - - }); - - describe('rules', function() { - - it('disallow individual boundary events copying', inject(function(copyPaste, elementRegistry, canvas) { - - var boundaryEventA = elementRegistry.get('BoundaryEvent_1404oxd'), - boundaryEventB = elementRegistry.get('BoundaryEvent_1c94bi9'), - tree; - - // when - copyPaste.copy([ boundaryEventA, boundaryEventB ]); - - tree = new DescriptorTree(copyPaste._tree); - - expect(tree.getLength()).to.equal(0); - })); - }); + expect(subProcess.isExpanded).to.be.true; + expect(startEvent.name).to.equal('hello'); + expect(textAnnotation.text).to.equal('foo'); + expect(boundaryEvent.eventDefinitions).to.contain('bpmn:TimerEventDefinition'); + })); }); - describe('basic collaboration', function() { - beforeEach(bootstrapModeler(collaborationXML, { modules: testModules })); + describe('integration', function() { - describe('integration', function() { + it('should retain label\'s relative position', + inject(function(modeling, copyPaste, canvas, elementRegistry) { + // given + var startEvent = elementRegistry.get('StartEvent_1'), + startEventLabel = startEvent.label, + seqFlow = elementRegistry.get('SequenceFlow_1rtr33r'), + seqFlowLabel = seqFlow.label, + task = elementRegistry.get('Task_1fo63a7'), + rootElement = canvas.getRootElement(), + newStrtEvt, newSeqFlow; - it('participant with including lanes + elements', inject(integrationTest([ 'Participant_0uu1rvj' ]))); + // when + copyPaste.copy([ startEvent, task ]); - it('collapsed pool', inject(integrationTest([ 'Participant_145muai' ]))); + copyPaste.paste({ + element: rootElement, + point: { + x: 1100, + y: 250 + } + }); - }); + newStrtEvt = elementRegistry.filter(function(element) { + return element.parent === rootElement && element.type === 'bpmn:StartEvent'; + })[0]; - describe('rules', function () { + newSeqFlow = elementRegistry.filter(function(element) { + return element.parent === rootElement && element.type === 'bpmn:SequenceFlow'; + })[0]; - it('disallow individual lanes copying', inject(function(copyPaste, elementRegistry, canvas) { + // then + expect(newStrtEvt.label.x - newStrtEvt.x).to.equal(startEventLabel.x - startEvent.x); + expect(newStrtEvt.label.y - newStrtEvt.y).to.equal(startEventLabel.y - startEvent.y); - var laneA = elementRegistry.get('Lane_13h648l'), - laneB = elementRegistry.get('Lane_1gl63sa'), - tree; - - // when - copyPaste.copy([ laneA, laneB ]); - - tree = new DescriptorTree(copyPaste._tree); - - expect(tree.getLength()).to.equal(0); - })); + expect(newSeqFlow.label.x - newSeqFlow.waypoints[0].x).to.equal(seqFlowLabel.x - seqFlow.waypoints[0].x); + expect(newSeqFlow.label.y - newSeqFlow.waypoints[0].y).to.equal(seqFlowLabel.y - seqFlow.waypoints[0].y); + })); - it('pasting on a collaboration is disallowed when NOT every element is a Participant', - inject(function(copyPaste, elementRegistry, canvas, tooltips, eventBus) { - var task = elementRegistry.get('Task_13xbgyg'), - participant = elementRegistry.get('Participant_145muai'), - collaboration = canvas.getRootElement(), - tree; + it('should retain default & conditional flow property', + inject(function(elementRegistry, copyPaste, canvas, modeling) { + // given + var subProcess = elementRegistry.get('SubProcess_1kd6ist'), + rootElement = canvas.getRootElement(), + task, defaultFlow, conditionalFlow; - var pasteRejected = sinon.spy(function() {}); + // when + copyPaste.copy(subProcess); - // when - copyPaste.copy([ task, participant ]); + modeling.removeElements([ subProcess ]); - tree = new DescriptorTree(copyPaste._tree); + copyPaste.paste({ + element: rootElement, + point: { + x: 1100, + y: 250 + } + }); - // then - expect(tree.getDepthLength(0)).to.equal(2); + task = elementRegistry.filter(function(element) { + return element.type === 'bpmn:Task'; + })[0]; - // when - eventBus.on('elements.paste.rejected', pasteRejected); + defaultFlow = elementRegistry.filter(function(element) { + return !!(element.type === 'bpmn:SequenceFlow' && task.businessObject.default.id === element.id); + })[0]; - copyPaste.paste({ - element: collaboration, - point: { - x: 1000, - y: 1000 - } - }); + conditionalFlow = elementRegistry.filter(function(element) { + return !!(element.type === 'bpmn:SequenceFlow' && element.businessObject.conditionExpression); + })[0]; - expect(pasteRejected).to.have.been.called; - })); + expect(defaultFlow).to.exist; + expect(conditionalFlow).to.exist; + })); - }); + + it('should retain loop characteristics', + inject(function(elementRegistry, copyPaste, canvas, modeling) { + // given + var subProcess = elementRegistry.get('SubProcess_0gev7mx'), + rootElement = canvas.getRootElement(), + loopCharacteristics; + + // when + copyPaste.copy(subProcess); + + modeling.removeElements([ subProcess ]); + + copyPaste.paste({ + element: rootElement, + point: { + x: 1100, + y: 250 + } + }); + + subProcess = elementRegistry.filter(function(element) { + return !!(element.id !== 'SubProcess_1kd6ist' && element.type === 'bpmn:SubProcess'); + })[0]; + + loopCharacteristics = subProcess.businessObject.loopCharacteristics; + + expect(loopCharacteristics.$type).to.equal('bpmn:MultiInstanceLoopCharacteristics'); + expect(loopCharacteristics.isSequential).to.be.true; + })); + + + it('selected elements', inject(integrationTest([ 'SubProcess_1kd6ist' ]))); }); - describe('complex collaboration', function() { - beforeEach(bootstrapModeler(collaborationMultipleXML, { modules: testModules })); + describe('rules', function() { - describe('basics', function() { + it('disallow individual boundary events copying', inject(function(copyPaste, elementRegistry, canvas) { - it('pasting on lane', inject(function(elementRegistry, copyPaste) { - // given - var lane = elementRegistry.get('Lane_1yo0kyz'), - task = elementRegistry.get('Task_0n0k2nj'), - participant = elementRegistry.get('Participant_0pgdgt4'); + var boundaryEventA = elementRegistry.get('BoundaryEvent_1404oxd'), + boundaryEventB = elementRegistry.get('BoundaryEvent_1c94bi9'), + tree; - // when - copyPaste.copy(task); + // when + copyPaste.copy([ boundaryEventA, boundaryEventB ]); - copyPaste.paste({ - element: lane, - point: { - x: 200, - y: 75 - } - }); + tree = new DescriptorTree(copyPaste._tree); - // then - expect(lane.children).to.be.empty; - expect(lane.businessObject.flowNodeRef).to.have.length(2); + expect(tree.getLength()).to.equal(0); + })); + }); - expect(lane.parent.children).to.have.length(2); - expect(participant.children).to.have.length(5); - })); + }); - }); - describe('integration', function() { + describe('basic collaboration', function() { - it('multiple participants', inject(integrationTest([ 'Participant_0pgdgt4', 'Participant_1id96b4' ]))); + beforeEach(bootstrapModeler(collaborationXML, { modules: testModules })); - }); + describe('integration', function() { + + it('participant with including lanes + elements', inject(integrationTest([ 'Participant_0uu1rvj' ]))); + + it('collapsed pool', inject(integrationTest([ 'Participant_145muai' ]))); }); + + describe('rules', function () { + + it('disallow individual lanes copying', inject(function(copyPaste, elementRegistry, canvas) { + + var laneA = elementRegistry.get('Lane_13h648l'), + laneB = elementRegistry.get('Lane_1gl63sa'), + tree; + + // when + copyPaste.copy([ laneA, laneB ]); + + tree = new DescriptorTree(copyPaste._tree); + + expect(tree.getLength()).to.equal(0); + })); + + + it('pasting on a collaboration is disallowed when NOT every element is a Participant', + inject(function(copyPaste, elementRegistry, canvas, tooltips, eventBus) { + var task = elementRegistry.get('Task_13xbgyg'), + participant = elementRegistry.get('Participant_145muai'), + collaboration = canvas.getRootElement(), + tree; + + var pasteRejected = sinon.spy(function() {}); + + // when + copyPaste.copy([ task, participant ]); + + tree = new DescriptorTree(copyPaste._tree); + + // then + expect(tree.getDepthLength(0)).to.equal(2); + + // when + eventBus.on('elements.paste.rejected', pasteRejected); + + copyPaste.paste({ + element: collaboration, + point: { + x: 1000, + y: 1000 + } + }); + + expect(pasteRejected).to.have.been.called; + })); + + + it('pasting participants on a process is disallowed when it\'s not a collaboration', + inject(function(copyPaste, elementRegistry, canvas, tooltips, eventBus, modeling, elementFactory) { + + var participant = elementRegistry.get('Participant_145muai'), + otherParticipant = elementRegistry.get('Participant_0uu1rvj'), + startEvent = elementFactory.create('shape', { type: 'bpmn:StartEvent' }), + rootElement; + + var pasteRejected = sinon.spy(function() {}); + + // when + copyPaste.copy([ participant ]); + + modeling.removeElements([ participant, otherParticipant ]); + + rootElement = canvas.getRootElement(); + + modeling.createShape(startEvent, { x: 50, y: 50 }, rootElement); + + eventBus.on('elements.paste.rejected', pasteRejected); + + copyPaste.paste({ + element: rootElement, + point: { + x: 500, + y: 200 + } + }); + + expect(pasteRejected).to.have.been.called; + })); + + }); + + }); + + + describe('complex collaboration', function() { + + beforeEach(bootstrapModeler(collaborationMultipleXML, { modules: testModules })); + + describe('basics', function() { + + it('pasting on lane', inject(function(elementRegistry, copyPaste) { + // given + var lane = elementRegistry.get('Lane_1yo0kyz'), + task = elementRegistry.get('Task_0n0k2nj'), + participant = elementRegistry.get('Participant_0pgdgt4'); + + // when + copyPaste.copy(task); + + copyPaste.paste({ + element: lane, + point: { + x: 200, + y: 75 + } + }); + + // then + expect(lane.children).to.be.empty; + expect(lane.businessObject.flowNodeRef).to.have.length(2); + + expect(lane.parent.children).to.have.length(2); + expect(participant.children).to.have.length(5); + })); + + }); + + + describe('integration', function() { + + it('multiple participants', inject(integrationTest([ 'Participant_0pgdgt4', 'Participant_1id96b4' ]))); + + }); + + }); + });