fix(modeling): correctly update Lane#flowNodeRefs

Previously, the bpmn:FlowNode <-> bpmn:Lane relationship was not
properly maintained in the BpmnUpdater.

This commit fixes the behavior.

Closes #341
This commit is contained in:
Nico Rehwaldt 2015-08-31 14:59:29 +02:00
parent 39db57987d
commit afa72ad5bd
3 changed files with 367 additions and 4 deletions

View File

@ -59,7 +59,19 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) {
// update parent
function updateParent(e) {
self.updateParent(e.context.shape || e.context.connection);
var context = e.context;
self.updateParent(context.shape || context.connection, context.oldParent);
}
function reverseUpdateParent(e) {
var context = e.context;
var element = context.shape || context.connection,
// oldParent is the (old) new parent, because we are undoing
oldParent = context.parent || context.newParent;
self.updateParent(element, oldParent);
}
this.executed([ 'shape.move',
@ -68,12 +80,13 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) {
'connection.create',
'connection.move',
'connection.delete' ], updateParent);
this.reverted([ 'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete' ], updateParent);
'connection.delete' ], reverseUpdateParent);
/*
* ## Updating Parent
@ -162,7 +175,7 @@ inherits(BpmnUpdater, CommandInterceptor);
module.exports = BpmnUpdater;
BpmnUpdater.$inject = [ 'eventBus', 'bpmnFactory', 'connectionDocking'];
BpmnUpdater.$inject = [ 'eventBus', 'bpmnFactory', 'connectionDocking' ];
/////// implementation //////////////////////////////////
@ -176,7 +189,7 @@ BpmnUpdater.prototype.updateAttachment = function(context) {
businessObject.attachedToRef = host && host.businessObject;
};
BpmnUpdater.prototype.updateParent = function(element) {
BpmnUpdater.prototype.updateParent = function(element, oldParent) {
// do not update BPMN 2.0 label parent
if (element instanceof Model.Label) {
return;
@ -188,6 +201,10 @@ BpmnUpdater.prototype.updateParent = function(element) {
parentBusinessObject = parentShape && parentShape.businessObject,
parentDi = parentBusinessObject && parentBusinessObject.di;
if (is(element, 'bpmn:FlowNode')) {
this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
}
this.updateSemanticParent(businessObject, parentBusinessObject);
this.updateDiParent(businessObject.di, parentDi);
@ -208,6 +225,24 @@ BpmnUpdater.prototype.updateBounds = function(shape) {
});
};
BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
if (oldContainment === newContainment) {
return;
}
var oldRefs, newRefs;
if (is (oldContainment, 'bpmn:Lane')) {
oldRefs = oldContainment.get('flowNodeRef');
Collections.remove(oldRefs, businessObject);
}
if (is(newContainment, 'bpmn:Lane')) {
newRefs = newContainment.get('flowNodeRef');
Collections.add(newRefs, businessObject);
}
};
BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {

View File

@ -0,0 +1,271 @@
'use strict';
var TestHelper = require('../../../../TestHelper');
/* global bootstrapModeler, inject */
var modelingModule = require('../../../../../lib/features/modeling'),
coreModule = require('../../../../../lib/core');
describe('features/modeling - lanes - flowNodeRefs', function() {
var diagramXML = require('./flowNodeRefs.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should unwire during move', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
// when
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// then
expect(sourceLane.flowNodeRef).not.to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// when
commandStack.undo();
// then
expect(sourceLane.flowNodeRef).to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B');
modeling.moveElements([ taskShape ], { x: 0, y: +200 }, targetParticipantShape);
// when
commandStack.undo();
commandStack.redo();
// then
expect(sourceLane.flowNodeRef).to.not.contain(task);
}));
});
describe('should wire during move', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
// when
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// then
expect(targetLane.flowNodeRef).to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// when
commandStack.undo();
// then
expect(targetLane.flowNodeRef).not.to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_B'),
task = taskShape.businessObject,
targetLaneShape = elementRegistry.get('Lane'),
targetLane = targetLaneShape.businessObject;
modeling.moveElements([ taskShape ], { x: 0, y: -200 }, targetLaneShape);
// when
commandStack.undo();
commandStack.redo();
// then
expect(targetLane.flowNodeRef).to.contain(task);
}));
});
describe('should unwire during delete', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
// when
modeling.removeElements([ taskShape ]);
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
modeling.removeElements([ taskShape ]);
// when
commandStack.undo();
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
modeling.removeElements([ taskShape ]);
// when
commandStack.undo();
commandStack.redo();
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
});
describe('should wire during create', function() {
it('execute', inject(function(elementRegistry, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
// when
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 200, y: 300 }, parentLaneShape);
task = taskShape.businessObject;
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
it('undo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 200, y: 300 }, parentLaneShape);
task = taskShape.businessObject;
// when
commandStack.undo();
// then
expect(parentLane.flowNodeRef).not.to.contain(task);
}));
it('redo', inject(function(elementRegistry, commandStack, modeling) {
// given
var taskShape, task,
parentLaneShape = elementRegistry.get('Lane'),
parentLane = parentLaneShape.businessObject;
taskShape = modeling.createShape({ type: 'bpmn:Task' }, { x: 200, y: 300 }, parentLaneShape);
task = taskShape.businessObject;
// when
commandStack.undo();
commandStack.redo();
// then
expect(parentLane.flowNodeRef).to.contain(task);
}));
});
it('should unwire moving multiple', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_A'),
task = taskShape.businessObject,
eventShape = elementRegistry.get('Event'),
event = eventShape.businessObject,
targetParticipantShape = elementRegistry.get('Participant_B'),
sourceLaneShape = elementRegistry.get('Lane'),
sourceLane = sourceLaneShape.businessObject;
// when
modeling.moveElements([ taskShape, eventShape ], { x: 0, y: +200 }, targetParticipantShape);
// then
expect(sourceLane.flowNodeRef).not.to.contain(task);
expect(sourceLane.flowNodeRef).not.to.contain(event);
}));
});

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="_2_FUoE-xEeWT0c1N_GlSWA" exporter="camunda modeler" exporterVersion="2.6.0" targetNamespace="http://activiti.org/bpmn">
<bpmn2:collaboration id="_Collaboration_2">
<bpmn2:participant id="Participant_A" name="Participant_A" processRef="Process_A"/>
<bpmn2:participant id="Participant_B" name="Participant_B" processRef="Process_B"/>
</bpmn2:collaboration>
<bpmn2:process id="Process_A" isExecutable="false">
<bpmn2:laneSet id="LaneSet_1" name="Lane Set 1">
<bpmn2:lane id="Lane" name="Lane">
<bpmn2:flowNodeRef>Task_A</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>Event</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
<bpmn2:task id="Task_A" name="Task_A">
<bpmn2:outgoing>SequenceFlow</bpmn2:outgoing>
</bpmn2:task>
<bpmn2:sequenceFlow id="SequenceFlow" name="" sourceRef="Task_A" targetRef="Event"/>
<bpmn2:intermediateCatchEvent id="Event" name="Event">
<bpmn2:incoming>SequenceFlow</bpmn2:incoming>
</bpmn2:intermediateCatchEvent>
</bpmn2:process>
<bpmn2:process id="Process_B" isExecutable="false">
<bpmn2:task id="Task_B" name="Task_B"/>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="_Collaboration_2">
<bpmndi:BPMNShape id="_BPMNShape_Participant_2" bpmnElement="Participant_A" isHorizontal="true">
<dc:Bounds height="145.0" width="540.0" x="84.0" y="96.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Participant_3" bpmnElement="Participant_B" isHorizontal="true">
<dc:Bounds height="133.0" width="540.0" x="84.0" y="312.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_2" bpmnElement="Task_A">
<dc:Bounds height="80.0" width="100.0" x="168.0" y="129.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Lane_2" bpmnElement="Lane" isHorizontal="true">
<dc:Bounds height="145.0" width="510.0" x="114.0" y="96.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_IntermediateCatchEvent_2" bpmnElement="Event">
<dc:Bounds height="36.0" width="36.0" x="384.0" y="151.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="21.0" width="112.0" x="346.0" y="192.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow" sourceElement="_BPMNShape_Task_2" targetElement="_BPMNShape_IntermediateCatchEvent_2">
<di:waypoint xsi:type="dc:Point" x="268.0" y="169.0"/>
<di:waypoint xsi:type="dc:Point" x="384.0" y="169.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="6.0" width="6.0" x="290.0" y="169.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_B">
<dc:Bounds height="80.0" width="100.0" x="492.0" y="339.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>