diff --git a/src/component/mxgraph/BpmnRenderer.ts b/src/component/mxgraph/BpmnRenderer.ts index 6d4819e374..b96c46c269 100644 --- a/src/component/mxgraph/BpmnRenderer.ts +++ b/src/component/mxgraph/BpmnRenderer.ts @@ -34,11 +34,18 @@ import StyleComputer from './renderer/StyleComputer'; * @internal */ export class BpmnRenderer { + private readonly ignoreBpmnActivityLabelBounds: boolean; + private readonly ignoreBpmnTaskLabelBounds: boolean; + constructor( readonly graph: BpmnGraph, readonly coordinatesTranslator: CoordinatesTranslator, readonly styleComputer: StyleComputer, - ) {} + rendererOptions: RendererOptions, + ) { + this.ignoreBpmnActivityLabelBounds = rendererOptions?.ignoreBpmnActivityLabelBounds ?? false; + this.ignoreBpmnTaskLabelBounds = rendererOptions?.ignoreBpmnTaskLabelBounds ?? false; + } render(renderedModel: RenderedModel): void { this.insertShapesAndEdges(renderedModel); @@ -71,9 +78,7 @@ export class BpmnRenderer { const bpmnElement = shape.bpmnElement; const parent = this.getParent(bpmnElement); const bounds = shape.bounds; - let labelBounds = shape.label?.bounds; - // pool/lane label bounds are not managed for now (use hard coded values) - labelBounds = ShapeUtil.isPoolOrLane(bpmnElement.kind) ? undefined : labelBounds; + const labelBounds = isLabelBoundsIgnored(shape, this.ignoreBpmnActivityLabelBounds, this.ignoreBpmnTaskLabelBounds) ? undefined : shape.label?.bounds; const style = this.styleComputer.computeStyle(shape, labelBounds); this.insertVertex(parent, bpmnElement.id, bpmnElement.name, bounds, labelBounds, style); @@ -139,11 +144,27 @@ export class BpmnRenderer { } } +/** + * @internal + */ +export function isLabelBoundsIgnored(shape: Shape, ignoreBpmnActivityLabelBounds: boolean, ignoreBpmnTaskLabelBounds: boolean): boolean { + const kind = shape.bpmnElement.kind; + if (ShapeUtil.isPoolOrLane(kind)) { + return true; + } + + if (ignoreBpmnActivityLabelBounds && ShapeUtil.isActivity(kind)) { + return true; + } + + return ignoreBpmnTaskLabelBounds && ShapeUtil.isTask(kind); +} + /** * @internal */ export function newBpmnRenderer(graph: BpmnGraph, options: RendererOptions): BpmnRenderer { - return new BpmnRenderer(graph, new CoordinatesTranslator(graph), new StyleComputer(options)); + return new BpmnRenderer(graph, new CoordinatesTranslator(graph), new StyleComputer(options), options); } /** diff --git a/src/component/mxgraph/renderer/StyleComputer.ts b/src/component/mxgraph/renderer/StyleComputer.ts index 7bb645a603..d24cb54c42 100644 --- a/src/component/mxgraph/renderer/StyleComputer.ts +++ b/src/component/mxgraph/renderer/StyleComputer.ts @@ -40,14 +40,10 @@ import { BpmnStyleIdentifier } from '../style'; export default class StyleComputer { private readonly ignoreBpmnColors: boolean; private readonly ignoreBpmnLabelStyles: boolean; - private readonly ignoreBpmnActivityLabelBounds: boolean; - private readonly ignoreBpmnTaskLabelBounds: boolean; constructor(options?: RendererOptions) { this.ignoreBpmnColors = options?.ignoreBpmnColors ?? true; this.ignoreBpmnLabelStyles = options?.ignoreBpmnLabelStyles ?? false; - this.ignoreBpmnActivityLabelBounds = options?.ignoreBpmnActivityLabelBounds ?? false; - this.ignoreBpmnTaskLabelBounds = options?.ignoreBpmnTaskLabelBounds ?? false; } computeStyle(bpmnCell: Shape | Edge, labelBounds: Bounds): string { @@ -62,16 +58,12 @@ export default class StyleComputer { } const fontStyleValues = this.computeFontStyleValues(bpmnCell); - const labelStyleValues = this.computeLabelStyleValues(bpmnCell, labelBounds); + const labelStyleValues = computeLabelStyleValues(bpmnCell, labelBounds); styles.push(...toArrayOfMxGraphStyleEntries([...mainStyleValues, ...fontStyleValues, ...labelStyleValues])); return styles.join(';'); } - private computeLabelStyleValues(bpmnCell: Shape | Edge, labelBounds: Bounds): Map { - return computeLabelStyleValues(bpmnCell, labelBounds, this.ignoreBpmnActivityLabelBounds, this.ignoreBpmnTaskLabelBounds); - } - private computeShapeStyleValues(shape: Shape): Map { const styleValues = new Map(); const bpmnElement = shape.bpmnElement; @@ -182,20 +174,11 @@ function computeEdgeBaseStyles(edge: Edge): string[] { return styles; } -function computeLabelStyleValues( - bpmnCell: Shape | Edge, - labelBounds: Bounds, - ignoreBpmnActivityLabelBounds: boolean, - ignoreBpmnTaskLabelBounds: boolean, -): Map { +function computeLabelStyleValues(bpmnCell: Shape | Edge, labelBounds: Bounds): Map { const styleValues = new Map(); const bpmnElement = bpmnCell.bpmnElement; - - // Check if we should ignore label bounds for this element - const shouldIgnoreLabelBounds = shouldIgnoreBpmnLabelBounds(bpmnCell, ignoreBpmnActivityLabelBounds, ignoreBpmnTaskLabelBounds); - - if (labelBounds && !shouldIgnoreLabelBounds) { + if (labelBounds) { styleValues.set(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_TOP); if (bpmnCell.bpmnElement.kind != ShapeBpmnElementKind.TEXT_ANNOTATION) { styleValues.set(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER); @@ -224,32 +207,6 @@ function computeLabelStyleValues( return styleValues; } -/** - * Determines if label bounds should be ignored based on the element type and options. - */ -function shouldIgnoreBpmnLabelBounds(bpmnCell: Shape | Edge, ignoreBpmnActivityLabelBounds: boolean, ignoreBpmnTaskLabelBounds: boolean): boolean { - // Only apply to shapes, not edges - if (!(bpmnCell instanceof Shape)) { - return false; - } - - const bpmnElement = bpmnCell.bpmnElement; - - // If ignoring all activity label bounds - if (ignoreBpmnActivityLabelBounds && bpmnElement instanceof ShapeBpmnActivity) { - return true; - } - - // If ignoring task label bounds only, check if it's a task (but not subprocess or call activity) - if (ignoreBpmnTaskLabelBounds && bpmnElement instanceof ShapeBpmnActivity) { - // Activities include tasks, sub-processes, and call activities - // We only want to ignore bounds for tasks, not sub-processes or call activities - return !(bpmnElement instanceof ShapeBpmnSubProcess) && !(bpmnElement instanceof ShapeBpmnCallActivity); - } - - return false; -} - /** * @internal * @private diff --git a/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/activities.with.wrongly.positioned.labels.png b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/activities.with.wrongly.positioned.labels.png new file mode 100644 index 0000000000..73c0180592 Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/activities.with.wrongly.positioned.labels.png differ diff --git a/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/labels.with.font.styles.png b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/labels.with.font.styles.png new file mode 100644 index 0000000000..79ecfc1d40 Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/ignored/labels.with.font.styles.png differ diff --git a/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/activities.with.wrongly.positioned.labels.png b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/activities.with.wrongly.positioned.labels.png new file mode 100644 index 0000000000..db365ef543 Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/activities.with.wrongly.positioned.labels.png differ diff --git a/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/labels.with.font.styles.png b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/labels.with.font.styles.png new file mode 100644 index 0000000000..543118da19 Binary files /dev/null and b/test/e2e/__image_snapshots__/bpmn-rendering-ignore-options/not-ignored/labels.with.font.styles.png differ diff --git a/test/e2e/bpmn.rendering.ignore.options.test.ts b/test/e2e/bpmn.rendering.ignore.options.test.ts new file mode 100644 index 0000000000..1966bdee40 --- /dev/null +++ b/test/e2e/bpmn.rendering.ignore.options.test.ts @@ -0,0 +1,138 @@ +/* +Copyright 2025 Bonitasoft S.A. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import type { ImageSnapshotThresholdConfig } from './helpers/visu/image-snapshot-config'; + +import { ImageSnapshotConfigurator, MultiBrowserImageSnapshotThresholds } from './helpers/visu/image-snapshot-config'; + +import { AvailableTestPages, PageTester } from '@test/shared/visu/bpmn-page-utils'; + +class ImageSnapshotThresholdsActivityLabelBounds extends MultiBrowserImageSnapshotThresholds { + constructor() { + super({ chromium: 0 / 100, firefox: 0 / 100, webkit: 0 / 100 }); + } + + protected override getChromiumThresholds(): Map { + return new Map(); + } + + protected override getFirefoxThresholds(): Map { + return new Map(); + } + + protected override getWebkitThresholds(): Map { + return new Map(); + } +} + +class ImageSnapshotThresholdsActivityLabelBoundsIgnored extends MultiBrowserImageSnapshotThresholds { + constructor() { + super({ chromium: 0 / 100, firefox: 0 / 100, webkit: 0 / 100 }); + } + + protected override getChromiumThresholds(): Map { + return new Map(); + } + + protected override getFirefoxThresholds(): Map { + return new Map(); + } + + protected override getWebkitThresholds(): Map { + return new Map(); + } +} + +class ImageSnapshotThresholdsLabelStyles extends MultiBrowserImageSnapshotThresholds { + constructor() { + super({ chromium: 0 / 100, firefox: 0 / 100, webkit: 0 / 100 }); + } + + protected override getChromiumThresholds(): Map { + return new Map(); + } + + protected override getFirefoxThresholds(): Map { + return new Map(); + } + + protected override getWebkitThresholds(): Map { + return new Map(); + } +} + +class ImageSnapshotThresholdsLabelStylesIgnored extends MultiBrowserImageSnapshotThresholds { + constructor() { + super({ chromium: 0 / 100, firefox: 0 / 100, webkit: 0 / 100 }); + } + + protected override getChromiumThresholds(): Map { + return new Map(); + } + + protected override getFirefoxThresholds(): Map { + return new Map(); + } + + protected override getWebkitThresholds(): Map { + return new Map(); + } +} + +describe('BPMN rendering - ignore options', () => { + const diagramSubfolder = 'bpmn-rendering-ignore-options'; + const pageTester = new PageTester({ targetedPage: AvailableTestPages.BPMN_RENDERING, diagramSubfolder }, page); + + describe('Ignore activity label bounds', () => { + const bpmnDiagramName = 'activities.with.wrongly.positioned.labels'; + + describe.each([false, true])('ignoreBpmnActivityLabelBounds: %s', (ignoreBpmnActivityLabelBounds: boolean) => { + const imageSnapshotConfigurator = ignoreBpmnActivityLabelBounds + ? new ImageSnapshotConfigurator(new ImageSnapshotThresholdsActivityLabelBoundsIgnored(), 'bpmn-rendering-ignore-options/ignored') + : new ImageSnapshotConfigurator(new ImageSnapshotThresholdsActivityLabelBounds(), 'bpmn-rendering-ignore-options/not-ignored'); + + it(`${bpmnDiagramName}`, async () => { + await pageTester.gotoPageAndLoadBpmnDiagram(bpmnDiagramName, { + rendererIgnoreBpmnActivityLabelBounds: ignoreBpmnActivityLabelBounds, + }); + + const image = await page.screenshot({ fullPage: true }); + const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); + expect(image).toMatchImageSnapshot(config); + }); + }); + }); + + describe('Ignore label styles', () => { + const bpmnDiagramName = 'labels.with.font.styles'; + + describe.each([false, true])('ignoreBpmnLabelStyles: %s', (ignoreBpmnLabelStyles: boolean) => { + const imageSnapshotConfigurator = ignoreBpmnLabelStyles + ? new ImageSnapshotConfigurator(new ImageSnapshotThresholdsLabelStylesIgnored(), 'bpmn-rendering-ignore-options/ignored') + : new ImageSnapshotConfigurator(new ImageSnapshotThresholdsLabelStyles(), 'bpmn-rendering-ignore-options/not-ignored'); + + it(`${bpmnDiagramName}`, async () => { + await pageTester.gotoPageAndLoadBpmnDiagram(bpmnDiagramName, { + rendererIgnoreBpmnLabelStyles: ignoreBpmnLabelStyles, + }); + + const image = await page.screenshot({ fullPage: true }); + const config = imageSnapshotConfigurator.getConfig(bpmnDiagramName); + expect(image).toMatchImageSnapshot(config); + }); + }); + }); +}); diff --git a/test/fixtures/bpmn/bpmn-rendering-ignore-options/activities.with.wrongly.positioned.labels.bpmn b/test/fixtures/bpmn/bpmn-rendering-ignore-options/activities.with.wrongly.positioned.labels.bpmn new file mode 100644 index 0000000000..32e03654bc --- /dev/null +++ b/test/fixtures/bpmn/bpmn-rendering-ignore-options/activities.with.wrongly.positioned.labels.bpmn @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/fixtures/bpmn/bpmn-rendering-ignore-options/labels.with.font.styles.bpmn b/test/fixtures/bpmn/bpmn-rendering-ignore-options/labels.with.font.styles.bpmn new file mode 100644 index 0000000000..bc7a3f607d --- /dev/null +++ b/test/fixtures/bpmn/bpmn-rendering-ignore-options/labels.with.font.styles.bpmn @@ -0,0 +1,34 @@ + + + + + Flow_1 + + + Flow_1 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/fixtures/bpmn/bpmn-rendering/pools.01.labels.and.lanes.bpmn b/test/fixtures/bpmn/bpmn-rendering/pools.01.labels.and.lanes.bpmn index efc62629d9..2602b66766 100644 --- a/test/fixtures/bpmn/bpmn-rendering/pools.01.labels.and.lanes.bpmn +++ b/test/fixtures/bpmn/bpmn-rendering/pools.01.labels.and.lanes.bpmn @@ -38,6 +38,10 @@ + + + + diff --git a/test/fixtures/bpmn/bpmn-rendering/pools.02.vertical.with.lanes.bpmn b/test/fixtures/bpmn/bpmn-rendering/pools.02.vertical.with.lanes.bpmn index 30eaf58ce5..eb361986bd 100644 --- a/test/fixtures/bpmn/bpmn-rendering/pools.02.vertical.with.lanes.bpmn +++ b/test/fixtures/bpmn/bpmn-rendering/pools.02.vertical.with.lanes.bpmn @@ -18,9 +18,17 @@ + + + + + + + + diff --git a/test/fixtures/bpmn/bpmn-rendering/pools.03.black.box.bpmn b/test/fixtures/bpmn/bpmn-rendering/pools.03.black.box.bpmn index be6b75082a..09315b7302 100644 --- a/test/fixtures/bpmn/bpmn-rendering/pools.03.black.box.bpmn +++ b/test/fixtures/bpmn/bpmn-rendering/pools.03.black.box.bpmn @@ -8,6 +8,10 @@ + + + + diff --git a/test/shared/visu/bpmn-page-utils.ts b/test/shared/visu/bpmn-page-utils.ts index 8fc760bad5..1bcf699265 100644 --- a/test/shared/visu/bpmn-page-utils.ts +++ b/test/shared/visu/bpmn-page-utils.ts @@ -143,6 +143,8 @@ export interface PageOptions { bpmnElementIdToCollapse?: string; poolIdsToFilter?: string | string[]; rendererIgnoreBpmnColors?: boolean; + rendererIgnoreBpmnLabelStyles?: boolean; + rendererIgnoreBpmnActivityLabelBounds?: boolean; } export interface Point { @@ -256,6 +258,9 @@ export class PageTester { otherPageOptions?.poolIdsToFilter && (url += `&bpmn.filter.pool.ids=${otherPageOptions.poolIdsToFilter}`); // renderer options otherPageOptions?.rendererIgnoreBpmnColors !== undefined && (url += `&renderer.ignore.bpmn.colors=${otherPageOptions.rendererIgnoreBpmnColors}`); + otherPageOptions?.rendererIgnoreBpmnLabelStyles !== undefined && (url += `&renderer.ignore.label.style=${otherPageOptions.rendererIgnoreBpmnLabelStyles}`); + otherPageOptions?.rendererIgnoreBpmnActivityLabelBounds !== undefined && + (url += `&renderer.ignore.activity.label.bounds=${otherPageOptions.rendererIgnoreBpmnActivityLabelBounds}`); return url; } diff --git a/test/unit/component/mxgraph/BpmnRenderer.test.ts b/test/unit/component/mxgraph/BpmnRenderer.test.ts new file mode 100644 index 0000000000..49645d4b8b --- /dev/null +++ b/test/unit/component/mxgraph/BpmnRenderer.test.ts @@ -0,0 +1,85 @@ +/** + * @jest-environment jsdom + */ +/* +Copyright 2025 Bonitasoft S.A. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { isLabelBoundsIgnored } from '@lib/component/mxgraph/BpmnRenderer'; +import { ShapeBpmnElementKind } from '@lib/model/bpmn/internal'; +import Shape from '@lib/model/bpmn/internal/shape/Shape'; +import ShapeBpmnElement from '@lib/model/bpmn/internal/shape/ShapeBpmnElement'; + +describe('isLabelBoundsIgnored', () => { + describe('no specific option', () => { + const cases: [ShapeBpmnElementKind, boolean][] = [ + [ShapeBpmnElementKind.POOL, true], + [ShapeBpmnElementKind.LANE, true], + [ShapeBpmnElementKind.TASK_USER, false], + [ShapeBpmnElementKind.CALL_ACTIVITY, false], + [ShapeBpmnElementKind.EVENT_START, false], + [ShapeBpmnElementKind.EVENT_END, false], + [ShapeBpmnElementKind.GATEWAY_EXCLUSIVE, false], + [ShapeBpmnElementKind.GATEWAY_PARALLEL, false], + ]; + + test.each(cases)('should ignore %s label bounds? %s', (kind, expected) => { + const shape = new Shape('id', new ShapeBpmnElement('id', 'name', kind)); + expect(isLabelBoundsIgnored(shape, false, false)).toBe(expected); + }); + }); + + describe('with ignoreBpmnActivityLabelBounds option', () => { + const cases: [ShapeBpmnElementKind, boolean][] = [ + [ShapeBpmnElementKind.POOL, true], + [ShapeBpmnElementKind.LANE, true], + [ShapeBpmnElementKind.TASK_USER, true], + [ShapeBpmnElementKind.TASK_SCRIPT, true], + [ShapeBpmnElementKind.CALL_ACTIVITY, true], + [ShapeBpmnElementKind.SUB_PROCESS, true], + [ShapeBpmnElementKind.EVENT_START, false], + [ShapeBpmnElementKind.EVENT_END, false], + [ShapeBpmnElementKind.GATEWAY_EXCLUSIVE, false], + [ShapeBpmnElementKind.GATEWAY_PARALLEL, false], + ]; + + describe.each([true, false])('with ignoreBpmnTaskLabelBounds option set to %b', (ignoreBpmnTaskLabelBounds: boolean) => { + test.each(cases)('should ignore %s label bounds? %s', (kind, expected) => { + const shape = new Shape('id', new ShapeBpmnElement('id', 'name', kind)); + expect(isLabelBoundsIgnored(shape, true, ignoreBpmnTaskLabelBounds)).toBe(expected); + }); + }); + }); + + describe('with ignoreBpmnTaskLabelBounds option', () => { + const cases: [ShapeBpmnElementKind, boolean][] = [ + [ShapeBpmnElementKind.POOL, true], + [ShapeBpmnElementKind.LANE, true], + [ShapeBpmnElementKind.TASK_USER, true], + [ShapeBpmnElementKind.TASK_SCRIPT, true], + [ShapeBpmnElementKind.CALL_ACTIVITY, false], + [ShapeBpmnElementKind.SUB_PROCESS, false], + [ShapeBpmnElementKind.EVENT_START, false], + [ShapeBpmnElementKind.EVENT_END, false], + [ShapeBpmnElementKind.GATEWAY_EXCLUSIVE, false], + [ShapeBpmnElementKind.GATEWAY_PARALLEL, false], + ]; + + test.each(cases)('should ignore %s label bounds? %s', (kind, expected) => { + const shape = new Shape('id', new ShapeBpmnElement('id', 'name', kind)); + expect(isLabelBoundsIgnored(shape, false, true)).toBe(expected); + }); + }); +}); diff --git a/test/unit/component/mxgraph/renderer/StyleComputer.test.ts b/test/unit/component/mxgraph/renderer/StyleComputer.test.ts index f162411ade..46c2d6a144 100644 --- a/test/unit/component/mxgraph/renderer/StyleComputer.test.ts +++ b/test/unit/component/mxgraph/renderer/StyleComputer.test.ts @@ -607,136 +607,4 @@ describe('Style Computer', () => { }); }); }); - - describe('compute style - ignore label bounds options', () => { - describe('ignoreBpmnActivityLabelBounds', () => { - const styleComputer = new StyleComputer({ ignoreBpmnActivityLabelBounds: true }); - - function computeStyleWithIgnoreActivityBounds(element: Shape | Edge): string { - return styleComputer.computeStyle(element, element.label?.bounds); - } - - it('should ignore label bounds for task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Without the ignore option, this would include labelWidth, labelPosition, etc. - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe('task;fontFamily=Arial'); - }); - - it('should ignore label bounds for user task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK_USER), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe('userTask;fontFamily=Arial'); - }); - - it('should ignore label bounds for sub-process', () => { - const shape = newShape(newShapeBpmnSubProcess(ShapeBpmnSubProcessKind.EMBEDDED, []), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe('subProcess;bpmn.subProcessKind=embedded;fontFamily=Arial;verticalAlign=top'); - }); - - it('should ignore label bounds for call activity', () => { - const shape = newShape(newShapeBpmnCallActivityCallingProcess([]), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe('callActivity;fontFamily=Arial;verticalAlign=top'); - }); - - it('should NOT ignore label bounds for events', () => { - const shape = newShape(newShapeBpmnElement(ShapeBpmnElementKind.EVENT_START), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Events are not activities, so their label bounds should still be used - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe( - 'startEvent;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle', - ); - }); - - it('should NOT ignore label bounds for gateways', () => { - const shape = newShape(newShapeBpmnElement(ShapeBpmnElementKind.GATEWAY_EXCLUSIVE), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Gateways are not activities, so their label bounds should still be used - expect(computeStyleWithIgnoreActivityBounds(shape)).toBe( - 'exclusiveGateway;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle', - ); - }); - - it('should NOT ignore label bounds for edges', () => { - const edge = new Edge('id', newSequenceFlow(SequenceFlowKind.NORMAL), undefined, newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Edges are not activities, so their label bounds should still be used - expect(computeStyleWithIgnoreActivityBounds(edge)).toBe('sequenceFlow;normal;fontFamily=Arial;verticalAlign=top;align=center'); - }); - }); - - describe('ignoreBpmnTaskLabelBounds', () => { - const styleComputer = new StyleComputer({ ignoreBpmnTaskLabelBounds: true }); - - function computeStyleWithIgnoreTaskBounds(element: Shape | Edge): string { - return styleComputer.computeStyle(element, element.label?.bounds); - } - - it('should ignore label bounds for task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreTaskBounds(shape)).toBe('task;fontFamily=Arial'); - }); - - it('should ignore label bounds for user task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK_USER), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreTaskBounds(shape)).toBe('userTask;fontFamily=Arial'); - }); - - it('should ignore label bounds for service task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK_SERVICE), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithIgnoreTaskBounds(shape)).toBe('serviceTask;fontFamily=Arial'); - }); - - it('should NOT ignore label bounds for sub-process (still an activity but not a task)', () => { - const shape = newShape(newShapeBpmnSubProcess(ShapeBpmnSubProcessKind.EMBEDDED, []), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Sub-processes should still have their label bounds applied since ignoreBpmnTaskLabelBounds only affects tasks - expect(computeStyleWithIgnoreTaskBounds(shape)).toBe( - 'subProcess;bpmn.subProcessKind=embedded;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle', - ); - }); - - it('should NOT ignore label bounds for call activity (still an activity but not a task)', () => { - const shape = newShape(newShapeBpmnCallActivityCallingProcess([]), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Call activities should still have their label bounds applied since ignoreBpmnTaskLabelBounds only affects tasks - expect(computeStyleWithIgnoreTaskBounds(shape)).toBe( - 'callActivity;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle', - ); - }); - }); - - describe('both options false (default behavior)', () => { - const styleComputer = new StyleComputer({ ignoreBpmnActivityLabelBounds: false, ignoreBpmnTaskLabelBounds: false }); - - function computeStyleWithDefaultOptions(element: Shape | Edge): string { - return styleComputer.computeStyle(element, element.label?.bounds); - } - - it('should use label bounds for task', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithDefaultOptions(shape)).toBe('task;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle'); - }); - - it('should use label bounds for sub-process', () => { - const shape = newShape(newShapeBpmnSubProcess(ShapeBpmnSubProcessKind.EMBEDDED, []), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - expect(computeStyleWithDefaultOptions(shape)).toBe( - 'subProcess;bpmn.subProcessKind=embedded;fontFamily=Arial;verticalAlign=top;align=center;labelWidth=101;labelPosition=ignore;verticalLabelPosition=middle', - ); - }); - }); - - describe('option precedence', () => { - const styleComputer = new StyleComputer({ ignoreBpmnActivityLabelBounds: true, ignoreBpmnTaskLabelBounds: true }); - - function computeStyleWithBothOptions(element: Shape | Edge): string { - return styleComputer.computeStyle(element, element.label?.bounds); - } - - it('should ignore label bounds for task when both options are true', () => { - const shape = newShape(newShapeBpmnActivity(ShapeBpmnElementKind.TASK), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Both options would apply to tasks, result should be the same - expect(computeStyleWithBothOptions(shape)).toBe('task;fontFamily=Arial'); - }); - - it('should ignore label bounds for sub-process when ignoreBpmnActivityLabelBounds is true', () => { - const shape = newShape(newShapeBpmnSubProcess(ShapeBpmnSubProcessKind.EMBEDDED, []), newLabel({ name: 'Arial' }, new Bounds(50, 50, 100, 100))); - // Only ignoreBpmnActivityLabelBounds applies to sub-processes - expect(computeStyleWithBothOptions(shape)).toBe('subProcess;bpmn.subProcessKind=embedded;fontFamily=Arial;verticalAlign=top'); - }); - }); - }); });