From acb2184087f8b9772f1af547f58589597f4bc817 Mon Sep 17 00:00:00 2001 From: Ricardo Matias Date: Wed, 15 Jul 2015 08:50:21 +0200 Subject: [PATCH] feat(import): attach boundary events * establish host <> attachers relationship * clean up import tests (use diagrams in fixtures/bpmn/import) closes #310 --- lib/import/BpmnImporter.js | 49 ++- test/fixtures/bpmn/import/boundaryEvent.bpmn | 34 ++ test/fixtures/bpmn/import/collaboration.bpmn | 51 +++ .../collaboration.bpmn} | 0 .../process.bpmn} | 0 .../boundaryEvent-invalidAttachToRef.bpmn | 30 ++ .../boundaryEvent-missingAttachToRef.bpmn | 34 ++ .../dangling-process-message-flow.bpmn | 0 .../error/invalid-flow-element.bpmn | 0 .../bpmn/{ => import}/error/multiple-dis.bpmn | 0 test/fixtures/bpmn/import/process.bpmn | 61 ++++ test/fixtures/bpmn/simple.bpmn | 36 +-- test/spec/import/ImporterSpec.js | 291 ++++++++++++------ test/spec/import/elements/CollapsedSpec.js | 4 +- 14 files changed, 475 insertions(+), 115 deletions(-) create mode 100644 test/fixtures/bpmn/import/boundaryEvent.bpmn create mode 100644 test/fixtures/bpmn/import/collaboration.bpmn rename test/fixtures/bpmn/import/{collapsed-collaboration.bpmn => collapsed/collaboration.bpmn} (100%) rename test/fixtures/bpmn/import/{collapsed.bpmn => collapsed/process.bpmn} (100%) create mode 100644 test/fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn create mode 100644 test/fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn rename test/fixtures/bpmn/import/{ => error}/dangling-process-message-flow.bpmn (100%) rename test/fixtures/bpmn/{ => import}/error/invalid-flow-element.bpmn (100%) rename test/fixtures/bpmn/{ => import}/error/multiple-dis.bpmn (100%) create mode 100644 test/fixtures/bpmn/import/process.bpmn diff --git a/lib/import/BpmnImporter.js b/lib/import/BpmnImporter.js index be3c5c10..f9636bdd 100644 --- a/lib/import/BpmnImporter.js +++ b/lib/import/BpmnImporter.js @@ -5,6 +5,8 @@ var assign = require('lodash/object/assign'), var LabelUtil = require('../util/LabelUtil'); +var is = require('../util/ModelUtil').is; + var hasExternalLabel = LabelUtil.hasExternalLabel, getExternalLabelBounds = LabelUtil.getExternalLabelBounds, isExpanded = require('../util/DiUtil').isExpanded, @@ -25,6 +27,11 @@ function collectWaypoints(waypoints) { }); } +function notYetDrawn(semantic, refSemantic, property) { + return new Error( + 'element ' + elementToString(refSemantic) + ' referenced by ' + + elementToString(semantic) + '#' + property + ' not yet drawn'); +} /** * An importer that adds bpmn elements to the canvas @@ -84,6 +91,10 @@ BpmnImporter.prototype.add = function(semantic, parentElement) { height: Math.round(bounds.height) })); + if (is(semantic, 'bpmn:BoundaryEvent')) { + this._attachBoundary(semantic, element); + } + this._canvas.addShape(element, parentElement); } @@ -116,6 +127,40 @@ BpmnImporter.prototype.add = function(semantic, parentElement) { }; +/** + * Attach the boundary element to the given host + * + * @param {ModdleElement} boundarySemantic + * @param {djs.model.Base} boundaryElement + */ +BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) { + + var hostSemantic = boundarySemantic.attachedToRef; + + if (!hostSemantic) { + throw new Error('missing ' + elementToString(boundarySemantic) + '#attachedToRef'); + } + + var host = this._elementRegistry.get(hostSemantic.id), + attachers = host && host.attachers; + + if (!host) { + throw notYetDrawn(boundarySemantic, hostSemantic, 'attachedToRef'); + } + + // wire element.host <> host.attachers + boundaryElement.host = host; + + if (!attachers) { + host.attachers = attachers = []; + } + + if (attachers.indexOf(boundaryElement) === -1) { + attachers.push(boundaryElement); + } +}; + + /** * add label for an element */ @@ -168,9 +213,7 @@ BpmnImporter.prototype._getEnd = function(semantic, side) { } if (refSemantic) { - throw new Error( - 'element ' + elementToString(refSemantic) + ' referenced by ' + - elementToString(semantic) + '#' + side + 'Ref not yet drawn'); + throw notYetDrawn(semantic, refSemantic, side + 'Ref'); } else { throw new Error(elementToString(semantic) + '#' + side + 'Ref not specified'); } diff --git a/test/fixtures/bpmn/import/boundaryEvent.bpmn b/test/fixtures/bpmn/import/boundaryEvent.bpmn new file mode 100644 index 00000000..75f04f4c --- /dev/null +++ b/test/fixtures/bpmn/import/boundaryEvent.bpmn @@ -0,0 +1,34 @@ + + + + + + SequenceFlow_1 + + + SequenceFlow_1 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/import/collaboration.bpmn b/test/fixtures/bpmn/import/collaboration.bpmn new file mode 100644 index 00000000..a3cbcfb7 --- /dev/null +++ b/test/fixtures/bpmn/import/collaboration.bpmn @@ -0,0 +1,51 @@ + + + + + + + + + + + + + Task_1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/import/collapsed-collaboration.bpmn b/test/fixtures/bpmn/import/collapsed/collaboration.bpmn similarity index 100% rename from test/fixtures/bpmn/import/collapsed-collaboration.bpmn rename to test/fixtures/bpmn/import/collapsed/collaboration.bpmn diff --git a/test/fixtures/bpmn/import/collapsed.bpmn b/test/fixtures/bpmn/import/collapsed/process.bpmn similarity index 100% rename from test/fixtures/bpmn/import/collapsed.bpmn rename to test/fixtures/bpmn/import/collapsed/process.bpmn diff --git a/test/fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn b/test/fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn new file mode 100644 index 00000000..0ae7c037 --- /dev/null +++ b/test/fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn @@ -0,0 +1,30 @@ + + + + + SequenceFlow_1 + + + SequenceFlow_1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn b/test/fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn new file mode 100644 index 00000000..61bd1f04 --- /dev/null +++ b/test/fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn @@ -0,0 +1,34 @@ + + + + + + SequenceFlow_1 + + + SequenceFlow_1 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/import/dangling-process-message-flow.bpmn b/test/fixtures/bpmn/import/error/dangling-process-message-flow.bpmn similarity index 100% rename from test/fixtures/bpmn/import/dangling-process-message-flow.bpmn rename to test/fixtures/bpmn/import/error/dangling-process-message-flow.bpmn diff --git a/test/fixtures/bpmn/error/invalid-flow-element.bpmn b/test/fixtures/bpmn/import/error/invalid-flow-element.bpmn similarity index 100% rename from test/fixtures/bpmn/error/invalid-flow-element.bpmn rename to test/fixtures/bpmn/import/error/invalid-flow-element.bpmn diff --git a/test/fixtures/bpmn/error/multiple-dis.bpmn b/test/fixtures/bpmn/import/error/multiple-dis.bpmn similarity index 100% rename from test/fixtures/bpmn/error/multiple-dis.bpmn rename to test/fixtures/bpmn/import/error/multiple-dis.bpmn diff --git a/test/fixtures/bpmn/import/process.bpmn b/test/fixtures/bpmn/import/process.bpmn new file mode 100644 index 00000000..f9ec5d4e --- /dev/null +++ b/test/fixtures/bpmn/import/process.bpmn @@ -0,0 +1,61 @@ + + + + + SequenceFlow_3 + SequenceFlow_2 + + SequenceFlow_1 + + + SequenceFlow_1 + + + + + SequenceFlow_2 + + + + SequenceFlow_3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/bpmn/simple.bpmn b/test/fixtures/bpmn/simple.bpmn index f9ec5d4e..3635af9e 100644 --- a/test/fixtures/bpmn/simple.bpmn +++ b/test/fixtures/bpmn/simple.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow_3 @@ -10,52 +10,52 @@ SequenceFlow_1 - + SequenceFlow_2 - + SequenceFlow_3 - + - + - + - + - - + + - + - - + + - + - + - - + + - + - \ No newline at end of file + diff --git a/test/spec/import/ImporterSpec.js b/test/spec/import/ImporterSpec.js index 5947c103..55d51f1f 100644 --- a/test/spec/import/ImporterSpec.js +++ b/test/spec/import/ImporterSpec.js @@ -2,6 +2,9 @@ var TestHelper = require('../../TestHelper'); +/* global bootstrapViewer, inject */ + + var TestContainer = require('mocha-test-container-support'); var Diagram = require('diagram-js/lib/Diagram'), @@ -10,6 +13,8 @@ var Diagram = require('diagram-js/lib/Diagram'), Viewer = require('../../../lib/Viewer'); +var is = require('../../../lib/util/ModelUtil').is; + describe('import - Importer', function() { var moddle = new BpmnModdle(); @@ -48,10 +53,10 @@ describe('import - Importer', function() { describe('event emitter', function() { - it('should fire during import', function(done) { + it('should fire during import', function(done) { // given - var xml = require('../../fixtures/bpmn/simple.bpmn'); + var xml = require('../../fixtures/bpmn/import/process.bpmn'); var eventCount = 0; @@ -75,10 +80,10 @@ describe('import - Importer', function() { describe('basics', function() { - it('should import simple process', function(done) { + it('should import process', function(done) { // given - var xml = require('../../fixtures/bpmn/simple.bpmn'); + var xml = require('../../fixtures/bpmn/import/process.bpmn'); var events = []; @@ -116,7 +121,7 @@ describe('import - Importer', function() { it('should import collaboration', function(done) { // given - var xml = require('../../fixtures/bpmn/collaboration.bpmn'); + var xml = require('../../fixtures/bpmn/import/collaboration.bpmn'); var events = []; @@ -148,109 +153,168 @@ describe('import - Importer', function() { done(err); }); }); + + + it('should import boundary events', function(done) { + + // given + var xml = require('../../fixtures/bpmn/import/boundaryEvent.bpmn'); + + var events = []; + + // log events + diagram.get('eventBus').on('bpmnElement.added', function(e) { + events.push({ + type: 'add', + semantic: e.element.businessObject.id, + di: e.element.businessObject.di.id, + diagramElement: e.element && e.element.id + }); + }); + + // when + runImport(diagram, xml, function(err, warnings) { + + // then + expect(events).to.eql([ + { type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1'}, + { type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_2', diagramElement: 'Task_1'}, + { type: 'add', semantic: 'Task_2', di: '_BPMNShape_Task_3', diagramElement: 'Task_2'}, + { type: 'add', semantic: 'BoundaryEvent_1', di: '_BPMNShape_BoundaryEvent_2', diagramElement: 'BoundaryEvent_1'}, + { type: 'add', semantic: 'SequenceFlow_1', di: 'BPMNEdge_SequenceFlow_1', diagramElement: 'SequenceFlow_1'} + ]); + + done(err); + }); + }); }); describe('model wiring', function() { - var xml = require('../../fixtures/bpmn/simple.bpmn'); + describe('basics', function() { - var elements; + var xml = require('../../fixtures/bpmn/import/process.bpmn'); - beforeEach(function(done) { - elements = []; + beforeEach(bootstrapViewer(xml)); - // log events - diagram.get('eventBus').on('bpmnElement.added', function(e) { - elements.push(e.element); - }); - runImport(diagram, xml, done); + it('should wire root element', inject(function(elementRegistry, canvas) { + + // when + var processElement = elementRegistry.get('Process_1'); + var subProcessShape = elementRegistry.get('SubProcess_1'); + + // then + expect(subProcessShape.parent).to.eql(processElement); + expect(canvas.getRootElement()).to.eql(processElement); + + expect(is(processElement, 'bpmn:Process')).to.be.true; + })); + + + it('should wire parent child relationship', inject(function(elementRegistry) { + + // when + var subProcessShape = elementRegistry.get('SubProcess_1'); + var startEventShape = elementRegistry.get('StartEvent_1'); + + // then + expect(startEventShape.type).to.equal('bpmn:StartEvent'); + expect(startEventShape.parent).to.eql(subProcessShape); + + expect(subProcessShape.children.length).to.equal(5); + })); + + + it('should wire label relationship', inject(function(elementRegistry) { + + // when + var startEventShape = elementRegistry.get('StartEvent_1'); + var label = startEventShape.label; + + // then + expect(label).to.be.defined; + expect(label.id).to.equal(startEventShape.id + '_label'); + + expect(label.labelTarget).to.eql(startEventShape); + })); + + + it('should wire businessObject', inject(function(elementRegistry) { + + // when + var subProcessShape = elementRegistry.get('SubProcess_1'); + var startEventShape = elementRegistry.get('StartEvent_1'); + + var subProcess = subProcessShape.businessObject, + startEvent = startEventShape.businessObject; + + // then + expect(subProcess).to.be.defined; + expect(is(subProcess, 'bpmn:SubProcess')).to.be.true; + + expect(startEvent).to.be.defined; + expect(is(startEvent, 'bpmn:StartEvent')).to.be.true; + })); + + + it('should wire shape di', inject(function(elementRegistry) { + + // when + var subProcessShape = elementRegistry.get('SubProcess_1'); + + var subProcess = subProcessShape.businessObject; + var subProcessDi = subProcess.di; + + // then + expect(subProcessDi).to.be.defined; + expect(subProcessDi.bpmnElement).to.eql(subProcess); + })); + + + it('should wire connection di', inject(function(elementRegistry) { + + // when + var sequenceFlowElement = elementRegistry.get('SequenceFlow_1'); + + var sequenceFlow = sequenceFlowElement.businessObject; + var sequenceFlowDi = sequenceFlow.di; + + // then + expect(sequenceFlowDi).to.be.defined; + expect(sequenceFlowDi.bpmnElement).to.eql(sequenceFlow); + })); + }); - it('should wire root element', function() { + describe('boundary events', function() { - // given - var canvas = diagram.get('canvas'); + var xml = require('../../fixtures/bpmn/import/boundaryEvent.bpmn'); - // when - var root = elements[0]; - var anyChild = elements[1]; - - // assume - expect(root.businessObject.$instanceOf('bpmn:Process')).to.be.true; - expect(anyChild.parent).to.eql(root); - - // then - expect(canvas.getRootElement()).to.eql(root); - }); + beforeEach(bootstrapViewer(xml)); - it('should wire parent child relationship', function() { + it('should wire host attacher relationship', inject(function(elementRegistry) { - // when - var subProcessShape = elements[1]; - var startEventShape = elements[2]; + // when + var boundaryEventShape = elementRegistry.get('BoundaryEvent_1'), + boundaryEvent = boundaryEventShape.businessObject; - // then - expect(startEventShape.type).to.equal('bpmn:StartEvent'); - expect(startEventShape.parent).to.eql(subProcessShape); + var taskShape = elementRegistry.get('Task_1'), + task = taskShape.businessObject; - expect(subProcessShape.children.length).to.equal(5); - }); + // assume + expect(boundaryEvent.attachedToRef).to.eql(task); + // then + expect(boundaryEventShape.host).to.eql(taskShape); - it('should wire label relationship', function() { + expect(taskShape.attachers).to.exist; + expect(taskShape.attachers).to.contain(boundaryEventShape); + })); - // when - var startEventShape = elements[2]; - var label = startEventShape.label; - - // then - expect(label).to.be.defined; - expect(label.id).to.equal(startEventShape.id + '_label'); - - expect(label.labelTarget).to.eql(startEventShape); - }); - - - it('should wire businessObject', function() { - - // when - var subProcessShape = elements[1]; - var startEventShape = elements[2]; - - var subProcess = subProcessShape.businessObject, - startEvent = startEventShape.businessObject; - - // then - expect(subProcess).to.be.defined; - expect(subProcess.$instanceOf('bpmn:SubProcess')).to.be.true; - - expect(startEvent).to.be.defined; - expect(startEvent.$instanceOf('bpmn:StartEvent')).to.be.true; - }); - - - it('should wire di', function() { - - // when - var subProcessShape = elements[1]; - var startEventShape = elements[2]; - - var subProcess = subProcessShape.businessObject, - startEvent = startEventShape.businessObject; - - var subProcessDi = subProcess.di, - startEventDi = startEvent.di; - - // then - expect(subProcessDi).to.be.defined; - expect(subProcessDi.bpmnElement).to.eql(subProcess); - - expect(startEventDi).to.be.defined; - expect(startEventDi.bpmnElement).to.eql(startEvent); }); }); @@ -261,7 +325,7 @@ describe('import - Importer', function() { it('should import invalid flowElement', function(done) { // given - var xml = require('../../fixtures/bpmn/error/invalid-flow-element.bpmn'); + var xml = require('../../fixtures/bpmn/import/error/invalid-flow-element.bpmn'); // when runImport(diagram, xml, function(err, warnings) { @@ -276,7 +340,7 @@ describe('import - Importer', function() { it('should import multiple DIs', function(done) { // given - var xml = require('../../fixtures/bpmn/error/multiple-dis.bpmn'); + var xml = require('../../fixtures/bpmn/import/error/multiple-dis.bpmn'); // when runImport(diagram, xml, function(err, warnings) { @@ -310,6 +374,48 @@ describe('import - Importer', function() { }); }); + + describe('boundary events', function() { + + it('should handle missing attachToRef', function(done) { + + // given + var xml = require('../../fixtures/bpmn/import/error/boundaryEvent-missingAttachToRef.bpmn'); + + // when + runImport(diagram, xml, function(err, warnings) { + + // then + expect(warnings.length).to.eql(2); + + expect(warnings[0].message).to.eql('missing #attachedToRef'); + expect(warnings[1].message).to.eql('element referenced by #sourceRef not yet drawn'); + + done(err); + }); + }); + + + it('should handle invalid attachToRef', function(done) { + + // given + var xml = require('../../fixtures/bpmn/import/error/boundaryEvent-invalidAttachToRef.bpmn'); + + // when + runImport(diagram, xml, function(err, warnings) { + + // then + expect(warnings.length).to.eql(2); + + expect(warnings[0].message).to.eql('missing #attachedToRef'); + expect(warnings[1].message).to.eql('element referenced by #sourceRef not yet drawn'); + + done(err); + }); + }); + + }); + }); @@ -334,7 +440,7 @@ describe('import - Importer', function() { it('should import dangling process message flows', function(done) { // given - var xml = require('../../fixtures/bpmn/import/dangling-process-message-flow.bpmn'); + var xml = require('../../fixtures/bpmn/import/error/dangling-process-message-flow.bpmn'); // when runImport(diagram, xml, function(err, warnings) { @@ -348,12 +454,13 @@ describe('import - Importer', function() { done(err); }); }); + }); describe('position', function() { - var xml1 = require('../../fixtures/bpmn/import/position/position-testcase.bpmn'); + var xml = require('../../fixtures/bpmn/import/position/position-testcase.bpmn'); it('should round shape\'s x and y coordinates', function(done) { @@ -366,7 +473,7 @@ describe('import - Importer', function() { events[e.element.id] = e.element; }); - runImport(diagram, xml1, function(err, warnings) { + runImport(diagram, xml, function(err, warnings) { //round up expect(events.ID_End.x).to.equal(Math.round(340.6)); @@ -392,7 +499,7 @@ describe('import - Importer', function() { events[e.element.id] = e.element; }); - runImport(diagram, xml1, function(err, warnings) { + runImport(diagram, xml, function(err, warnings) { //round down expect(events.ID_Start.height).to.equal(Math.round(30.4)); diff --git a/test/spec/import/elements/CollapsedSpec.js b/test/spec/import/elements/CollapsedSpec.js index 2bf57de3..7ba347cd 100644 --- a/test/spec/import/elements/CollapsedSpec.js +++ b/test/spec/import/elements/CollapsedSpec.js @@ -9,7 +9,7 @@ describe('import - collapsed container', function() { describe('in process', function() { - var diagramXML = require('../../../fixtures/bpmn/import/collapsed.bpmn'); + var diagramXML = require('../../../fixtures/bpmn/import/collapsed/process.bpmn'); beforeEach(bootstrapViewer(diagramXML)); @@ -74,7 +74,7 @@ describe('import - collapsed container', function() { describe('in collaboration', function() { - var diagramXML = require('../../../fixtures/bpmn/import/collapsed-collaboration.bpmn'); + var diagramXML = require('../../../fixtures/bpmn/import/collapsed/collaboration.bpmn'); beforeEach(bootstrapViewer(diagramXML));