diff --git a/lib/draw/BpmnRenderer.js b/lib/draw/BpmnRenderer.js index 6c2afdb64b..15a675c942 100644 --- a/lib/draw/BpmnRenderer.js +++ b/lib/draw/BpmnRenderer.js @@ -64,6 +64,7 @@ import { import Ids from 'ids'; import { black } from './BpmnRenderUtil'; +import { measureTextLines } from '../features/modeling/BpmnUpdater'; var markerIds = new Ids(); @@ -1037,10 +1038,12 @@ export default function BpmnRenderer( } function renderLabel(parentGfx, label, attrs = {}) { + + // additional Padding for left and right to stabilize the rendering attrs = assign({ size: { width: 100 - } + }, }, attrs); var text = textRenderer.createText(label || '', attrs); @@ -1074,8 +1077,8 @@ export default function BpmnRenderer( function renderExternalLabel(parentGfx, element, attrs = {}) { var box = { - width: 90, - height: 30, + width: element.width, + height: element.height, x: element.width / 2 + element.x, y: element.height / 2 + element.y }; @@ -1083,6 +1086,12 @@ export default function BpmnRenderer( return renderLabel(parentGfx, getLabel(element), { box: box, fitBox: true, + padding: { + top: 0, + left: -2, + right: -2, + bottom: 0 + }, style: assign( {}, textRenderer.getExternalStyle(), @@ -2164,6 +2173,10 @@ export default function BpmnRenderer( return renderTask(parentGfx, element, attrs); }, 'bpmn:TextAnnotation': function(parentGfx, element, attrs = {}) { + if (element.correctBounds) { + changeBounds(element.correctBounds, element); + element.correctBounds = undefined; + } attrs = pickAttrs(attrs, [ 'fill', 'stroke', @@ -2175,12 +2188,34 @@ export default function BpmnRenderer( width, height } = getBounds(element, attrs); - + width = Math.ceil(width / 20) * 20; var textElement = drawRect(parentGfx, width, height, 0, 0, { fill: 'none', stroke: 'none' }); + var semantic = getSemantic(element), + text = semantic.get('text') || ''; + var renderedLabel = renderLabel(parentGfx, text, { + align: 'left-top', + box: getBounds(element, attrs), + padding: { + top: 2, + left: +2, + right: +5, + bottom: 0 + }, + style: { + fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) + } + }); + + height = getTextHeightForTextAnnotation(renderedLabel); + textElement.setAttribute('height', height); + width = getTextWidthForTextAnnotation(renderedLabel, width); + element.width = width; + textElement.setAttribute('width', width); + var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { xScaleFactor: 1, yScaleFactor: 1, @@ -2195,19 +2230,6 @@ export default function BpmnRenderer( drawPath(parentGfx, textPathData, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); - - var semantic = getSemantic(element), - text = semantic.get('text') || ''; - - renderLabel(parentGfx, text, { - align: 'left-top', - box: getBounds(element, attrs), - padding: 7, - style: { - fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) - } - }); - return textElement; }, 'bpmn:Transaction': function(parentGfx, element, attrs = {}) { @@ -2305,6 +2327,11 @@ export default function BpmnRenderer( return task; }, 'label': function(parentGfx, element, attrs = {}) { + if (element.correctBounds) { + changeBounds(element.correctBounds, element); + element.correctBounds = undefined; + } + return renderExternalLabel(parentGfx, element, attrs); } }; @@ -2410,4 +2437,46 @@ function pickAttrs(attrs, keys = []) { return pickedAttrs; }, {}); +} + +function changeBounds(newBounds, element) { + element.x = newBounds.x; + element.y = newBounds.y; + element.width = newBounds.width; + element.height = newBounds.height; +} + + +function getTextHeightForTextAnnotation(text) { + return Math.max(text.lastChild.y.animVal[0].value, 30) + 10; +} + +function getTextWidthForTextAnnotation(text, width) { + + if ((text.textContent === '')) { + return 100; + } + + let lineLengths = Array.from(measureTextLines(text.textContent, getStyleFromTextannotation(text), width + 5, true)) + .map(elem => elem.width); + + return Math.round(Math.max(...lineLengths)); +} + +function getStyleFromTextannotation(text) { + const styleString = text.attributes.style.nodeValue; + + let style = Object.fromEntries( + styleString + .split(';') + .map(part => part.trim()) + .filter(Boolean) + .map(part => { + const [ key, value ] = part.split(':').map(s => s.trim()); + return [ key, value ]; + }) + ); + style['lineHeight'] = text.attributes.lineHeight.nodeValue; + + return style; } \ No newline at end of file diff --git a/lib/features/label-editing/LabelEditingProvider.js b/lib/features/label-editing/LabelEditingProvider.js index 3c690d2eb2..ac70287e8a 100644 --- a/lib/features/label-editing/LabelEditingProvider.js +++ b/lib/features/label-editing/LabelEditingProvider.js @@ -228,7 +228,8 @@ LabelEditingProvider.prototype.activate = function(element) { // external labels if (isLabelExternal(element)) { assign(options, { - autoResize: true + resizable: true, + autoResize: true, }); // keep background and border for external labels @@ -393,7 +394,8 @@ LabelEditingProvider.prototype.getEditingBBox = function(element) { }); } - var width = 90 * zoom, + // making sure that editing box is correct + var width = bbox.width + 10 * zoom, paddingTop = 7 * zoom, paddingBottom = 4 * zoom; @@ -402,7 +404,7 @@ LabelEditingProvider.prototype.getEditingBBox = function(element) { assign(bounds, { width: width, height: bbox.height + paddingTop + paddingBottom, - x: mid.x - width / 2, + x: bbox.x, y: bbox.y - paddingTop }); diff --git a/lib/features/modeling/BpmnUpdater.js b/lib/features/modeling/BpmnUpdater.js index f41e2ca2af..d5d57d960f 100644 --- a/lib/features/modeling/BpmnUpdater.js +++ b/lib/features/modeling/BpmnUpdater.js @@ -1,34 +1,23 @@ -import { - assign, - forEach -} from 'min-dash'; +import { assign, forEach } from 'min-dash'; import inherits from 'inherits-browser'; -import { - add as collectionAdd, - remove as collectionRemove -} from 'diagram-js/lib/util/Collections'; +import { add as collectionAdd, remove as collectionRemove } from 'diagram-js/lib/util/Collections'; -import { - getBusinessObject, - getDi, - is -} from '../../util/ModelUtil'; +import { getBusinessObject, getDi, is } from '../../util/ModelUtil'; import { isAny } from './util/ModelingUtil'; -import { - getLabel, - isLabel, - isLabelExternal -} from '../../util/LabelUtil'; +import { getLabel, isLabel, isLabelExternal } from '../../util/LabelUtil'; + +import { append as svgAppend, attr as svgAttr, create as svgCreate, remove as svgRemove } from 'tiny-svg'; import { isPlane } from '../../util/DrilldownUtil'; import { delta } from 'diagram-js/lib/util/PositionUtil'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +import { assignStyle } from 'min-dom'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus @@ -185,12 +174,95 @@ export default function BpmnUpdater( // Handle labels separately. This is necessary, because the label bounds have to be updated // every time its shape changes, not only on move, create and resize. + eventBus.on('shape.changed', function(event) { if (event.element.type === 'label') { + if (canHandleLabelResize(event)) { + event.element.height = getTextHeightForLabel(event); + event.element.width = getTextWidthForLabel(event); + } + updateBounds({ context: { shape: event.element } }); + } + + + // handles difference for TextAnnotations + if (event.element.type === 'bpmn:TextAnnotation') { + event.element.height = Math.max(Math.round(getTextHeightForTextAnnotation(event)), 30); updateBounds({ context: { shape: event.element } }); + event.element.incoming.forEach((connection) => { + if (event.element.oldWayPoint) { + let parentGfx = getConnectionGfx(event, connection); + + connection.waypoints[1].x = event.element.oldWayPoint.x; + connection.waypoints[1].y = event.element.oldWayPoint.y; + connection.waypoints[1].original.x = event.element.oldWayPoint.original.x; + connection.waypoints[1].original.y = event.element.oldWayPoint.original.y; + eventBus.fire('element.changed', { element: connection , gfx: parentGfx }); + event.element.oldWayPoint = undefined; + } + + }); + + } + }); + function canHandleLabelResize(event) { + const element = event.element, + + currentlabel = getLabel(element), + + renderedLabel = getGfxLabel(event.gfx, currentlabel); + + if (currentlabel !== renderedLabel) { + return true; + } + let renderedLabelHeight = getTextHeightForLabel(event), + renderedLabelWidth = getTextWidthForLabel(event); + if (hasMoreDiffThan(element, renderedLabelHeight, renderedLabelWidth, 5)) { + return true; + } + + return false; + } + + function getConnectionGfx(event, connection) { + const children = event.gfx.parentElement.parentElement.children; + for (let child of children) { + const dataElementId = child.firstChild.attributes?.getNamedItem('data-element-id').value; + if (connection.id === dataElementId) { + return child; + } + } + } + + function getTextHeightForTextAnnotation(event) { + return Math.max(event.gfx.firstChild.children[1].lastChild.y.animVal[0].value, 30) + 10; + } + + function getTextHeightForLabel(event) { + const text = event.gfx.firstChild.firstChild; + return Math.max(Math.round(text.lastChild.y.animVal[0].value), 15); + } + + function getTextWidthForLabel(event) { + let lineLengths = Array.from(measureTextLines(getGfxLabel(event.gfx), getStyleFromLabel(event), event.element.width + 5)) + .map(elem => elem.width); + + return Math.round(Math.max(...lineLengths)); + } + + function getGfxLabel(gfx) { + return Array.from(gfx.firstChild.firstChild.children) + .map(child => child.textContent) + .join(''); + } + + function hasMoreDiffThan(element, height, width, padding) { + return Math.abs(element.width - width) > padding || Math.abs(element.height - height) > padding; + } + // attach / detach connection function updateConnection(e) { self.updateConnection(e.context); @@ -296,7 +368,8 @@ export default function BpmnUpdater( function updateBPMNLabel(event) { const { element } = event.context, - label = getLabel(element); + label = getLabel(element), + newBounds = event.context.newBounds; const di = getDi(element), diLabel = di && di.get('label'); @@ -304,6 +377,13 @@ export default function BpmnUpdater( return; } + if (newBounds) { + element.x = newBounds.x; + element.y = newBounds.y; + element.width = newBounds.width; + element.height = newBounds.height; + } + if (label && !diLabel) { di.set('label', bpmnFactory.create('bpmndi:BPMNLabel')); } else if (!label && diLabel) { @@ -816,4 +896,174 @@ function getEmbeddedLabelBounds(shape) { } return label.get('bounds'); +} + +// ---------- helper svg -------------- + +function getHelperSvg() { + let helperSvg = document.getElementById('helper-svg'); + + if (!helperSvg) { + helperSvg = svgCreate('svg'); + + svgAttr(helperSvg, { id: 'helper-svg' }); + + assignStyle(helperSvg, { + visibility: 'hidden', + position: 'fixed', + width: 0, + height: 0, + }); + + document.body.appendChild(helperSvg); + } + + return helperSvg; +} + +// ---------- text measurement --------- + +function getTextBBox(text, fakeText) { + fakeText.textContent = text; + + try { + const emptyLine = text === ''; + fakeText.textContent = emptyLine ? 'dummy' : text; + + const raw = fakeText.getBBox(); + + const box = { + width: raw.width + raw.x * 2, + height: raw.height, + }; + + if (emptyLine) box.width = 0; + + return box; + } catch (e) { + console.warn('BBox failed:', e); + return { width: 0, height: 0 }; + } +} + +// -------- shorten logic ---------- + +const SOFT_BREAK = '\u00AD'; + +function semanticShorten(line, maxLength) { + const parts = line.split(/(\s|-|\u00AD)/g); + let part; + const shortenedParts = []; + let length = 0; + + while ((part = parts.shift())) { + if (part.length + length < maxLength) { + shortenedParts.push(part); + length += part.length; + } else { + if (part === '-' || part === SOFT_BREAK) { + shortenedParts.pop(); + } + break; + } + } + + const last = shortenedParts[shortenedParts.length - 1]; + if (last === SOFT_BREAK) shortenedParts[shortenedParts.length - 1] = '-'; + + return shortenedParts.join(''); +} + +function shortenLine(line, width, maxWidth) { + const length = Math.max((line.length * maxWidth) / width, 1); + let shortened = semanticShorten(line, length); + + if (!shortened) { + shortened = line.slice(0, Math.max(Math.round(length - 1), 1)); + } + return shortened; +} + +// ---------- layout line ------------- + +function layoutNext(lines, maxWidth, fakeText) { + const originalLine = lines.shift(); + let fitLine = originalLine; + + for (;;) { + const bbox = getTextBBox(fitLine, fakeText); + bbox.width = fitLine ? bbox.width : 0; + + // fits or too short to split + if ( + fitLine === ' ' || + fitLine === '' || + bbox.width < Math.round(maxWidth) || + fitLine.length < 2 + ) { + return fit(lines, fitLine, originalLine, bbox); + } + + // otherwise shorten and try again + fitLine = shortenLine(fitLine, bbox.width, maxWidth); + } +} + +function fit(lines, fitLine, originalLine, bbox) { + if (fitLine.length < originalLine.length) { + const remainder = originalLine.slice(fitLine.length).trim(); + lines.unshift(remainder); + } + + // is doof + return { + text: fitLine, + width: Math.round(bbox.width), + height: bbox.height, + }; +} + +function getStyleFromLabel(event) { + const styleString = event.gfx.firstChild.firstChild.attributes.style.nodeValue; + + let style = Object.fromEntries( + styleString + .split(';') + .map(part => part.trim()) + .filter(Boolean) + .map(part => { + const [ key, value ] = part.split(':').map(s => s.trim()); + return [ key, value ]; + }) + ); + style['lineHeight'] = event.gfx.firstChild.firstChild.attributes.lineHeight.nodeValue; + + return style; +} + +// ===================================================== +// PUBLIC: measureTextLines() +// ===================================================== + +export function measureTextLines(text, style, maxWidth, isTextAnnotation) { + + // prepare helper text node + const helperText = svgCreate('text'); + svgAttr(helperText, { x: 0, y: 0 }); + svgAttr(helperText, style); + + const helperSvg = getHelperSvg(); + svgAppend(helperSvg, helperText); + + const lines = text.split(/\u00AD?\r?\n/); + const results = []; + + while (lines.length) { + const line = layoutNext(lines, maxWidth, helperText, isTextAnnotation); + results.push(line); + } + + svgRemove(helperText); + + return results; } \ No newline at end of file diff --git a/lib/features/modeling/behavior/LabelBehavior.js b/lib/features/modeling/behavior/LabelBehavior.js index d2c645543c..9da524af14 100644 --- a/lib/features/modeling/behavior/LabelBehavior.js +++ b/lib/features/modeling/behavior/LabelBehavior.js @@ -217,12 +217,25 @@ export default function LabelBehavior( // move external label after resizing this.postExecute('shape.resize', function(event) { - var context = event.context, shape = context.shape, newBounds = context.newBounds, oldBounds = context.oldBounds; + if (shape.type !== 'label' && !hasExternalLabel(shape)) { + return; + } + + // helps to regulate bound rendering to stop overwriting x and y coords after resizing + if ((newBounds.y + newBounds.height === oldBounds.y + oldBounds.height) && (newBounds.y !== oldBounds.y || newBounds.height !== oldBounds.height)) { + event.context.shape.correctBounds = oldBounds; + newBounds = oldBounds; + } + if ((newBounds.x + newBounds.width === oldBounds.x + oldBounds.width) && (newBounds.x !== oldBounds.x || newBounds.width !== oldBounds.width)) { + event.context.shape.correctBounds = oldBounds; + newBounds = oldBounds; + } + if (hasExternalLabel(shape)) { var label = shape.label, diff --git a/lib/features/modeling/behavior/TextAnnotationBehavior.js b/lib/features/modeling/behavior/TextAnnotationBehavior.js index b144f7dc95..a177f137da 100644 --- a/lib/features/modeling/behavior/TextAnnotationBehavior.js +++ b/lib/features/modeling/behavior/TextAnnotationBehavior.js @@ -34,6 +34,25 @@ export default function TextAnnotationBehavior(eventBus) { context.hints.autoResize = false; } }, true); + + this.postExecute('shape.resize', function(event) { + if (event.context.shape.type === 'bpmn:TextAnnotation') { + var context = event.context, + newBounds = context.newBounds, + oldBounds = context.oldBounds; + + // helps to regulate bound rendering to stop overwriting x and y coords after resizing + if (newBounds.y !== oldBounds.y && newBounds.height !== oldBounds.height) { + event.context.shape.oldWayPoint = event.context.shape.incoming?.[0]?.waypoints?.[1]; + event.context.shape.correctBounds = oldBounds; + } + if ((newBounds.x !== oldBounds.x && newBounds.width !== oldBounds.width)) { + event.context.shape.oldWayPoint = event.context.shape.incoming?.[0]?.waypoints?.[1]; + event.context.shape.correctBounds = oldBounds; + } + } + }); + } inherits(TextAnnotationBehavior, CommandInterceptor); diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js index 42979c92c9..260522a5e4 100644 --- a/lib/features/rules/BpmnRules.js +++ b/lib/features/rules/BpmnRules.js @@ -999,11 +999,15 @@ function canResize(shape, newBounds) { return true; } + if (isLabel(shape)) { + return true; + } + return false; } /** - * Check whether one of of the elements to be connected is a text annotation. + * Check whether one of the elements to be connected is a text annotation. * * @param {Element} source * @param {Element} target diff --git a/test/spec/ModelerSpec.js b/test/spec/ModelerSpec.js index 200d2febe9..5ddbd93907 100644 --- a/test/spec/ModelerSpec.js +++ b/test/spec/ModelerSpec.js @@ -4,29 +4,22 @@ import Modeler from 'lib/Modeler'; import Viewer from 'lib/Viewer'; import NavigatedViewer from 'lib/NavigatedViewer'; -import { isAny } from 'lib/util/ModelUtil'; +import { getDi, isAny } from 'lib/util/ModelUtil'; import Clipboard from 'diagram-js/lib/features/clipboard/Clipboard'; import TestContainer from 'mocha-test-container-support'; -import { - createCanvasEvent -} from '../util/MockEvents'; +import { createCanvasEvent } from '../util/MockEvents'; -import { - setBpmnJS, - clearBpmnJS, - collectTranslations, - enableLogging -} from 'test/TestHelper'; +import { bootstrapModeler, clearBpmnJS, collectTranslations, enableLogging, setBpmnJS } from 'test/TestHelper'; -import { - pick, - find -} from 'min-dash'; - -import { getDi } from 'lib/util/ModelUtil'; +import { find, pick } from 'min-dash'; +import createModule from 'diagram-js/lib/features/create'; +import resizeModule from 'diagram-js/lib/features/resize'; +import moveModule from 'diagram-js/lib/features/move'; +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; var singleStart = window.__env__ && window.__env__.SINGLE_START === 'modeler'; @@ -947,6 +940,71 @@ describe('Modeler', function() { }); + describe('resize text element and preserve width', function() { + + var diagramXML = require('../fixtures/bpmn/simple.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: [ + coreModule, + createModule, + modelingModule, + resizeModule, + moveModule + ] + })); + + it('should adapt width of StartEvent label when text is changed and resized horizontally', async function() { + + const result = await createModeler(diagramXML); + expect(result.error).not.to.exist; + + var modeler = result.modeler; + var elementRegistry = modeler.get('elementRegistry'); + var modeling = modeler.get('modeling'); + + var startEvent = elementRegistry.get('StartEvent_2'); + expect(startEvent).to.exist; + + var label = startEvent.label; + expect(label).to.exist; + expect(label.businessObject.name).to.equal('Start'); + + modeling.updateLabel(startEvent, 'This is a much longer start event text'); + + modeling.resizeShape(label, { x: label.x, y: label.y, width: label.width + 20, height: label.height }, 'e'); + + const updatedLabel = elementRegistry.get(label.id); + expect(updatedLabel.width).to.closeTo(105, 3); + }); + + it('should adapt width of StartEvent label when text is changed and resized vertically', async function() { + + const result = await createModeler(diagramXML); + expect(result.error).not.to.exist; + + var modeler = result.modeler; + var elementRegistry = modeler.get('elementRegistry'); + var modeling = modeler.get('modeling'); + + var startEvent = elementRegistry.get('StartEvent_2'); + expect(startEvent).to.exist; + + var label = startEvent.label; + expect(label).to.exist; + expect(label.businessObject.name).to.equal('Start'); + + modeling.updateLabel(startEvent, 'This is a much longer start event text'); + + modeling.resizeShape(label, { x: label.x, y: label.y, width: label.width, height: label.height - 20 }, 'e'); + + const updatedLabel = elementRegistry.get(label.id); + expect(updatedLabel.height).to.equal(36); + }); + + }); + + }); diff --git a/test/spec/draw/BpmnRendererSpec.js b/test/spec/draw/BpmnRendererSpec.js index d1575d908e..5952d9636d 100644 --- a/test/spec/draw/BpmnRendererSpec.js +++ b/test/spec/draw/BpmnRendererSpec.js @@ -914,6 +914,9 @@ describe('draw - bpmn renderer', function() { if (isCollapsedSubProcess(element)) { expect(svgAttr(rect, 'width')).to.equal('100'); expect(svgAttr(rect, 'height')).to.equal('80'); + } else if (element.type === 'bpmn:TextAnnotation') { + expect(svgAttr(rect, 'width')).to.equal('78'); + expect(svgAttr(rect, 'height')).to.equal('40'); } else { expect(svgAttr(rect, 'width')).to.equal('200'); expect(svgAttr(rect, 'height')).to.equal('100'); diff --git a/test/spec/features/auto-place/BpmnAutoPlaceSpec.js b/test/spec/features/auto-place/BpmnAutoPlaceSpec.js index 7bff2e9970..08040439fc 100644 --- a/test/spec/features/auto-place/BpmnAutoPlaceSpec.js +++ b/test/spec/features/auto-place/BpmnAutoPlaceSpec.js @@ -104,14 +104,14 @@ describe('features/auto-place', function() { it('top right of source', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'TASK_2', - expectedBounds: { x: 579, y: 210, width: 100, height: 30 } + expectedBounds: { x: 579, y: 210, width: 100, height: 40 } })); it('above existing', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'TASK_3', - expectedBounds: { x: 896, y: 96, width: 100, height: 30 } + expectedBounds: { x: 896, y: 96, width: 100, height: 40 } })); @@ -120,14 +120,14 @@ describe('features/auto-place', function() { it('top right', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'SequenceFlow_1', - expectedBounds: { x: 500, y: 265, width: 100, height: 30 } + expectedBounds: { x: 500, y: 265, width: 100, height: 40 } })); it('above existing', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'SequenceFlow_1', - expectedBounds: { x: 500, y: 205, width: 100, height: 30 } + expectedBounds: { x: 500, y: 205, width: 100, height: 40 } })); }); @@ -138,14 +138,14 @@ describe('features/auto-place', function() { it('bottom right', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'MessageFlow_1', - expectedBounds: { x: 579, y: 580, width: 100, height: 30 } + expectedBounds: { x: 579, y: 580, width: 100, height: 40 } })); it('next to existing', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'MessageFlow_1', - expectedBounds: { x: 709, y: 580, width: 100, height: 30 } + expectedBounds: { x: 709, y: 580, width: 100, height: 40 } })); }); @@ -245,14 +245,14 @@ describe('features/auto-place', function() { it('bottom right of source', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'V_Task_2', - expectedBounds: { x: 550, y: 530, width: 100, height: 30 } + expectedBounds: { x: 550, y: 530, width: 100, height: 40 } })); it('right of existing', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'V_Task_3', - expectedBounds: { x: 600, y: 840, width: 100, height: 30 } + expectedBounds: { x: 600, y: 840, width: 100, height: 40 } })); @@ -261,14 +261,14 @@ describe('features/auto-place', function() { it('bottom right', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'SequenceFlow_1', - expectedBounds: { x: 500, y: 445, width: 100, height: 30 } + expectedBounds: { x: 500, y: 445, width: 100, height: 40 } })); it('right of existing', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'SequenceFlow_1', - expectedBounds: { x: 630, y: 445, width: 100, height: 30 } + expectedBounds: { x: 630, y: 445, width: 100, height: 40 } })); }); @@ -537,7 +537,7 @@ describe('features/auto-place', function() { it('should place annotation horizontally above Nested_Start_Event', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'Nested_Start_Event', - expectedBounds: { x: 215, y: -1, width: 100, height: 30 } + expectedBounds: { x: 215, y: -1, width: 100, height: 40 } })); @@ -577,7 +577,7 @@ describe('features/auto-place', function() { it('should place annotation horizontally above Nested_Start_Event', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'Nested_Start_Event', - expectedBounds: { x: 215, y: 19, width: 100, height: 30 } + expectedBounds: { x: 215, y: 19, width: 100, height: 40 } })); @@ -617,7 +617,7 @@ describe('features/auto-place', function() { it('should place annotation vertically right of Nested_Start_Event', autoPlace({ element: 'bpmn:TextAnnotation', behind: 'Nested_Start_Event', - expectedBounds: { x: 245, y: 115, width: 100, height: 30 } + expectedBounds: { x: 245, y: 115, width: 100, height: 40 } })); diff --git a/test/spec/features/grid-snapping/BpmnGridSnappingSpec.js b/test/spec/features/grid-snapping/BpmnGridSnappingSpec.js index f3e3f8ac4d..cbbb01c1aa 100644 --- a/test/spec/features/grid-snapping/BpmnGridSnappingSpec.js +++ b/test/spec/features/grid-snapping/BpmnGridSnappingSpec.js @@ -197,7 +197,7 @@ describe('features/grid-snapping', function() { ]); // when - move.start(canvasEvent({ x: 700, y: 20 }), textAnnotation); + move.start(canvasEvent({ x: 700, y: 30 }), textAnnotation); dragging.move(canvasEvent({ x: 706, y: 32 })); dragging.move(canvasEvent({ x: 712, y: 44 })); @@ -209,17 +209,17 @@ describe('features/grid-snapping', function() { // then expect(events.map(position('top-left'))).to.eql([ - { x: 710, y: 30 }, // move - { x: 710, y: 40 }, // move - { x: 720, y: 60 }, // move - { x: 720, y: 70 }, // move - { x: 730, y: 80 }, // move - { x: 730, y: 80 } // end + { x: 710, y: 25 }, // move + { x: 710, y: 35 }, // move + { x: 720, y: 55 }, // move + { x: 720, y: 65 }, // move + { x: 730, y: 75 }, // move + { x: 730, y: 75 } // end ]); // expect snapped to top-left expect(textAnnotation.x).to.equal(730); - expect(textAnnotation.y).to.equal(80); + expect(textAnnotation.y).to.equal(70); })); }); diff --git a/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js b/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js index c4a0b57007..33b3af7b07 100644 --- a/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js +++ b/test/spec/features/grid-snapping/behavior/AutoPlaceBehaviorSpec.js @@ -102,7 +102,7 @@ describe('features/grid-snapping - auto-place', function() { expect(getMid(shape1)).to.eql({ x: 170, // 168 snapped to 170 - y: 15 // 22 snapped to 15 + y: 20 // 22 snapped to 20 }); })); @@ -132,7 +132,7 @@ describe('features/grid-snapping - auto-place', function() { expect(getMid(shape2)).to.eql({ x: 170, // 168 snapped to 170 - y: -45 // -45 snapped to -45 + y: -40 // -45 snapped to -40 }); })); diff --git a/test/spec/features/label-editing/LabelEditingProviderSpec.js b/test/spec/features/label-editing/LabelEditingProviderSpec.js index b5c43c49a9..33fa949931 100644 --- a/test/spec/features/label-editing/LabelEditingProviderSpec.js +++ b/test/spec/features/label-editing/LabelEditingProviderSpec.js @@ -19,7 +19,7 @@ import { var MEDIUM_LINE_HEIGHT = 12 * 1.2; -var DELTA = 3; +var DELTA = 6; describe('features - label-editing', function() { @@ -705,17 +705,13 @@ describe('features - label-editing', function() { var startEvent = elementRegistry.get('StartEvent_1'); var bounds = canvas.getAbsoluteBBox(startEvent.label); - var mid = { - x: bounds.x + bounds.width / 2, - y: bounds.y + bounds.height / 2 - }; directEditing.activate(startEvent); expectBounds(directEditing._textbox.parent, { - x: mid.x - (45 * zoom), + x: bounds.x, y: bounds.y - (7 * zoom), - width: (90 * zoom), + width: bounds.width + 10 * zoom, height: bounds.height + (5 * zoom) + 7 }); } @@ -731,17 +727,13 @@ describe('features - label-editing', function() { var startEvent = elementRegistry.get('StartEvent_1'); var bounds = canvas.getAbsoluteBBox(startEvent.label); - var mid = { - x: bounds.x + bounds.width / 2, - y: bounds.y + bounds.height / 2 - }; directEditing.activate(startEvent); expectBounds(directEditing._textbox.parent, { - x: mid.x - (45 * zoom), + x: bounds.x, y: bounds.y - (7 * zoom), - width: (90 * zoom), + width: bounds.width + 10 * zoom, height: bounds.height + (5 * zoom) + (7 * zoom) }); } @@ -799,17 +791,13 @@ describe('features - label-editing', function() { var sequenceFlow = elementRegistry.get('SequenceFlow_1'); var bounds = canvas.getAbsoluteBBox(sequenceFlow.label); - var mid = { - x: bounds.x + bounds.width / 2, - y: bounds.y + bounds.height / 2 - }; directEditing.activate(sequenceFlow); expectBounds(directEditing._textbox.parent, { - x: mid.x - (45 * zoom), + x: bounds.x, y: bounds.y - (7 * zoom), - width: (90 * zoom), + width: bounds.width + 10 * zoom, height: bounds.height + (5 * zoom) + 7 }); } @@ -825,17 +813,13 @@ describe('features - label-editing', function() { var sequenceflow = elementRegistry.get('SequenceFlow_1'); var bounds = canvas.getAbsoluteBBox(sequenceflow.label); - var mid = { - x: bounds.x + bounds.width / 2, - y: bounds.y + bounds.height / 2 - }; directEditing.activate(sequenceflow); expectBounds(directEditing._textbox.parent, { - x: mid.x - (45 * zoom), + x: bounds.x, y: bounds.y - (7 * zoom), - width: (90 * zoom), + width: bounds.width + 10 * zoom, height: bounds.height + (5 * zoom) + (7 * zoom) }); } diff --git a/test/spec/features/modeling/LabelLayoutingSpec.js b/test/spec/features/modeling/LabelLayoutingSpec.js index 5163eb38ed..66e6ae95da 100644 --- a/test/spec/features/modeling/LabelLayoutingSpec.js +++ b/test/spec/features/modeling/LabelLayoutingSpec.js @@ -506,7 +506,7 @@ describe('modeling - label layouting', function() { // then expect(connection.label.y - labelPosition.y).to.be.within(-77, -73); - expect(connection.label.x - labelPosition.x).to.be.within(-54, -51); + expect(connection.label.x - labelPosition.x).to.be.within(-54, -48); } )); @@ -557,7 +557,7 @@ function getLabelPosition(connection) { var label = connection.label; var mid = { - x: label.x + (label.width / 2), + x: label.x, y: label.y + (label.height / 2) }; diff --git a/test/spec/features/modeling/UpdateLabelSpec.js b/test/spec/features/modeling/UpdateLabelSpec.js index 2d622520d5..61bcec213a 100644 --- a/test/spec/features/modeling/UpdateLabelSpec.js +++ b/test/spec/features/modeling/UpdateLabelSpec.js @@ -153,17 +153,39 @@ describe('features/modeling - update label', function() { // given var element = elementRegistry.get('TextAnnotation_1'); - var newBounds = { x: 100, y: 100, width: 100, height: 30 }; + var newBounds = { x: 100, y: 100, width: 18, height: 40 }; // when modeling.updateLabel(element, 'bar', newBounds); // then expect(element.businessObject.text).to.equal('bar'); - expect(element).to.have.bounds(newBounds); + expectBounds(element, newBounds, 1); } )); + it('should not change text annotation text and bounds', inject( + function(modeling, elementRegistry) { + + // given + var text = 'this should be the text'; + var element = elementRegistry.get('TextAnnotation_1'); + + // when + modeling.updateLabel(element, text); + + var oldBounds = { x: element.x, y: element.y, width: element.width, height: element.height }; + var newBounds = { x: oldBounds.x, y: oldBounds.y + 100, width: oldBounds.width, height: oldBounds.height + 100 }; + + modeling.resizeShape(element, newBounds); + + newBounds = { x: oldBounds.x + 100, y: oldBounds.y, width: oldBounds.width + 100, height: oldBounds.height }; + + // then + expect(element.businessObject.text).to.equal(text); + expectBounds(element, oldBounds, 1); + } + )); it('should update group label', inject(function(modeling, elementRegistry) { @@ -255,7 +277,7 @@ describe('features/modeling - update label', function() { // given var element = elementRegistry.get('TextAnnotation_1'); - var newBounds = { x: 100, y: 100, width: 100, height: 30 }; + var newBounds = { x: 100, y: 100, width: 100, height: 40 }; // when modeling.updateLabel(element, null, newBounds); @@ -296,4 +318,11 @@ describe('features/modeling - update label', function() { }); -}); \ No newline at end of file +}); + +function expectBounds(parent, bounds, delta) { + expect(parent.x).to.be.closeTo(bounds.x, delta); + expect(parent.y).to.be.closeTo(bounds.y, delta); + expect(parent.width).to.be.closeTo(bounds.width, delta); + expect(parent.height).to.be.closeTo(bounds.height, delta); +} diff --git a/test/spec/features/modeling/append/TextAnnotationSpec.js b/test/spec/features/modeling/append/TextAnnotationSpec.js index 0eaa111895..90cfdd8c2a 100644 --- a/test/spec/features/modeling/append/TextAnnotationSpec.js +++ b/test/spec/features/modeling/append/TextAnnotationSpec.js @@ -97,7 +97,23 @@ describe('features/modeling - append text-annotation', function() { // then expect(annotationShape.width).to.eql(100); - expect(annotationShape.height).to.eql(30); + expect(annotationShape.height).to.eql(40); + })); + + it('and stick with right size', inject(function(elementRegistry, elementFactory, modeling) { + + // given + var eventShape = elementRegistry.get('IntermediateCatchEvent_1'); + var oldBounds = { x: eventShape.x, y: eventShape.y, width: eventShape.width, height: eventShape.height }; + var newBounds = { x: oldBounds.x + 100, y: oldBounds.y, width: oldBounds.width + 100, height: oldBounds.height }; + + // when + var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }); + modeling.resizeShape(annotationShape, newBounds); + + // then + expect(annotationShape.width).to.eql(100); + expect(annotationShape.height).to.eql(40); })); }); diff --git a/test/spec/features/modeling/behavior/LabelBehaviorSpec.js b/test/spec/features/modeling/behavior/LabelBehaviorSpec.js index 0e5f2993d4..eabcc64727 100644 --- a/test/spec/features/modeling/behavior/LabelBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/LabelBehaviorSpec.js @@ -621,7 +621,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { // then expect(label.x).to.equal(labelBounds.x); - expect(label.y).to.be.below(labelBounds.y); + expect(label.y).to.be.at.most(labelBounds.y); })); @@ -643,7 +643,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { ); // then - expect(label.x).to.be.above(labelBounds.x); + expect(label.x).to.be.at.least(labelBounds.x); expect(label.y).to.equal(labelBounds.y); })); @@ -667,7 +667,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { // then expect(label.x).to.equal(labelBounds.x); - expect(label.y).to.be.above(labelBounds.y); + expect(label.y).to.be.at.least(labelBounds.y); })); @@ -689,7 +689,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { ); // then - expect(label.x).to.be.below(labelBounds.x); + expect(label.x).to.be.at.most(labelBounds.x); expect(label.y).to.equal(labelBounds.y); })); @@ -739,7 +739,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { // then expect(label.x).to.equal(labelBounds.x); - expect(label.y).to.be.below(labelBounds.y); + expect(label.y).to.be.at.most(labelBounds.y); })); @@ -761,7 +761,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { ); // then - expect(label.x).to.be.above(labelBounds.x); + expect(label.x).to.be.at.least(labelBounds.x); expect(label.y).to.equal(labelBounds.y); })); @@ -785,7 +785,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { // then expect(label.x).to.equal(labelBounds.x); - expect(label.y).to.be.above(labelBounds.y); + expect(label.y).to.be.at.least(labelBounds.y); })); @@ -807,7 +807,7 @@ describe('features/modeling/behavior - LabelBehavior', function() { ); // then - expect(label.x).to.be.below(labelBounds.x); + expect(label.x).to.be.at.most(labelBounds.x); expect(label.y).to.equal(labelBounds.y); })); diff --git a/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js b/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js index 7a13084df5..a11942d00d 100644 --- a/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js @@ -487,8 +487,8 @@ describe('features/modeling - resize behavior', function() { dragging.end(); // then - expect(textAnnotation.width).to.equal(50); - expect(textAnnotation.height).to.equal(30); + expect(textAnnotation.width).to.equal(100); + expect(textAnnotation.height).to.equal(40); })); }); diff --git a/test/spec/features/modeling/layout/LayoutAssociationSpec.js b/test/spec/features/modeling/layout/LayoutAssociationSpec.js index 7a5e7308f0..884d1ea59a 100644 --- a/test/spec/features/modeling/layout/LayoutAssociationSpec.js +++ b/test/spec/features/modeling/layout/LayoutAssociationSpec.js @@ -40,7 +40,7 @@ describe('features/modeling - layout association', function() { // then expect(waypoints).to.eql([ - { original: { x: 400, y: 400 }, x: 389, y: 385 }, + { original: { x: 400, y: 405 }, x: 385, y: 385 }, { original: { x: 191, y: 120 }, x: 202, y: 134 } ]); @@ -62,7 +62,7 @@ describe('features/modeling - layout association', function() { // then expect(waypoints).to.eql([ - { original: { x: 420, y: 400 }, x: 408, y: 385 }, + { original: { x: 420, y: 405 }, x: 404, y: 385 }, { original: { x: 191, y: 120 }, x: 202, y: 134 } ]); @@ -88,7 +88,7 @@ describe('features/modeling - layout association', function() { // then expect(connection).to.have.waypoints([ - { original: { x: 420, y: 400 }, x: 417, y: 385 }, + { original: { x: 420, y: 400 }, x: 416, y: 385 }, { x: 400, y: 300 }, { original: { x: 191, y: 120 }, x: 204, y: 132 } ]); diff --git a/test/spec/features/outline/OutlineProviderSpec.js b/test/spec/features/outline/OutlineProviderSpec.js index 71205372c8..4f2f4df24e 100644 --- a/test/spec/features/outline/OutlineProviderSpec.js +++ b/test/spec/features/outline/OutlineProviderSpec.js @@ -139,7 +139,7 @@ describe('features/outline - outline provider', function() { var DELTA = 3; - it('should update label according to label dimentions', inject(function(elementRegistry, selection, modeling) { + it('should update label according to label dimensions', inject(function(elementRegistry, selection, modeling) { // given var event = elementRegistry.get('Event'); @@ -158,8 +158,8 @@ describe('features/outline - outline provider', function() { // then bounds = outlineShape.getBoundingClientRect(); - expect(bounds.width).to.be.closeTo(93, DELTA); - expect(bounds.height).to.be.closeTo(37, DELTA); + expect(bounds.width).to.be.closeTo(33, DELTA); + expect(bounds.height).to.be.closeTo(25, DELTA); })); });