import { bootstrapModeler, bootstrapViewer, inject } from 'test/TestHelper'; import { create as svgCreate } from 'tiny-svg'; import coreModule from 'lib/core'; import rendererModule from 'lib/draw'; import modelingModule from 'lib/features/modeling'; import { query as domQuery } from 'min-dom'; import { isAny } from 'lib/features/modeling/util/ModelingUtil'; import { getDi } from 'lib/draw/BpmnRenderUtil'; function checkErrors(err, warnings) { expect(warnings).to.be.empty; expect(err).not.to.exist; } describe('draw - bpmn renderer', function() { it('should render labels', function() { var xml = require('./BpmnRenderer.labels.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render activity markers', function() { var xml = require('../../fixtures/bpmn/draw/activity-markers.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render association markers', function() { var xml = require('../../fixtures/bpmn/draw/associations.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render activity markers (combination)', function() { var xml = require('../../fixtures/bpmn/draw/activity-markers-combination.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render conditional flows', function() { var xml = require('../../fixtures/bpmn/draw/conditional-flow.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render conditional default flows', function() { var xml = require('../../fixtures/bpmn/draw/conditional-flow-default.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { return checkErrors(result.error, result.warnings); }); }); it('should render NO conditional flow (gateway)', function() { var xml = require('../../fixtures/bpmn/draw/conditional-flow-gateways.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render conditional flow (typed task)', function() { var xml = require('../../fixtures/bpmn/draw/conditional-flow-typed-task.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render data objects', function() { var xml = require('../../fixtures/bpmn/draw/data-objects.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render events', function() { var xml = require('../../fixtures/bpmn/draw/events.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render events (interrupting)', function() { var xml = require('../../fixtures/bpmn/draw/events-interrupting.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render event subprocesses (collapsed)', function() { var xml = require('../../fixtures/bpmn/draw/event-subprocesses-collapsed.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render event subprocesses (expanded)', function() { var xml = require('../../fixtures/bpmn/draw/event-subprocesses-expanded.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render gateways', function() { var xml = require('../../fixtures/bpmn/draw/gateways.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render group', function() { var xml = require('../../fixtures/bpmn/draw/group.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render message marker', function() { var xml = require('../../fixtures/bpmn/draw/message-marker.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render message label', function() { var xml = require('../../fixtures/bpmn/draw/message-label.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); inject(function(elementRegistry) { var dataFlow = elementRegistry.getGraphics('dataFlow'); expect(domQuery('.djs-label', dataFlow)).to.exist; })(); }); }); it('should render pools', function() { var xml = require('../../fixtures/bpmn/draw/pools.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render pool collection marker', function() { var xml = require('../../fixtures/bpmn/draw/pools-with-collection-marker.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render task types', function() { var xml = require('../../fixtures/bpmn/draw/task-types.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render text annotations', function() { var xml = require('../../fixtures/bpmn/draw/text-annotation.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render flow markers', function() { var xml = require('../../fixtures/bpmn/flow-markers.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render xor gateways blank and with X', function() { var xml = require('../../fixtures/bpmn/draw/xor.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render boundary events with correct z-index', function() { var xml = require('../../fixtures/bpmn/draw/boundary-event-z-index.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render boundary events without flowNodeRef', function() { var xml = require('../../fixtures/bpmn/draw/boundary-event-without-refnode.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render boundary event only once if referenced incorrectly via flowNodeRef (robustness)', function() { var xml = require('../../fixtures/bpmn/draw/boundary-event-with-refnode.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render gateway event if attribute is missing in XML', function() { var xml = require('../../fixtures/bpmn/draw/gateway-type-default.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render call activity', function() { var xml = require('../../fixtures/bpmn/draw/call-activity.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(elementRegistry) { var callActivityGfx = elementRegistry.getGraphics('CallActivity'); // make sure the + marker is shown expect(domQuery('[data-marker=sub-process]', callActivityGfx)).to.exist; })(); }); }); it('should render adhoc sub process', function() { var xml = require('../../fixtures/bpmn/draw/activity-markers-simple.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(elementRegistry) { var callActivityGfx = elementRegistry.getGraphics('AdHocSubProcess'); // make sure the + marker is shown expect(domQuery('[data-marker=adhoc]', callActivityGfx)).to.exist; })(); }); }); it('should add random ID suffix to marker ID', function() { var xml = require('../../fixtures/bpmn/simple.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(canvas) { var svg = canvas._svg; var markers = svg.querySelectorAll('marker'); expect(markers[0].id).to.match(/^sequenceflow-end-white-black-[A-Za-z0-9]+$/); })(); }); }); it('should properly render colored markers', function() { var xml = require('./BpmnRenderer.colors.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(canvas) { var svg = canvas._svg, markers = svg.querySelectorAll('marker'); expect(markers).to.have.length(7); expect(markers[0].id).to.match(/^sequenceflow-end-rgb_255_224_178_-rgb_251_140_0_-[A-Za-z0-9]{25}$/); expect(markers[1].id).to.match(/^sequenceflow-end-yellow-blue-[A-Za-z0-9]{25}$/); expect(markers[2].id).to.match(/^sequenceflow-end-white-_FB8C00-[A-Za-z0-9]{25}$/); expect(markers[3].id).to.match(/^sequenceflow-end-white-rgba_255_0_0_0_9_-[A-Za-z0-9]{25}$/); expect(markers[4].id).to.match(/^association-end-_FFE0B2-_FB8C00-[A-Za-z0-9]{25}$/); expect(markers[5].id).to.match(/^messageflow-end-_FFE0B2-_FB8C00-[A-Za-z0-9]{25}$/); expect(markers[6].id).to.match(/^messageflow-start-_FFE0B2-_FB8C00-[A-Za-z0-9]{25}$/); })(); }); }); it('should properly render connection markers (2)', function() { var xml = require('./BpmnRenderer.connection-colors.bpmn'); return bootstrapViewer(xml).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(canvas) { var svg = canvas._svg, markers = svg.querySelectorAll('marker'); expect(markers).to.have.length(4); expect(markers[0].id).to.match(/^association-end-rgb_23_100_344_-rgb_23_100_344_-[A-Za-z0-9]{25}$/); expect(markers[1].id).to.match(/^association-end-_E1BEE7-_8E24AA-[A-Za-z0-9]{25}$/); expect(markers[2].id).to.match(/^messageflow-end-rgb_23_100_344_-rgb_23_100_344_-[A-Za-z0-9]{25}$/); expect(markers[3].id).to.match(/^messageflow-start-rgb_23_100_344_-rgb_23_100_344_-[A-Za-z0-9]{25}$/); })(); }); }); it('should render sequenceFlows without source', function() { var xml = require('./BpmnRenderer.sequenceFlow-no-source.bpmn'); return bootstrapModeler(xml, { modules: [ coreModule, rendererModule, modelingModule ] }).call(this).then(function(result) { var err = result.error; expect(err).not.to.exist; inject(function(elementFactory, graphicsFactory) { // given var g = svgCreate('g'); var connection = elementFactory.create('connection', { type: 'bpmn:SequenceFlow', waypoints: [ { x: 0, y: 0 }, { x: 10, y: 100 } ] }); var gfx = graphicsFactory.drawConnection(g, connection); expect(gfx).to.exist; })(); }); }); describe('colors', function() { var xml = require('./BpmnRenderer.colors.bpmn'); var groupXML = require('./BpmnRenderer.group-colors.bpmn'); it('should render colors without warnings and errors', function() { return bootstrapViewer(xml).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); it('should render group colors', function() { return bootstrapViewer(groupXML).call(this).then(function(result) { checkErrors(result.error, result.warnings); }); }); describe('default colors', function() { var defaultFillColor = 'red', defaultStrokeColor = 'lime', defaultLabelColor = 'blue'; // TODO(philippfromme): remove once we drop PhantomJS function expectedColors(color) { var conversionValues = { blue: '#0000ff', lime: '#00ff00', red: '#ff0000', yellow: '#ffff00', 'rgb(251, 140, 0)': '#fb8c00', '#FB8C00': 'rgb(251, 140, 0)', '#3399aa': 'rgb(51, 153, 170)' }; return [ color, color.toLowerCase(), color.toUpperCase(), conversionValues[ color ], conversionValues[ color ] && conversionValues[ color ].toLowerCase(), conversionValues[ color ] && conversionValues[ color ].toUpperCase() ]; } beforeEach(bootstrapViewer(xml,{ bpmnRenderer: { defaultFillColor: defaultFillColor, defaultStrokeColor: defaultStrokeColor, defaultLabelColor: defaultLabelColor } })); // TODO(philippfromme): remove once we drop PhantomJS /** * Ensure alpha channel of RGB (rgba) color has one decimal point. * * @param {string} color * * @return {string} */ function fixRgba(color) { if (color.indexOf('rgba') !== -1) { return [ 'rgba(', color .replace(/rgba\(|\)/g, '') .split(',') .map(function(string) { if (string.indexOf('.') !== -1) { return parseFloat(string).toFixed(1); } return parseInt(string); }) .join(', '), ')' ].join(''); } return color; } function expectFillColor(element, color) { expect(expectedColors(color)).to.include(fixRgba(element.style.fill)); } function expectStrokeColor(element, color) { expect(expectedColors(color)).to.include(fixRgba(element.style.stroke)); } /** * Expect colors depending on element type. * * @param {djs.model.base} element - Element. * @param {SVG} gfx - Graphics of element. * @param {string} fillColor - Fill color to expect. * @param {string} strokeColor - Stroke color to expect. */ function expectColors(element, gfx, fillColor, strokeColor, labelColor) { var djsVisual = domQuery('.djs-visual', gfx); var circle, path, polygon, polyline, rect, text; if (element.labelTarget) { text = domQuery('text', djsVisual); expectFillColor(text, labelColor); } else if (element.waypoints) { path = domQuery('path', djsVisual); polyline = domQuery('polyline', djsVisual); expectStrokeColor(path || polyline, strokeColor); } else if (isAny(element, [ 'bpmn:StartEvent', 'bpmn:EndEvent' ])) { circle = domQuery('circle', djsVisual); expectFillColor(circle, fillColor); expectStrokeColor(circle, strokeColor); } else if (isAny(element, [ 'bpmn:Task', 'bpmn:SubProcess', 'bpmn:Participant' ])) { rect = domQuery('rect', djsVisual); text = domQuery('text', djsVisual); expectFillColor(rect, fillColor); expectStrokeColor(rect, strokeColor); expectFillColor(text, labelColor); } else if (isAny(element, [ 'bpmn:Gateway' ])) { polygon = domQuery('polygon', djsVisual); expectFillColor(polygon, fillColor); expectStrokeColor(polygon, strokeColor); } } it('should render default colors without overriding', inject(function(canvas, elementRegistry) { var rootElement = canvas.getRootElement(); elementRegistry.forEach(function(element) { if (element === rootElement) { return; } var gfx = elementRegistry.getGraphics(element), di = getDi(element), fillColor = di.get('color:background-color') || di.get('bioc:fill') || defaultFillColor, strokeColor = di.get('color:border-color') || di.get('bioc:stroke') || defaultStrokeColor, labelDi = di.get('label'), labelColor = labelDi && labelDi.get('color:color') || defaultLabelColor; expectColors(element, gfx, fillColor, strokeColor, labelColor); }); })); }); }); describe('path', function() { var diagramXML = require('./BpmnRenderer.simple-cropping.bpmn'); var testModules = [ coreModule, rendererModule ]; beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); describe('circle', function() { it('should return a circle path', inject(function(canvas, elementRegistry, graphicsFactory) { // given var eventElement = elementRegistry.get('StartEvent_1'); // when var startPath = graphicsFactory.getShapePath(eventElement); // then expect(startPath).to.equal('M247,343m0,-18a18,18,0,1,1,0,36a18,18,0,1,1,0,-36z'); })); it('should return a diamond path', inject(function(canvas, elementRegistry, graphicsFactory) { // given var gatewayElement = elementRegistry.get('ExclusiveGateway_1'); // when var gatewayPath = graphicsFactory.getShapePath(gatewayElement); // then expect(gatewayPath).to.equal('M418,318l25,25l-25,25l-25,-25z'); })); it('should return a rounded rectangular path', inject(function(canvas, elementRegistry, graphicsFactory) { // given var subProcessElement = elementRegistry.get('SubProcess_1'); // when var subProcessPath = graphicsFactory.getShapePath(subProcessElement); // then expect(subProcessPath).to.equal('M584,243l330,0a10,10,0,0,1,10,10l0,180a10,10,0,0,1,-10,10' + 'l-330,0a10,10,0,0,1,-10,-10l0,-180a10,10,0,0,1,10,-10z'); })); it('should return a rectangular path', inject(function(canvas, elementRegistry, graphicsFactory) { // given var TextAnnotationElement = elementRegistry.get('TextAnnotation_1'); // when var TextAnnotationPath = graphicsFactory.getShapePath(TextAnnotationElement); // then expect(TextAnnotationPath).to.equal('M368,156l100,0l0,80l-100,0z'); })); }); }); describe('extension API', function() { var diagramXML = require('../../fixtures/bpmn/simple.bpmn'); beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule, rendererModule ] })); it('should expose helpers', inject(function(bpmnRenderer) { // then // unsafe to use render APIs expect(bpmnRenderer._drawPath).to.be.a('function'); expect(bpmnRenderer._renderer).to.be.a('function'); // very unsafe to use internal state expect(bpmnRenderer.handlers).to.exist; })); }); });